DF-2989 / tree2989.c
/* DF-2989 (userland mirror of tree.h scan_info_done): the walk * 'while (*infopp != scan) infopp = &(*infopp)->link;' has no NULL exit. * With a corrupted rbh_inprog list that does not contain our scan, the * walk dereferences NULL (or wild memory). Compiled against the same * macros from sys/sys/tree.h (RB_SCAN_LOCK is a no-op outside _KERNEL). * * demo1: healthy list -> clean unlink (both kernels) * demo2: head->rbh_inprog hijacked to an unrelated scan chain * -> stock tree.h: SIGSEGV (unbounded walk off the chain end) * build: cc -O -o tree2989 tree2989.c -I/usr/src/sys */ #include <sys/tree.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> struct item { RB_ENTRY(item) entry; int key; }; static int item_cmp(struct item *a, struct item *b); RB_HEAD(item_tree, item); RB_PROTOTYPE(item_tree, item, entry, item_cmp); RB_GENERATE(item_tree, item, entry, item_cmp); static int item_cmp(struct item *a, struct item *b) { return (a->key - b->key); } int main(int argc, char **argv) { struct item_tree head; struct item items[8]; struct item_tree_scan_info info, other; int i; RB_INIT(&head); memset(&info, 0, sizeof(info)); memset(&other, 0, sizeof(other)); for (i = 0; i < 8; i++) { items[i].key = i * 10; RB_INSERT(item_tree, &head, &items[i]); } /* demo 1: healthy scan lifecycle */ item_tree_scan_info_link(&info, &head); item_tree_scan_info_done(&info, &head); printf("demo1: healthy scan_info_done ok, inprog=%p\n", (void *)head.rbh_inprog); /* demo 2: hijacked inprog list (prior corruption elsewhere) */ other.link = NULL; item_tree_scan_info_link(&info, &head); /* corruption strikes AFTER our scan registered: head now points at * an unrelated chain that does NOT contain &info */ head.rbh_inprog = &other; printf("demo2: inprog=%p (hijacked), calling scan_info_done...\n", (void *)head.rbh_inprog); fflush(stdout); item_tree_scan_info_done(&info, &head); printf("demo2: returned cleanly (fixed tree.h)\n"); return 0; } |