diff options
| author | Al Viro <viro@zeniv.linux.org.uk> | 2005-11-04 05:18:40 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-11-08 20:57:30 -0500 |
| commit | 330d57fb98a916fa8e1363846540dd420e99499a (patch) | |
| tree | 841d5e5eeda46fd95ac03c36964919818a9bc3a6 | |
| parent | 8546df6f357dadf1989ad8da9309c9524fd56cdf (diff) | |
[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>
| -rw-r--r-- | arch/s390/appldata/appldata_base.c | 7 | ||||
| -rw-r--r-- | include/linux/proc_fs.h | 1 | ||||
| -rw-r--r-- | include/linux/sysctl.h | 3 | ||||
| -rw-r--r-- | kernel/sysctl.c | 136 |
4 files changed, 116 insertions, 31 deletions
diff --git a/arch/s390/appldata/appldata_base.c b/arch/s390/appldata/appldata_base.c index c9f2f60cfa58..dee6ab54984d 100644 --- a/arch/s390/appldata/appldata_base.c +++ b/arch/s390/appldata/appldata_base.c | |||
| @@ -592,12 +592,15 @@ int appldata_register_ops(struct appldata_ops *ops) | |||
| 592 | */ | 592 | */ |
| 593 | void appldata_unregister_ops(struct appldata_ops *ops) | 593 | void appldata_unregister_ops(struct appldata_ops *ops) |
| 594 | { | 594 | { |
| 595 | void *table; | ||
| 595 | spin_lock(&appldata_ops_lock); | 596 | spin_lock(&appldata_ops_lock); |
| 596 | unregister_sysctl_table(ops->sysctl_header); | ||
| 597 | list_del(&ops->list); | 597 | list_del(&ops->list); |
| 598 | kfree(ops->ctl_table); | 598 | /* at that point any incoming access will fail */ |
| 599 | table = ops->ctl_table; | ||
| 599 | ops->ctl_table = NULL; | 600 | ops->ctl_table = NULL; |
| 600 | spin_unlock(&appldata_ops_lock); | 601 | spin_unlock(&appldata_ops_lock); |
| 602 | unregister_sysctl_table(ops->sysctl_header); | ||
| 603 | kfree(table); | ||
| 601 | P_INFO("%s-ops unregistered!\n", ops->name); | 604 | P_INFO("%s-ops unregistered!\n", ops->name); |
| 602 | } | 605 | } |
| 603 | /********************** module-ops management <END> **************************/ | 606 | /********************** module-ops management <END> **************************/ |
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h index 65ceeaa30652..74488e49166d 100644 --- a/include/linux/proc_fs.h +++ b/include/linux/proc_fs.h | |||
| @@ -66,6 +66,7 @@ struct proc_dir_entry { | |||
| 66 | write_proc_t *write_proc; | 66 | write_proc_t *write_proc; |
| 67 | atomic_t count; /* use count */ | 67 | atomic_t count; /* use count */ |
| 68 | int deleted; /* delete flag */ | 68 | int deleted; /* delete flag */ |
| 69 | void *set; | ||
| 69 | }; | 70 | }; |
| 70 | 71 | ||
| 71 | struct kcore_list { | 72 | struct kcore_list { |
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index fc8e367f671e..fc131d6602b9 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h | |||
| @@ -24,6 +24,7 @@ | |||
| 24 | #include <linux/compiler.h> | 24 | #include <linux/compiler.h> |
| 25 | 25 | ||
| 26 | struct file; | 26 | struct file; |
| 27 | struct completion; | ||
| 27 | 28 | ||
| 28 | #define CTL_MAXNAME 10 /* how many path components do we allow in a | 29 | #define CTL_MAXNAME 10 /* how many path components do we allow in a |
| 29 | call to sysctl? In other words, what is | 30 | call to sysctl? In other words, what is |
| @@ -925,6 +926,8 @@ struct ctl_table_header | |||
| 925 | { | 926 | { |
| 926 | ctl_table *ctl_table; | 927 | ctl_table *ctl_table; |
| 927 | struct list_head ctl_entry; | 928 | struct list_head ctl_entry; |
| 929 | int used; | ||
| 930 | struct completion *unregistering; | ||
| 928 | }; | 931 | }; |
| 929 | 932 | ||
| 930 | struct ctl_table_header * register_sysctl_table(ctl_table * table, | 933 | struct ctl_table_header * register_sysctl_table(ctl_table * table, |
diff --git a/kernel/sysctl.c b/kernel/sysctl.c index c4f35f96884d..9990e10192e8 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c | |||
| @@ -169,7 +169,7 @@ struct file_operations proc_sys_file_operations = { | |||
| 169 | 169 | ||
| 170 | extern struct proc_dir_entry *proc_sys_root; | 170 | extern struct proc_dir_entry *proc_sys_root; |
| 171 | 171 | ||
| 172 | static void register_proc_table(ctl_table *, struct proc_dir_entry *); | 172 | static void register_proc_table(ctl_table *, struct proc_dir_entry *, void *); |
| 173 | static void unregister_proc_table(ctl_table *, struct proc_dir_entry *); | 173 | static void unregister_proc_table(ctl_table *, struct proc_dir_entry *); |
| 174 | #endif | 174 | #endif |
| 175 | 175 | ||
| @@ -992,10 +992,51 @@ static ctl_table dev_table[] = { | |||
| 992 | 992 | ||
| 993 | extern void init_irq_proc (void); | 993 | extern void init_irq_proc (void); |
| 994 | 994 | ||
| 995 | static DEFINE_SPINLOCK(sysctl_lock); | ||
| 996 | |||
| 997 | /* called under sysctl_lock */ | ||
| 998 | static int use_table(struct ctl_table_header *p) | ||
| 999 | { | ||
| 1000 | if (unlikely(p->unregistering)) | ||
| 1001 | return 0; | ||
| 1002 | p->used++; | ||
| 1003 | return 1; | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | /* called under sysctl_lock */ | ||
| 1007 | static void unuse_table(struct ctl_table_header *p) | ||
| 1008 | { | ||
| 1009 | if (!--p->used) | ||
| 1010 | if (unlikely(p->unregistering)) | ||
| 1011 | complete(p->unregistering); | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | /* called under sysctl_lock, will reacquire if has to wait */ | ||
| 1015 | static void start_unregistering(struct ctl_table_header *p) | ||
| 1016 | { | ||
| 1017 | /* | ||
| 1018 | * if p->used is 0, nobody will ever touch that entry again; | ||
| 1019 | * we'll eliminate all paths to it before dropping sysctl_lock | ||
| 1020 | */ | ||
| 1021 | if (unlikely(p->used)) { | ||
| 1022 | struct completion wait; | ||
| 1023 | init_completion(&wait); | ||
| 1024 | p->unregistering = &wait; | ||
| 1025 | spin_unlock(&sysctl_lock); | ||
| 1026 | wait_for_completion(&wait); | ||
| 1027 | spin_lock(&sysctl_lock); | ||
| 1028 | } | ||
| 1029 | /* | ||
| 1030 | * do not remove from the list until nobody holds it; walking the | ||
| 1031 | * list in do_sysctl() relies on that. | ||
| 1032 | */ | ||
| 1033 | list_del_init(&p->ctl_entry); | ||
| 1034 | } | ||
| 1035 | |||
| 995 | void __init sysctl_init(void) | 1036 | void __init sysctl_init(void) |
| 996 | { | 1037 | { |
| 997 | #ifdef CONFIG_PROC_FS | 1038 | #ifdef CONFIG_PROC_FS |
| 998 | register_proc_table(root_table, proc_sys_root); | 1039 | register_proc_table(root_table, proc_sys_root, &root_table_header); |
| 999 | init_irq_proc(); | 1040 | init_irq_proc(); |
| 1000 | #endif | 1041 | #endif |
| 1001 | } | 1042 | } |
| @@ -1004,6 +1045,7 @@ int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *ol | |||
| 1004 | void __user *newval, size_t newlen) | 1045 | void __user *newval, size_t newlen) |
| 1005 | { | 1046 | { |
| 1006 | struct list_head *tmp; | 1047 | struct list_head *tmp; |
| 1048 | int error = -ENOTDIR; | ||
| 1007 | 1049 | ||
| 1008 | if (nlen <= 0 || nlen >= CTL_MAXNAME) | 1050 | if (nlen <= 0 || nlen >= CTL_MAXNAME) |
| 1009 | return -ENOTDIR; | 1051 | return -ENOTDIR; |
| @@ -1012,20 +1054,30 @@ int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *ol | |||
| 1012 | if (!oldlenp || get_user(old_len, oldlenp)) | 1054 | if (!oldlenp || get_user(old_len, oldlenp)) |
| 1013 | return -EFAULT; | 1055 | return -EFAULT; |
| 1014 | } | 1056 | } |
| 1057 | spin_lock(&sysctl_lock); | ||
| 1015 | tmp = &root_table_header.ctl_entry; | 1058 | tmp = &root_table_header.ctl_entry; |
| 1016 | do { | 1059 | do { |
| 1017 | struct ctl_table_header *head = | 1060 | struct ctl_table_header *head = |
| 1018 | list_entry(tmp, struct ctl_table_header, ctl_entry); | 1061 | list_entry(tmp, struct ctl_table_header, ctl_entry); |
| 1019 | void *context = NULL; | 1062 | void *context = NULL; |
| 1020 | int error = parse_table(name, nlen, oldval, oldlenp, | 1063 | |
| 1064 | if (!use_table(head)) | ||
| 1065 | continue; | ||
| 1066 | |||
| 1067 | spin_unlock(&sysctl_lock); | ||
| 1068 | |||
| 1069 | error = parse_table(name, nlen, oldval, oldlenp, | ||
| 1021 | newval, newlen, head->ctl_table, | 1070 | newval, newlen, head->ctl_table, |
| 1022 | &context); | 1071 | &context); |
| 1023 | kfree(context); | 1072 | kfree(context); |
| 1073 | |||
| 1074 | spin_lock(&sysctl_lock); | ||
| 1075 | unuse_table(head); | ||
| 1024 | if (error != -ENOTDIR) | 1076 | if (error != -ENOTDIR) |
| 1025 | return error; | 1077 | break; |
| 1026 | tmp = tmp->next; | 1078 | } while ((tmp = tmp->next) != &root_table_header.ctl_entry); |
| 1027 | } while (tmp != &root_table_header.ctl_entry); | 1079 | spin_unlock(&sysctl_lock); |
| 1028 | return -ENOTDIR; | 1080 | return error; |
| 1029 | } | 1081 | } |
| 1030 | 1082 | ||
| 1031 | asmlinkage long sys_sysctl(struct __sysctl_args __user *args) | 1083 | asmlinkage long sys_sysctl(struct __sysctl_args __user *args) |
| @@ -1236,12 +1288,16 @@ struct ctl_table_header *register_sysctl_table(ctl_table * table, | |||
| 1236 | return NULL; | 1288 | return NULL; |
| 1237 | tmp->ctl_table = table; | 1289 | tmp->ctl_table = table; |
| 1238 | INIT_LIST_HEAD(&tmp->ctl_entry); | 1290 | INIT_LIST_HEAD(&tmp->ctl_entry); |
| 1291 | tmp->used = 0; | ||
| 1292 | tmp->unregistering = NULL; | ||
| 1293 | spin_lock(&sysctl_lock); | ||
| 1239 | if (insert_at_head) | 1294 | if (insert_at_head) |
| 1240 | list_add(&tmp->ctl_entry, &root_table_header.ctl_entry); | 1295 | list_add(&tmp->ctl_entry, &root_table_header.ctl_entry); |
| 1241 | else | 1296 | else |
| 1242 | list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry); | 1297 | list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry); |
| 1298 | spin_unlock(&sysctl_lock); | ||
| 1243 | #ifdef CONFIG_PROC_FS | 1299 | #ifdef CONFIG_PROC_FS |
| 1244 | register_proc_table(table, proc_sys_root); | 1300 | register_proc_table(table, proc_sys_root, tmp); |
| 1245 | #endif | 1301 | #endif |
| 1246 | return tmp; | 1302 | return tmp; |
| 1247 | } | 1303 | } |
| @@ -1255,10 +1311,13 @@ struct ctl_table_header *register_sysctl_table(ctl_table * table, | |||
| 1255 | */ | 1311 | */ |
| 1256 | void unregister_sysctl_table(struct ctl_table_header * header) | 1312 | void unregister_sysctl_table(struct ctl_table_header * header) |
| 1257 | { | 1313 | { |
| 1258 | list_del(&header->ctl_entry); | 1314 | might_sleep(); |
| 1315 | spin_lock(&sysctl_lock); | ||
| 1316 | start_unregistering(header); | ||
| 1259 | #ifdef CONFIG_PROC_FS | 1317 | #ifdef CONFIG_PROC_FS |
| 1260 | unregister_proc_table(header->ctl_table, proc_sys_root); | 1318 | unregister_proc_table(header->ctl_table, proc_sys_root); |
| 1261 | #endif | 1319 | #endif |
| 1320 | spin_unlock(&sysctl_lock); | ||
| 1262 | kfree(header); | 1321 | kfree(header); |
| 1263 | } | 1322 | } |
| 1264 | 1323 | ||
| @@ -1269,7 +1328,7 @@ void unregister_sysctl_table(struct ctl_table_header * header) | |||
| 1269 | #ifdef CONFIG_PROC_FS | 1328 | #ifdef CONFIG_PROC_FS |
| 1270 | 1329 | ||
| 1271 | /* Scan the sysctl entries in table and add them all into /proc */ | 1330 | /* Scan the sysctl entries in table and add them all into /proc */ |
| 1272 | static void register_proc_table(ctl_table * table, struct proc_dir_entry *root) | 1331 | static void register_proc_table(ctl_table * table, struct proc_dir_entry *root, void *set) |
| 1273 | { | 1332 | { |
| 1274 | struct proc_dir_entry *de; | 1333 | struct proc_dir_entry *de; |
| 1275 | int len; | 1334 | int len; |
| @@ -1305,13 +1364,14 @@ static void register_proc_table(ctl_table * table, struct proc_dir_entry *root) | |||
| 1305 | de = create_proc_entry(table->procname, mode, root); | 1364 | de = create_proc_entry(table->procname, mode, root); |
| 1306 | if (!de) | 1365 | if (!de) |
| 1307 | continue; | 1366 | continue; |
| 1367 | de->set = set; | ||
| 1308 | de->data = (void *) table; | 1368 | de->data = (void *) table; |
| 1309 | if (table->proc_handler) | 1369 | if (table->proc_handler) |
| 1310 | de->proc_fops = &proc_sys_file_operations; | 1370 | de->proc_fops = &proc_sys_file_operations; |
| 1311 | } | 1371 | } |
| 1312 | table->de = de; | 1372 | table->de = de; |
| 1313 | if (de->mode & S_IFDIR) | 1373 | if (de->mode & S_IFDIR) |
| 1314 | register_proc_table(table->child, de); | 1374 | register_proc_table(table->child, de, set); |
| 1315 | } | 1375 | } |
| 1316 | } | 1376 | } |
| 1317 | 1377 | ||
| @@ -1336,6 +1396,13 @@ static void unregister_proc_table(ctl_table * table, struct proc_dir_entry *root | |||
| 1336 | continue; | 1396 | continue; |
| 1337 | } | 1397 | } |
| 1338 | 1398 | ||
| 1399 | /* | ||
| 1400 | * In any case, mark the entry as goner; we'll keep it | ||
| 1401 | * around if it's busy, but we'll know to do nothing with | ||
| 1402 | * its fields. We are under sysctl_lock here. | ||
| 1403 | */ | ||
| 1404 | de->data = NULL; | ||
| 1405 | |||
| 1339 | /* Don't unregister proc entries that are still being used.. */ | 1406 | /* Don't unregister proc entries that are still being used.. */ |
| 1340 | if (atomic_read(&de->count)) | 1407 | if (atomic_read(&de->count)) |
| 1341 | continue; | 1408 | continue; |
| @@ -1349,27 +1416,38 @@ static ssize_t do_rw_proc(int write, struct file * file, char __user * buf, | |||
| 1349 | size_t count, loff_t *ppos) | 1416 | size_t count, loff_t *ppos) |
| 1350 | { | 1417 | { |
| 1351 | int op; | 1418 | int op; |
| 1352 | struct proc_dir_entry *de; | 1419 | struct proc_dir_entry *de = PDE(file->f_dentry->d_inode); |
| 1353 | struct ctl_table *table; | 1420 | struct ctl_table *table; |
| 1354 | size_t res; | 1421 | size_t res; |
| 1355 | ssize_t error; | 1422 | ssize_t error = -ENOTDIR; |
| 1356 | |||
| 1357 | de = PDE(file->f_dentry->d_inode); | ||
| 1358 | if (!de || !de->data) | ||
| 1359 | return -ENOTDIR; | ||
| 1360 | table = (struct ctl_table *) de->data; | ||
| 1361 | if (!table || !table->proc_handler) | ||
| 1362 | return -ENOTDIR; | ||
| 1363 | op = (write ? 002 : 004); | ||
| 1364 | if (ctl_perm(table, op)) | ||
| 1365 | return -EPERM; | ||
| 1366 | 1423 | ||
| 1367 | res = count; | 1424 | spin_lock(&sysctl_lock); |
| 1368 | 1425 | if (de && de->data && use_table(de->set)) { | |
| 1369 | error = (*table->proc_handler) (table, write, file, buf, &res, ppos); | 1426 | /* |
| 1370 | if (error) | 1427 | * at that point we know that sysctl was not unregistered |
| 1371 | return error; | 1428 | * and won't be until we finish |
| 1372 | return res; | 1429 | */ |
| 1430 | spin_unlock(&sysctl_lock); | ||
| 1431 | table = (struct ctl_table *) de->data; | ||
| 1432 | if (!table || !table->proc_handler) | ||
| 1433 | goto out; | ||
| 1434 | error = -EPERM; | ||
| 1435 | op = (write ? 002 : 004); | ||
| 1436 | if (ctl_perm(table, op)) | ||
| 1437 | goto out; | ||
| 1438 | |||
| 1439 | /* careful: calling conventions are nasty here */ | ||
| 1440 | res = count; | ||
| 1441 | error = (*table->proc_handler)(table, write, file, | ||
| 1442 | buf, &res, ppos); | ||
| 1443 | if (!error) | ||
| 1444 | error = res; | ||
| 1445 | out: | ||
| 1446 | spin_lock(&sysctl_lock); | ||
| 1447 | unuse_table(de->set); | ||
| 1448 | } | ||
| 1449 | spin_unlock(&sysctl_lock); | ||
| 1450 | return error; | ||
| 1373 | } | 1451 | } |
| 1374 | 1452 | ||
| 1375 | static int proc_opensys(struct inode *inode, struct file *file) | 1453 | static int proc_opensys(struct inode *inode, struct file *file) |
