DF-5704 / df5704_userland.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | /* * DF-5704 โ equals-walk stack abandonment in prop_object_equals_with_error * (sys/libprop/prop_object.c:1155-1156) violating the _prop_stack drain * contract (sys/libprop/prop_stack.h:60-62, prop_stack.c pop/drain). * * Userland differential against the SAME sys/libprop source the kernel * compiles (lib/libprop/Makefile .PATH ${.CURDIR}/../../sys/libprop). * * Mechanism: * _prop_array_equals() takes BOTH arrays' rwlocks on first iteration * (prop_array.c:219-227) and returns _PROP_OBJECT_EQUALS_RECURSE with * them still held; only pot_equals_finish() (prop_array.c:251-256), * invoked by the finish: drain loop, releases them. When the walk * hits a nested pair of DIFFERENT types, the early return at * prop_object.c:1155-1156 skips the drain: every ancestor's shared * lock is leaked and (>=17 levels) the kmalloc'd/malloc'd * _prop_stack_extern_elem entries (prop_stack.c:49-63) are leaked too. * * Modes: * wedge (default) โ control (types match -> add() OK) vs. buggy * (type mismatch at depth 1 -> prop_array_add() * blocks FOREVER on the leaked wrlock; proven by * fork+alarm(5): child exit 42 == still blocked). * leak โ 17-deep chains, type mismatch at the leaf: every * prop_array_equals() call leaks exactly one * malloc'd extern stack element; RSS climbs. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/resource.h> #include <libprop/proplib.h> #define EXIT_WEDGED 42 /* alarm fired while blocked in add() */ static void on_alarm(int sig __unused) { _exit(EXIT_WEDGED); } /* * Child: build a1=[leaf1], a2=[leaf2], run prop_array_equals(), then try * to WRITE a1 (prop_array_add takes the wrlock). If the equals leaked * a1's rdlock, add() never returns and SIGALRM (5s) ends us with 42. */ static int wedge_child(const char *leaf1, const char *leaf2) { prop_array_t a1, a2; prop_object_t o1, o2; bool eq; a1 = prop_array_create(); a2 = prop_array_create(); if (leaf2 == NULL) { /* type-mismatch variant */ o1 = prop_string_create_cstring(leaf1); o2 = prop_number_create_integer(5); } else { /* control: both strings */ o1 = prop_string_create_cstring(leaf1); o2 = prop_string_create_cstring(leaf2); } prop_array_add(a1, o1); prop_array_add(a2, o2); prop_object_release(o1); prop_object_release(o2); fprintf(stderr, " [child] built a1/a2, calling prop_array_equals...\n"); eq = prop_array_equals(a1, a2); fprintf(stderr, " [child] equals -> %d\n", eq); signal(SIGALRM, on_alarm); alarm(5); fprintf(stderr, " [child] calling prop_array_add (wrlock on a1)...\n"); fflush(stderr); if (prop_array_add(a1, prop_bool_create(true)) == false) { alarm(0); fprintf(stderr, " [child] add returned FALSE (no wedge)\n"); return (0); } alarm(0); fprintf(stderr, " [child] add returned TRUE (no wedge)\n"); return (0); } static int run_wedge(const char *label, const char *leaf1, const char *leaf2) { pid_t pid; int st; printf("== %s ==\n", label); fflush(stdout); pid = fork(); if (pid == 0) _exit(wedge_child(leaf1, leaf2)); waitpid(pid, &st, 0); if (WIFEXITED(st)) { int rc = WEXITSTATUS(st); if (rc == EXIT_WEDGED) { printf(" -> child STILL BLOCKED in prop_array_add " "after 5s: rdlock LEAKED (exit 42)\n"); return (1); } printf(" -> child exited %d: add() completed\n", rc); return (0); } printf(" -> child died abnormally: %d\n", st); return (2); } /* * Build a depth-@depth@ chain of arrays wrapping a final leaf. * type 0 -> string leaf, type 1 -> integer leaf. */ static prop_array_t build_chain(int depth, int type) { prop_array_t outer = NULL, cur = NULL; prop_object_t leaf; int i; leaf = type == 0 ? (prop_object_t)prop_string_create_cstring("s") : (prop_object_t)prop_number_create_integer(5); cur = (prop_array_t)leaf; for (i = 0; i < depth; i++) { outer = prop_array_create(); prop_array_add(outer, cur); prop_object_release(cur); cur = outer; } return (cur); } static void rss_checkpoint(const char *tag, int iters) { struct rusage ru; getrusage(RUSAGE_SELF, &ru); printf(" [%s] after %d equals(): ru_maxrss = %ld kB\n", tag, iters, ru.ru_maxrss); } static int run_leak(int iters) { prop_array_t a, b; int i; printf("== leak: 17-deep chains, type mismatch at leaf ==\n"); printf(" (each prop_array_equals() abandons exactly one malloc'd" " _prop_stack_extern_elem)\n"); a = build_chain(17, 0); /* [[[...["s"]...]]] */ b = build_chain(17, 1); /* [[[...[5]...]]] */ rss_checkpoint("start", 0); for (i = 1; i <= iters; i++) { (void)prop_array_equals(a, b); if (i % (iters / 4) == 0) rss_checkpoint("leak", i); } printf(" -> if ru_maxrss climbed ~ %d kB, the extern elements" " leaked\n", iters * 48 / 1024); return (0); } int main(int argc, char **argv) { const char *mode = argc > 1 ? argv[1] : "wedge"; int iters = argc > 2 ? atoi(argv[2]) : 400000; int rc = 0; if (strcmp(mode, "wedge") == 0) { rc |= run_wedge("control: a1=[\"x\"] vs a2=[\"y\"]" " (types match, equals false by value)", "x", "y"); rc |= run_wedge("buggy: a1=[\"x\"] vs a2=[5]" " (type mismatch at depth 1)", "x", NULL); printf("\nDF-5704 wedge result: %s\n", rc & 1 ? "REPRODUCED (control unwedged, buggy wedged)" : "not reproduced"); return (rc); } if (strcmp(mode, "leak") == 0) return (run_leak(iters)); fprintf(stderr, "usage: %s [wedge|leak [iters]]\n", argv[0]); return (64); } |