DF-4194 / trigger.c
/* * DF-4194 โ userspace demonstration of the ERR_PTR/NULL contract mismatch * in the DRM tile-group path (kernel source-level finding; the QEMU guest * has no DRM device, so this reproduces the pointer arithmetic, not the * kernel fault). * * Kernel chain being demonstrated (see VERDICT.md): * * sys/dev/drm/drm_connector.c:1917 drm_mode_create_tile_group: * kzalloc fail -> return ERR_PTR(-ENOMEM) * sys/dev/drm/drm_connector.c:1929 idr_alloc fail -> tg = ERR_PTR(ret) * sys/dev/drm/drm_edid.c:5141 caller checks ONLY: if (!tg) return -ENOMEM; * -> ERR_PTR is non-NULL, check passes * sys/dev/drm/drm_edid.c:5150 connector->tile_group = ERR_PTR(...) (stored) * * Crash sites, all reached with tile_group == (struct drm_tile_group *)-12: * sys/dev/drm/drm_edid.c:5148 drm_mode_put_tile_group() -> kref_put on ERR_PTR * sys/dev/drm/drm_edid.c:5219 out_drop_ref put -> kref_put on ERR_PTR * sys/dev/drm/drm_connector.c:388 drm_connector_cleanup -> kref_put on ERR_PTR * sys/dev/drm/drm_connector.c:1490 drm_connector_set_tile_property * -> snprintf("%d", connector->tile_group->id) * sys/dev/drm/drm_fb_helper.c:2388 debug print -> tile_group->id * * Build: cc -O2 -o trigger trigger.c * Run: ./trigger */ #include <stdio.h> #include <stddef.h> #include <stdint.h> #include <errno.h> /* struct drm_tile_group โ layout from sys/dev/drm/include/drm/drm_connector.h */ struct kref { unsigned int refcount; }; struct drm_device; struct drm_tile_group { struct kref refcount; /* offset 0 */ struct drm_device *dev; /* offset 8 */ int id; /* offset 16 */ char group_data[8]; /* offset 20 */ }; #define ERR_PTR(x) ((void *)(intptr_t)(x)) #define PTR_ERR(p) ((intptr_t)(p)) #define IS_ERR(p) ((uintptr_t)(p) >= (uintptr_t)PTR_ERR((void *)-4095UL)) #define kref_put(p) (*(volatile unsigned int *)&(p)->refcount -= 1) int main(void) { struct drm_tile_group *tg; puts("== DF-4194: drm_mode_create_tile_group() ERR_PTR vs NULL-check mismatch ==\n"); /* drm_connector.c:1917: allocation failure */ tg = ERR_PTR(-ENOMEM /* -12 */); printf("kernel: drm_mode_create_tile_group() returned ERR_PTR(-ENOMEM) = %p\n", tg); /* drm_edid.c:5141: the ONLY check the caller performs */ printf("caller: if (!tg) return -ENOMEM; -> tg is %s, so the check %s.\n", tg ? "non-NULL" : "NULL", tg ? "PASSES (bug: ERR_PTR slips through)" : "fires"); if (IS_ERR(tg)) printf("caller SHOULD have used IS_ERR(tg) == %d -> the bug.\n", IS_ERR(tg)); /* drm_edid.c:5150: poison stored */ printf("\nconnector->tile_group = tg; /* stored: %p */\n", tg); /* Crash site 1: drm_connector.c:1490 โ connector->tile_group->id (read) */ printf("\n[crash site 1] drm_connector.c:1490 snprintf(\"%%d\", connector->tile_group->id)\n"); printf(" effective address = tg + offsetof(id) = %p + %d = %p\n", (void *)tg, (int)offsetof(struct drm_tile_group, id), (void *)((char *)tg + offsetof(struct drm_tile_group, id))); /* Crash site 2: kref_put(&tg->refcount) (write, atomic dec) โ * drm_edid.c:5148/5219, drm_connector.c:388 */ printf("\n[crash site 2] kref_put(&tile_group->refcount) at drm_edid.c:5148/5219,\n" " drm_connector.c:388\n"); printf(" effective address = tg + offsetof(refcount) = %p + 0 = %p\n", (void *)tg, (void *)((char *)tg + offsetof(struct drm_tile_group, refcount))); printf("\nBoth are fixed, unmapped kernel addresses -> deterministic page fault\n" "(kernel panic) the moment any of the four sites executes.\n" "Sites are reached on the NEXT probe with the same monitor, on probe of a\n" "different monitor (out_drop_ref), at driver unload (cleanup), or โ for DP\n" "MST โ immediately in drm_connector_set_tile_property().\n"); return 0; } |