2.4 port of CVE-2005-2709 fix
hey Marcelo,
I've backported the fix for CVE-2005-2709 to 2.4 for Debian's 2.4
sarge kernel. Below is a patch against 2.4.32, in case one hasn't been
submitted to you yet. Please apply.
Signed-off-by: dann frazier <dannf@debian.org>
diff -urNp linux-2.4.32.orig/include/linux/proc_fs.h linux-2.4.32/include/linux/proc_fs.h
--- linux-2.4.32.orig/include/linux/proc_fs.h 2005-01-19 07:10:12.000000000 -0700
+++ linux-2.4.32/include/linux/proc_fs.h 2005-11-18 10:21:16.000000000 -0700
@@ -70,6 +70,7 @@ struct proc_dir_entry {
atomic_t count; /* use count */
int deleted; /* delete flag */
kdev_t rdev;
+ void *set;
};
#define PROC_INODE_PROPER(inode) ((inode)->i_ino & ~0xffff)
diff -urNp linux-2.4.32.orig/include/linux/sysctl.h linux-2.4.32/include/linux/sysctl.h
--- linux-2.4.32.orig/include/linux/sysctl.h 2005-04-03 19:42:20.000000000 -0600
+++ linux-2.4.32/include/linux/sysctl.h 2005-11-18 10:21:20.000000000 -0700
@@ -29,6 +29,7 @@
#include <linux/list.h>
struct file;
+struct completion;
#define CTL_MAXNAME 10
@@ -829,6 +830,8 @@ struct ctl_table_header
{
ctl_table *ctl_table;
struct list_head ctl_entry;
+ int used;
+ struct completion *unregistering;
};
struct ctl_table_header * register_sysctl_table(ctl_table * table,
diff -urNp linux-2.4.32.orig/kernel/sysctl.c linux-2.4.32/kernel/sysctl.c
--- linux-2.4.32.orig/kernel/sysctl.c 2005-01-19 07:10:13.000000000 -0700
+++ linux-2.4.32/kernel/sysctl.c 2005-11-18 10:41:30.000000000 -0700
@@ -147,7 +147,7 @@ static struct inode_operations proc_sys_
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
@@ -360,10 +360,51 @@ static ctl_table dev_table[] = {
extern void init_irq_proc (void);
+static spinlock_t sysctl_lock = SPIN_LOCK_UNLOCKED;
+
+/* called under sysctl_lock */
+static int use_table(struct ctl_table_header *p)
+{
+ if (unlikely(p->unregistering != NULL))
+ 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 != NULL))
+ 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
}
@@ -372,6 +413,7 @@ int do_sysctl(int *name, int nlen, void
void *newval, size_t newlen)
{
struct list_head *tmp;
+ int error = -ENOTDIR;
if (nlen <= 0 || nlen >= CTL_MAXNAME)
return -ENOTDIR;
@@ -383,21 +425,31 @@ int do_sysctl(int *name, int nlen, void
if ((ssize_t)old_len < 0)
return -EINVAL;
}
+ 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);
+
+ 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;
}
extern asmlinkage long sys_sysctl(struct __sysctl_args *args)
@@ -604,12 +656,16 @@ struct ctl_table_header *register_sysctl
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;
}
@@ -623,10 +679,12 @@ struct ctl_table_header *register_sysctl
*/
void unregister_sysctl_table(struct ctl_table_header * header)
{
- list_del(&header->ctl_entry);
+ 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);
}
@@ -637,7 +695,7 @@ void unregister_sysctl_table(struct ctl_
#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;
@@ -673,6 +731,7 @@ static void register_proc_table(ctl_tabl
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;
@@ -681,7 +740,7 @@ static void register_proc_table(ctl_tabl
}
table->de = de;
if (de->mode & S_IFDIR)
- register_proc_table(table->child, de);
+ register_proc_table(table->child, de, set);
}
}
@@ -706,6 +765,13 @@ static void unregister_proc_table(ctl_ta
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;
@@ -719,31 +785,44 @@ static ssize_t do_rw_proc(int write, str
size_t count, loff_t *ppos)
{
int op;
- struct proc_dir_entry *de;
+ struct proc_dir_entry *de =
+ (struct proc_dir_entry*) file->f_dentry->d_inode->u.generic_ip;
struct ctl_table *table;
size_t res;
- ssize_t error;
-
- de = (struct proc_dir_entry*) file->f_dentry->d_inode->u.generic_ip;
- 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;
+ ssize_t error = -ENOTDIR;
- /*
- * FIXME: we need to pass on ppos to the handler.
- */
+ 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);
- if (error)
- return error;
- return res;
+ /*
+ * FIXME: we need to pass on ppos to the handler.
+ */
+
+ error = (*table->proc_handler)(table, write, file,
+ buf, &res);
+ if (!error)
+ error = res;
+ out:
+ spin_lock(&sysctl_lock);
+ unuse_table(de->set);
+ }
+ spin_unlock(&sysctl_lock);
+ return error;
}
static ssize_t proc_readsys(struct file * file, char * buf,
Reply to: