]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - kernel/sysctl.c
[PATCH] CVE-2005-2709 sysctl unregistration oops
[karo-tx-linux.git] / kernel / sysctl.c
index 701d12c6306844136a8e525902d06082733127fb..a78ec8a3cdcf77e23d53ebf1c9eeaccaedfc9a75 100644 (file)
@@ -58,6 +58,7 @@ extern int sysctl_overcommit_ratio;
 extern int max_threads;
 extern int sysrq_enabled;
 extern int core_uses_pid;
+extern int suid_dumpable;
 extern char core_pattern[];
 extern int cad_pid;
 extern int pid_max;
@@ -113,6 +114,7 @@ extern int unaligned_enabled;
 extern int sysctl_ieee_emulation_warnings;
 #endif
 extern int sysctl_userprocess_debug;
+extern int spin_retry;
 #endif
 
 extern int sysctl_hz_timer;
@@ -145,6 +147,9 @@ extern ctl_table random_table[];
 #ifdef CONFIG_UNIX98_PTYS
 extern ctl_table pty_table[];
 #endif
+#ifdef CONFIG_INOTIFY
+extern ctl_table inotify_table[];
+#endif
 
 #ifdef HAVE_ARCH_PICK_MMAP_LAYOUT
 int sysctl_legacy_va_layout;
@@ -166,7 +171,7 @@ struct file_operations proc_sys_file_operations = {
 
 extern struct proc_dir_entry *proc_sys_root;
 
-static void register_proc_table(ctl_table *, struct proc_dir_entry *);
+static void register_proc_table(ctl_table *, struct proc_dir_entry *, void *);
 static void unregister_proc_table(ctl_table *, struct proc_dir_entry *);
 #endif
 
@@ -217,6 +222,7 @@ static ctl_table root_table[] = {
                .mode           = 0555,
                .child          = dev_table,
        },
+
        { .ctl_name = 0 }
 };
 
@@ -642,7 +648,16 @@ static ctl_table kern_table[] = {
                .mode           = 0644,
                .proc_handler   = &proc_dointvec,
        },
-
+#if defined(CONFIG_ARCH_S390)
+       {
+               .ctl_name       = KERN_SPIN_RETRY,
+               .procname       = "spin_retry",
+               .data           = &spin_retry,
+               .maxlen         = sizeof (int),
+               .mode           = 0644,
+               .proc_handler   = &proc_dointvec,
+       },
+#endif
        { .ctl_name = 0 }
 };
 
@@ -949,7 +964,23 @@ static ctl_table fs_table[] = {
                .mode           = 0644,
                .proc_handler   = &proc_dointvec,
        },
+#ifdef CONFIG_INOTIFY
+       {
+               .ctl_name       = FS_INOTIFY,
+               .procname       = "inotify",
+               .mode           = 0555,
+               .child          = inotify_table,
+       },
+#endif 
 #endif
+       {
+               .ctl_name       = KERN_SETUID_DUMPABLE,
+               .procname       = "suid_dumpable",
+               .data           = &suid_dumpable,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = &proc_dointvec,
+       },
        { .ctl_name = 0 }
 };
 
@@ -959,14 +990,55 @@ static ctl_table debug_table[] = {
 
 static ctl_table dev_table[] = {
        { .ctl_name = 0 }
-};  
+};
 
 extern void init_irq_proc (void);
 
+static DEFINE_SPINLOCK(sysctl_lock);
+
+/* called under sysctl_lock */
+static int use_table(struct ctl_table_header *p)
+{
+       if (unlikely(p->unregistering))
+               return 0;
+       p->used++;
+       return 1;
+}
+
+/* called under sysctl_lock */
+static void unuse_table(struct ctl_table_header *p)
+{
+       if (!--p->used)
+               if (unlikely(p->unregistering))
+                       complete(p->unregistering);
+}
+
+/* called under sysctl_lock, will reacquire if has to wait */
+static void start_unregistering(struct ctl_table_header *p)
+{
+       /*
+        * if p->used is 0, nobody will ever touch that entry again;
+        * we'll eliminate all paths to it before dropping sysctl_lock
+        */
+       if (unlikely(p->used)) {
+               struct completion wait;
+               init_completion(&wait);
+               p->unregistering = &wait;
+               spin_unlock(&sysctl_lock);
+               wait_for_completion(&wait);
+               spin_lock(&sysctl_lock);
+       }
+       /*
+        * do not remove from the list until nobody holds it; walking the
+        * list in do_sysctl() relies on that.
+        */
+       list_del_init(&p->ctl_entry);
+}
+
 void __init sysctl_init(void)
 {
 #ifdef CONFIG_PROC_FS
-       register_proc_table(root_table, proc_sys_root);
+       register_proc_table(root_table, proc_sys_root, &root_table_header);
        init_irq_proc();
 #endif
 }
@@ -975,6 +1047,7 @@ int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *ol
               void __user *newval, size_t newlen)
 {
        struct list_head *tmp;
+       int error = -ENOTDIR;
 
        if (nlen <= 0 || nlen >= CTL_MAXNAME)
                return -ENOTDIR;
@@ -983,21 +1056,30 @@ int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *ol
                if (!oldlenp || get_user(old_len, oldlenp))
                        return -EFAULT;
        }
+       spin_lock(&sysctl_lock);
        tmp = &root_table_header.ctl_entry;
        do {
                struct ctl_table_header *head =
                        list_entry(tmp, struct ctl_table_header, ctl_entry);
                void *context = NULL;
-               int error = parse_table(name, nlen, oldval, oldlenp, 
+
+               if (!use_table(head))
+                       continue;
+
+               spin_unlock(&sysctl_lock);
+
+               error = parse_table(name, nlen, oldval, oldlenp,
                                        newval, newlen, head->ctl_table,
                                        &context);
-               if (context)
-                       kfree(context);
+               kfree(context);
+
+               spin_lock(&sysctl_lock);
+               unuse_table(head);
                if (error != -ENOTDIR)
-                       return error;
-               tmp = tmp->next;
-       } while (tmp != &root_table_header.ctl_entry);
-       return -ENOTDIR;
+                       break;
+       } while ((tmp = tmp->next) != &root_table_header.ctl_entry);
+       spin_unlock(&sysctl_lock);
+       return error;
 }
 
 asmlinkage long sys_sysctl(struct __sysctl_args __user *args)
@@ -1208,12 +1290,16 @@ struct ctl_table_header *register_sysctl_table(ctl_table * table,
                return NULL;
        tmp->ctl_table = table;
        INIT_LIST_HEAD(&tmp->ctl_entry);
+       tmp->used = 0;
+       tmp->unregistering = NULL;
+       spin_lock(&sysctl_lock);
        if (insert_at_head)
                list_add(&tmp->ctl_entry, &root_table_header.ctl_entry);
        else
                list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry);
+       spin_unlock(&sysctl_lock);
 #ifdef CONFIG_PROC_FS
-       register_proc_table(table, proc_sys_root);
+       register_proc_table(table, proc_sys_root, tmp);
 #endif
        return tmp;
 }
@@ -1227,10 +1313,13 @@ struct ctl_table_header *register_sysctl_table(ctl_table * table,
  */
 void unregister_sysctl_table(struct ctl_table_header * header)
 {
-       list_del(&header->ctl_entry);
+       might_sleep();
+       spin_lock(&sysctl_lock);
+       start_unregistering(header);
 #ifdef CONFIG_PROC_FS
        unregister_proc_table(header->ctl_table, proc_sys_root);
 #endif
+       spin_unlock(&sysctl_lock);
        kfree(header);
 }
 
@@ -1241,7 +1330,7 @@ void unregister_sysctl_table(struct ctl_table_header * header)
 #ifdef CONFIG_PROC_FS
 
 /* Scan the sysctl entries in table and add them all into /proc */
-static void register_proc_table(ctl_table * table, struct proc_dir_entry *root)
+static void register_proc_table(ctl_table * table, struct proc_dir_entry *root, void *set)
 {
        struct proc_dir_entry *de;
        int len;
@@ -1277,13 +1366,14 @@ static void register_proc_table(ctl_table * table, struct proc_dir_entry *root)
                        de = create_proc_entry(table->procname, mode, root);
                        if (!de)
                                continue;
+                       de->set = set;
                        de->data = (void *) table;
                        if (table->proc_handler)
                                de->proc_fops = &proc_sys_file_operations;
                }
                table->de = de;
                if (de->mode & S_IFDIR)
-                       register_proc_table(table->child, de);
+                       register_proc_table(table->child, de, set);
        }
 }
 
@@ -1308,6 +1398,13 @@ static void unregister_proc_table(ctl_table * table, struct proc_dir_entry *root
                                continue;
                }
 
+               /*
+                * In any case, mark the entry as goner; we'll keep it
+                * around if it's busy, but we'll know to do nothing with
+                * its fields.  We are under sysctl_lock here.
+                */
+               de->data = NULL;
+
                /* Don't unregister proc entries that are still being used.. */
                if (atomic_read(&de->count))
                        continue;
@@ -1321,27 +1418,38 @@ static ssize_t do_rw_proc(int write, struct file * file, char __user * buf,
                          size_t count, loff_t *ppos)
 {
        int op;
-       struct proc_dir_entry *de;
+       struct proc_dir_entry *de = PDE(file->f_dentry->d_inode);
        struct ctl_table *table;
        size_t res;
-       ssize_t error;
+       ssize_t error = -ENOTDIR;
        
-       de = PDE(file->f_dentry->d_inode);
-       if (!de || !de->data)
-               return -ENOTDIR;
-       table = (struct ctl_table *) de->data;
-       if (!table || !table->proc_handler)
-               return -ENOTDIR;
-       op = (write ? 002 : 004);
-       if (ctl_perm(table, op))
-               return -EPERM;
-       
-       res = count;
-
-       error = (*table->proc_handler) (table, write, file, buf, &res, ppos);
-       if (error)
-               return error;
-       return res;
+       spin_lock(&sysctl_lock);
+       if (de && de->data && use_table(de->set)) {
+               /*
+                * at that point we know that sysctl was not unregistered
+                * and won't be until we finish
+                */
+               spin_unlock(&sysctl_lock);
+               table = (struct ctl_table *) de->data;
+               if (!table || !table->proc_handler)
+                       goto out;
+               error = -EPERM;
+               op = (write ? 002 : 004);
+               if (ctl_perm(table, op))
+                       goto out;
+
+               /* careful: calling conventions are nasty here */
+               res = count;
+               error = (*table->proc_handler)(table, write, file,
+                                               buf, &res, ppos);
+               if (!error)
+                       error = res;
+       out:
+               spin_lock(&sysctl_lock);
+               unuse_table(de->set);
+       }
+       spin_unlock(&sysctl_lock);
+       return error;
 }
 
 static int proc_opensys(struct inode *inode, struct file *file)