Commit 330d57fb98a916fa8e1363846540dd420e99499a
Committed by
Linus Torvalds
1 parent
8546df6f35
Exists in
master
and in
4 other branches
[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>
Showing 4 changed files with 116 additions and 31 deletions Side-by-side Diff
arch/s390/appldata/appldata_base.c
... | ... | @@ -592,12 +592,15 @@ |
592 | 592 | */ |
593 | 593 | void appldata_unregister_ops(struct appldata_ops *ops) |
594 | 594 | { |
595 | + void *table; | |
595 | 596 | spin_lock(&appldata_ops_lock); |
596 | - unregister_sysctl_table(ops->sysctl_header); | |
597 | 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 | 600 | ops->ctl_table = NULL; |
600 | 601 | spin_unlock(&appldata_ops_lock); |
602 | + unregister_sysctl_table(ops->sysctl_header); | |
603 | + kfree(table); | |
601 | 604 | P_INFO("%s-ops unregistered!\n", ops->name); |
602 | 605 | } |
603 | 606 | /********************** module-ops management <END> **************************/ |
include/linux/proc_fs.h
include/linux/sysctl.h
... | ... | @@ -24,6 +24,7 @@ |
24 | 24 | #include <linux/compiler.h> |
25 | 25 | |
26 | 26 | struct file; |
27 | +struct completion; | |
27 | 28 | |
28 | 29 | #define CTL_MAXNAME 10 /* how many path components do we allow in a |
29 | 30 | call to sysctl? In other words, what is |
... | ... | @@ -925,6 +926,8 @@ |
925 | 926 | { |
926 | 927 | ctl_table *ctl_table; |
927 | 928 | struct list_head ctl_entry; |
929 | + int used; | |
930 | + struct completion *unregistering; | |
928 | 931 | }; |
929 | 932 | |
930 | 933 | struct ctl_table_header * register_sysctl_table(ctl_table * table, |
kernel/sysctl.c
... | ... | @@ -169,7 +169,7 @@ |
169 | 169 | |
170 | 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 | 173 | static void unregister_proc_table(ctl_table *, struct proc_dir_entry *); |
174 | 174 | #endif |
175 | 175 | |
176 | 176 | |
... | ... | @@ -992,10 +992,51 @@ |
992 | 992 | |
993 | 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 | 1036 | void __init sysctl_init(void) |
996 | 1037 | { |
997 | 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 | 1040 | init_irq_proc(); |
1000 | 1041 | #endif |
1001 | 1042 | } |
... | ... | @@ -1004,6 +1045,7 @@ |
1004 | 1045 | void __user *newval, size_t newlen) |
1005 | 1046 | { |
1006 | 1047 | struct list_head *tmp; |
1048 | + int error = -ENOTDIR; | |
1007 | 1049 | |
1008 | 1050 | if (nlen <= 0 || nlen >= CTL_MAXNAME) |
1009 | 1051 | return -ENOTDIR; |
1010 | 1052 | |
1011 | 1053 | |
1012 | 1054 | |
... | ... | @@ -1012,20 +1054,30 @@ |
1012 | 1054 | if (!oldlenp || get_user(old_len, oldlenp)) |
1013 | 1055 | return -EFAULT; |
1014 | 1056 | } |
1057 | + spin_lock(&sysctl_lock); | |
1015 | 1058 | tmp = &root_table_header.ctl_entry; |
1016 | 1059 | do { |
1017 | 1060 | struct ctl_table_header *head = |
1018 | 1061 | list_entry(tmp, struct ctl_table_header, ctl_entry); |
1019 | 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 | 1070 | newval, newlen, head->ctl_table, |
1022 | 1071 | &context); |
1023 | 1072 | kfree(context); |
1073 | + | |
1074 | + spin_lock(&sysctl_lock); | |
1075 | + unuse_table(head); | |
1024 | 1076 | if (error != -ENOTDIR) |
1025 | - return error; | |
1026 | - tmp = tmp->next; | |
1027 | - } while (tmp != &root_table_header.ctl_entry); | |
1028 | - return -ENOTDIR; | |
1077 | + break; | |
1078 | + } while ((tmp = tmp->next) != &root_table_header.ctl_entry); | |
1079 | + spin_unlock(&sysctl_lock); | |
1080 | + return error; | |
1029 | 1081 | } |
1030 | 1082 | |
1031 | 1083 | asmlinkage long sys_sysctl(struct __sysctl_args __user *args) |
1032 | 1084 | |
1033 | 1085 | |
... | ... | @@ -1236,12 +1288,16 @@ |
1236 | 1288 | return NULL; |
1237 | 1289 | tmp->ctl_table = table; |
1238 | 1290 | INIT_LIST_HEAD(&tmp->ctl_entry); |
1291 | + tmp->used = 0; | |
1292 | + tmp->unregistering = NULL; | |
1293 | + spin_lock(&sysctl_lock); | |
1239 | 1294 | if (insert_at_head) |
1240 | 1295 | list_add(&tmp->ctl_entry, &root_table_header.ctl_entry); |
1241 | 1296 | else |
1242 | 1297 | list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry); |
1298 | + spin_unlock(&sysctl_lock); | |
1243 | 1299 | #ifdef CONFIG_PROC_FS |
1244 | - register_proc_table(table, proc_sys_root); | |
1300 | + register_proc_table(table, proc_sys_root, tmp); | |
1245 | 1301 | #endif |
1246 | 1302 | return tmp; |
1247 | 1303 | } |
1248 | 1304 | |
... | ... | @@ -1255,10 +1311,13 @@ |
1255 | 1311 | */ |
1256 | 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 | 1317 | #ifdef CONFIG_PROC_FS |
1260 | 1318 | unregister_proc_table(header->ctl_table, proc_sys_root); |
1261 | 1319 | #endif |
1320 | + spin_unlock(&sysctl_lock); | |
1262 | 1321 | kfree(header); |
1263 | 1322 | } |
1264 | 1323 | |
... | ... | @@ -1269,7 +1328,7 @@ |
1269 | 1328 | #ifdef CONFIG_PROC_FS |
1270 | 1329 | |
1271 | 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 | 1333 | struct proc_dir_entry *de; |
1275 | 1334 | int len; |
1276 | 1335 | |
... | ... | @@ -1305,13 +1364,14 @@ |
1305 | 1364 | de = create_proc_entry(table->procname, mode, root); |
1306 | 1365 | if (!de) |
1307 | 1366 | continue; |
1367 | + de->set = set; | |
1308 | 1368 | de->data = (void *) table; |
1309 | 1369 | if (table->proc_handler) |
1310 | 1370 | de->proc_fops = &proc_sys_file_operations; |
1311 | 1371 | } |
1312 | 1372 | table->de = de; |
1313 | 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 @@ |
1336 | 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 | 1406 | /* Don't unregister proc entries that are still being used.. */ |
1340 | 1407 | if (atomic_read(&de->count)) |
1341 | 1408 | continue; |
1342 | 1409 | |
1343 | 1410 | |
... | ... | @@ -1349,27 +1416,38 @@ |
1349 | 1416 | size_t count, loff_t *ppos) |
1350 | 1417 | { |
1351 | 1418 | int op; |
1352 | - struct proc_dir_entry *de; | |
1419 | + struct proc_dir_entry *de = PDE(file->f_dentry->d_inode); | |
1353 | 1420 | struct ctl_table *table; |
1354 | 1421 | size_t res; |
1355 | - ssize_t error; | |
1422 | + ssize_t error = -ENOTDIR; | |
1356 | 1423 | |
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 | - | |
1367 | - res = count; | |
1368 | - | |
1369 | - error = (*table->proc_handler) (table, write, file, buf, &res, ppos); | |
1370 | - if (error) | |
1371 | - return error; | |
1372 | - return res; | |
1424 | + spin_lock(&sysctl_lock); | |
1425 | + if (de && de->data && use_table(de->set)) { | |
1426 | + /* | |
1427 | + * at that point we know that sysctl was not unregistered | |
1428 | + * and won't be until we finish | |
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 | 1453 | static int proc_opensys(struct inode *inode, struct file *file) |