[PATCH] Fix sysctl unregistration oops (CVE-2005-2709)
You could open the /proc/sys/net/ipv4/conf/<if>/<whatever> file, then wait for interface to go away, try to grab as much memory as possible in hope to hit the (kfreed) ctl_table. Then fill it with pointers to your function. Then do read from file you've opened and if you are lucky, you'll get it called as ->proc_handler() in kernel mode. So this is at least an Oops and possibly more. It does depend on an interface going away though, so less of a security risk than it would otherwise be. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
8546df6f35
commit
330d57fb98
4 changed files with 116 additions and 31 deletions
|
@ -592,12 +592,15 @@ int appldata_register_ops(struct appldata_ops *ops)
|
||||||
*/
|
*/
|
||||||
void appldata_unregister_ops(struct appldata_ops *ops)
|
void appldata_unregister_ops(struct appldata_ops *ops)
|
||||||
{
|
{
|
||||||
|
void *table;
|
||||||
spin_lock(&appldata_ops_lock);
|
spin_lock(&appldata_ops_lock);
|
||||||
unregister_sysctl_table(ops->sysctl_header);
|
|
||||||
list_del(&ops->list);
|
list_del(&ops->list);
|
||||||
kfree(ops->ctl_table);
|
/* at that point any incoming access will fail */
|
||||||
|
table = ops->ctl_table;
|
||||||
ops->ctl_table = NULL;
|
ops->ctl_table = NULL;
|
||||||
spin_unlock(&appldata_ops_lock);
|
spin_unlock(&appldata_ops_lock);
|
||||||
|
unregister_sysctl_table(ops->sysctl_header);
|
||||||
|
kfree(table);
|
||||||
P_INFO("%s-ops unregistered!\n", ops->name);
|
P_INFO("%s-ops unregistered!\n", ops->name);
|
||||||
}
|
}
|
||||||
/********************** module-ops management <END> **************************/
|
/********************** module-ops management <END> **************************/
|
||||||
|
|
|
@ -66,6 +66,7 @@ struct proc_dir_entry {
|
||||||
write_proc_t *write_proc;
|
write_proc_t *write_proc;
|
||||||
atomic_t count; /* use count */
|
atomic_t count; /* use count */
|
||||||
int deleted; /* delete flag */
|
int deleted; /* delete flag */
|
||||||
|
void *set;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct kcore_list {
|
struct kcore_list {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include <linux/compiler.h>
|
#include <linux/compiler.h>
|
||||||
|
|
||||||
struct file;
|
struct file;
|
||||||
|
struct completion;
|
||||||
|
|
||||||
#define CTL_MAXNAME 10 /* how many path components do we allow in a
|
#define CTL_MAXNAME 10 /* how many path components do we allow in a
|
||||||
call to sysctl? In other words, what is
|
call to sysctl? In other words, what is
|
||||||
|
@ -925,6 +926,8 @@ struct ctl_table_header
|
||||||
{
|
{
|
||||||
ctl_table *ctl_table;
|
ctl_table *ctl_table;
|
||||||
struct list_head ctl_entry;
|
struct list_head ctl_entry;
|
||||||
|
int used;
|
||||||
|
struct completion *unregistering;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ctl_table_header * register_sysctl_table(ctl_table * table,
|
struct ctl_table_header * register_sysctl_table(ctl_table * table,
|
||||||
|
|
120
kernel/sysctl.c
120
kernel/sysctl.c
|
@ -169,7 +169,7 @@ struct file_operations proc_sys_file_operations = {
|
||||||
|
|
||||||
extern struct proc_dir_entry *proc_sys_root;
|
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 *);
|
static void unregister_proc_table(ctl_table *, struct proc_dir_entry *);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -992,10 +992,51 @@ static ctl_table dev_table[] = {
|
||||||
|
|
||||||
extern void init_irq_proc (void);
|
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)
|
void __init sysctl_init(void)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_PROC_FS
|
#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();
|
init_irq_proc();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -1004,6 +1045,7 @@ int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *ol
|
||||||
void __user *newval, size_t newlen)
|
void __user *newval, size_t newlen)
|
||||||
{
|
{
|
||||||
struct list_head *tmp;
|
struct list_head *tmp;
|
||||||
|
int error = -ENOTDIR;
|
||||||
|
|
||||||
if (nlen <= 0 || nlen >= CTL_MAXNAME)
|
if (nlen <= 0 || nlen >= CTL_MAXNAME)
|
||||||
return -ENOTDIR;
|
return -ENOTDIR;
|
||||||
|
@ -1012,20 +1054,30 @@ int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *ol
|
||||||
if (!oldlenp || get_user(old_len, oldlenp))
|
if (!oldlenp || get_user(old_len, oldlenp))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
}
|
}
|
||||||
|
spin_lock(&sysctl_lock);
|
||||||
tmp = &root_table_header.ctl_entry;
|
tmp = &root_table_header.ctl_entry;
|
||||||
do {
|
do {
|
||||||
struct ctl_table_header *head =
|
struct ctl_table_header *head =
|
||||||
list_entry(tmp, struct ctl_table_header, ctl_entry);
|
list_entry(tmp, struct ctl_table_header, ctl_entry);
|
||||||
void *context = NULL;
|
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,
|
newval, newlen, head->ctl_table,
|
||||||
&context);
|
&context);
|
||||||
kfree(context);
|
kfree(context);
|
||||||
|
|
||||||
|
spin_lock(&sysctl_lock);
|
||||||
|
unuse_table(head);
|
||||||
if (error != -ENOTDIR)
|
if (error != -ENOTDIR)
|
||||||
|
break;
|
||||||
|
} while ((tmp = tmp->next) != &root_table_header.ctl_entry);
|
||||||
|
spin_unlock(&sysctl_lock);
|
||||||
return error;
|
return error;
|
||||||
tmp = tmp->next;
|
|
||||||
} while (tmp != &root_table_header.ctl_entry);
|
|
||||||
return -ENOTDIR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage long sys_sysctl(struct __sysctl_args __user *args)
|
asmlinkage long sys_sysctl(struct __sysctl_args __user *args)
|
||||||
|
@ -1236,12 +1288,16 @@ struct ctl_table_header *register_sysctl_table(ctl_table * table,
|
||||||
return NULL;
|
return NULL;
|
||||||
tmp->ctl_table = table;
|
tmp->ctl_table = table;
|
||||||
INIT_LIST_HEAD(&tmp->ctl_entry);
|
INIT_LIST_HEAD(&tmp->ctl_entry);
|
||||||
|
tmp->used = 0;
|
||||||
|
tmp->unregistering = NULL;
|
||||||
|
spin_lock(&sysctl_lock);
|
||||||
if (insert_at_head)
|
if (insert_at_head)
|
||||||
list_add(&tmp->ctl_entry, &root_table_header.ctl_entry);
|
list_add(&tmp->ctl_entry, &root_table_header.ctl_entry);
|
||||||
else
|
else
|
||||||
list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry);
|
list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry);
|
||||||
|
spin_unlock(&sysctl_lock);
|
||||||
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
||||||
register_proc_table(table, proc_sys_root);
|
register_proc_table(table, proc_sys_root, tmp);
|
||||||
#endif
|
#endif
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
@ -1255,10 +1311,13 @@ struct ctl_table_header *register_sysctl_table(ctl_table * table,
|
||||||
*/
|
*/
|
||||||
void unregister_sysctl_table(struct ctl_table_header * header)
|
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
|
#ifdef CONFIG_PROC_FS
|
||||||
unregister_proc_table(header->ctl_table, proc_sys_root);
|
unregister_proc_table(header->ctl_table, proc_sys_root);
|
||||||
#endif
|
#endif
|
||||||
|
spin_unlock(&sysctl_lock);
|
||||||
kfree(header);
|
kfree(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1269,7 +1328,7 @@ void unregister_sysctl_table(struct ctl_table_header * header)
|
||||||
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
||||||
|
|
||||||
/* Scan the sysctl entries in table and add them all into /proc */
|
/* 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;
|
struct proc_dir_entry *de;
|
||||||
int len;
|
int len;
|
||||||
|
@ -1305,13 +1364,14 @@ static void register_proc_table(ctl_table * table, struct proc_dir_entry *root)
|
||||||
de = create_proc_entry(table->procname, mode, root);
|
de = create_proc_entry(table->procname, mode, root);
|
||||||
if (!de)
|
if (!de)
|
||||||
continue;
|
continue;
|
||||||
|
de->set = set;
|
||||||
de->data = (void *) table;
|
de->data = (void *) table;
|
||||||
if (table->proc_handler)
|
if (table->proc_handler)
|
||||||
de->proc_fops = &proc_sys_file_operations;
|
de->proc_fops = &proc_sys_file_operations;
|
||||||
}
|
}
|
||||||
table->de = de;
|
table->de = de;
|
||||||
if (de->mode & S_IFDIR)
|
if (de->mode & S_IFDIR)
|
||||||
register_proc_table(table->child, de);
|
register_proc_table(table->child, de, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1336,6 +1396,13 @@ static void unregister_proc_table(ctl_table * table, struct proc_dir_entry *root
|
||||||
continue;
|
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.. */
|
/* Don't unregister proc entries that are still being used.. */
|
||||||
if (atomic_read(&de->count))
|
if (atomic_read(&de->count))
|
||||||
continue;
|
continue;
|
||||||
|
@ -1349,27 +1416,38 @@ static ssize_t do_rw_proc(int write, struct file * file, char __user * buf,
|
||||||
size_t count, loff_t *ppos)
|
size_t count, loff_t *ppos)
|
||||||
{
|
{
|
||||||
int op;
|
int op;
|
||||||
struct proc_dir_entry *de;
|
struct proc_dir_entry *de = PDE(file->f_dentry->d_inode);
|
||||||
struct ctl_table *table;
|
struct ctl_table *table;
|
||||||
size_t res;
|
size_t res;
|
||||||
ssize_t error;
|
ssize_t error = -ENOTDIR;
|
||||||
|
|
||||||
de = PDE(file->f_dentry->d_inode);
|
spin_lock(&sysctl_lock);
|
||||||
if (!de || !de->data)
|
if (de && de->data && use_table(de->set)) {
|
||||||
return -ENOTDIR;
|
/*
|
||||||
|
* 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;
|
table = (struct ctl_table *) de->data;
|
||||||
if (!table || !table->proc_handler)
|
if (!table || !table->proc_handler)
|
||||||
return -ENOTDIR;
|
goto out;
|
||||||
|
error = -EPERM;
|
||||||
op = (write ? 002 : 004);
|
op = (write ? 002 : 004);
|
||||||
if (ctl_perm(table, op))
|
if (ctl_perm(table, op))
|
||||||
return -EPERM;
|
goto out;
|
||||||
|
|
||||||
|
/* careful: calling conventions are nasty here */
|
||||||
res = count;
|
res = count;
|
||||||
|
error = (*table->proc_handler)(table, write, file,
|
||||||
error = (*table->proc_handler) (table, write, file, buf, &res, ppos);
|
buf, &res, ppos);
|
||||||
if (error)
|
if (!error)
|
||||||
|
error = res;
|
||||||
|
out:
|
||||||
|
spin_lock(&sysctl_lock);
|
||||||
|
unuse_table(de->set);
|
||||||
|
}
|
||||||
|
spin_unlock(&sysctl_lock);
|
||||||
return error;
|
return error;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int proc_opensys(struct inode *inode, struct file *file)
|
static int proc_opensys(struct inode *inode, struct file *file)
|
||||||
|
|
Loading…
Reference in a new issue