[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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: