Error-path UAF: amdgpu_dm init failure paths kfree registered i2c adapters / connectors / encoders / planes that remain in DRM core lists
Summary
amdgpu_dm_connector_init() registers the DDC adapter via i2c_add_adapter but out_free kfrees it on later failure without i2c_del_adapter - the adapter stays in the i2c core pointing at freed memory. amdgpu_dm_initialize_drm_device()'s fail path unconditionally kfrees the last aencoder/aconnector and all planes even when fully registered in mode_config lists; the driver's own error unwind then calls drm_mode_config_cleanup() which walks those lists over the freed objects, and pending HPD IRQs use the freed aconnector cookie. Fault-injection/OOM class: an allocation or debugfs failure during DM init (boot/module load) turns the normal error unwind into a UAF - not attacker-timing-controllable, hence Low. Contrast DF-1153 which covers different functions and lifetime owners.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3656 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| env.txt | β | 323 B | view raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| fix.diff | β | 2.0 KB | view raw | |
| manifest.json | β | 997 B | view raw | |
| verdict.json | β | 3.6 KB | view raw | |
| README.md | β | 2.5 KB | β raw |
DF-3656 β Error-path UAF: registered i2c adapter / connector / encoder / planes freed while still in core lists
File: sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c
Lines: 4080-4136 (amdgpu_dm_connector_init out_free), 1937-1942 (amdgpu_dm_initialize_drm_device fail)
Severity: Low (requires OOM / debugfs failure at driver init β fault-injection class) Β· CWE-416 Β· confidence: certain
What
Two broken unwinds leave dangling pointers in DRM/i2c core lists:
-
amdgpu_dm_connector_init()registers the DDC i2c adapter (i2c_add_adapter(&i2c->base), amdgpu_dm.c:4080), then continues (drm_connector_init4089,connector_debugfs_init4120). On any later failure it runsout_free:which doeskfree(i2c)(4133) withouti2c_del_adapter(), and returns an error. Ifdrm_connector_inithad already succeeded (debugfs failure case), the connector is also still registered indev->mode_config.connector_listwhile the caller frees it. -
amdgpu_dm_initialize_drm_device()'sfail:path (1937-1942) β reached e.g. fromdce110_register_irq_handlers()failure (1922) after all connectors/encoders were fully created and registered β unconditionally doeskfree(aencoder); kfree(aconnector);for the last-allocated pair andkfree(mode_info->planes[i])for all planes, none of which are de-registered. Every one of those objects is still referenced bydev->mode_config.{connector,encoder,plane}_list, andaconnectoris still thelink->privHPD-cookie and themst_mgrowner.
The error path then runs amdgpu_dm_fini() β amdgpu_dm_destroy_drm_device()
β drm_mode_config_cleanup() (1947) which iterates those very lists and
calls cleanup on the freed memory β use-after-free. Likewise any later
i2c transaction on the DDC bus dereferences the freed adapter (i2c_get_adapdata).
Trigger
Allocation failure or debugfs failure during display-manager init (module load / boot / GPU reset re-init). Not attacker-timing-controllable without fault injection β hence Low severity β but it is exactly the error-path lifetime class the pass-2 audit targets (contrast DF-1153, which covers the reset functions, not this init unwind).
Reproduction
Not runnable on the audit guest (virtio-only, no AMD GPU β env.txt); source-level certainty. No harness is meaningful for an OOM-timing UAF; the VERDICT.md contains the full ownership trace.
Contents
env.txt Β· VERDICT.md Β· fix.diff Β· manifest.json Β· verdict.json Β· README.md
DF-3656 β VERDICT
Status: inconclusive (runtime hardware+OOM-gated on the virtio-only guest); ownership defect is source-certain.
Root cause (ownership trace)
Site 1 β amdgpu_dm_connector_init() (amdgpu_dm.c:4058-4137)
4073 i2c = create_i2c(link->ddc, link->link_index, &res);
...
4080 res = i2c_add_adapter(&i2c->base); /* ADAPTER REGISTERED */
4082 if (res) { ...; goto out_free; }
4089 res = drm_connector_init(dm->ddev, &aconnector->base, ...);
/* CONNECTOR REGISTERED */
4095 if (res) { ...; goto out_free; }
...
4119 #if defined(CONFIG_DEBUG_FS)
4120 res = connector_debugfs_init(aconnector);
4121 if (res) { ...; goto out_free; } /* connector ALREADY registered */
...
4131 out_free:
4132 if (res) {
4133 kfree(i2c); /* freed while registered */
4134 aconnector->i2c = NULL;
4135 }
4136 return res;
- On the debugfs-failure path the registered i2c adapter is kfree'd
without
i2c_del_adapter()β it remains in the i2c core's adapter list withadapdatapointing at freed memory; the next DDC transaction on that bus (EDID probe, CEC) is a UAF. - The caller (
amdgpu_dm_initialize_drm_device, 1875-1878) thengoto failand additionally kfrees the registeredaconnector(see site 2), which is still linked indev->mode_config.connector_list, stilllink->priv(4069), and still ownsdm_dp_aux/mst_mgr.
Site 2 β amdgpu_dm_initialize_drm_device() fail path (1937-1943)
1937 fail:
1938 kfree(aencoder);
1939 kfree(aconnector);
1940 for (i = 0; i < dm->dc->caps.max_planes; i++)
1941 kfree(mode_info->planes[i]);
1942 return -EINVAL;
Reached after full registration when e.g. dce110_register_irq_handlers()
fails (1915-1918/1922-1925 β amdgpu_irq_add_id() failure) or any
connector-loop init step fails for the last pair:
aencoderwas registered byamdgpu_dm_encoder_init()βdrm_encoder_init()(4164) β linked inmode_config.encoder_list; kfree'd withoutdrm_encoder_cleanup()β dangling.aconnectorregistered viadrm_connector_init(pluslink->priv,link->ddci2c, CEC) β dangling.mode_info->planes[i]registered viadrm_universal_plane_init()(3647/3657/3667) β linked inmode_config.plane_list; kfree'd withoutdrm_plane_cleanup()β dangling.
The error unwinds via amdgpu_dm_init() error β amdgpu_dm_fini() (487) β
amdgpu_dm_destroy_drm_device() β drm_mode_config_cleanup(dm->ddev)
(1947), which walks connector_list/encoder_list/plane_list and calls
the objects' ->destroy hooks on freed memory β use-after-free
(deterministic once the error path is taken). Additionally, any HPD IRQ
delivered before the lists are pruned uses the freed aconnector cookie
(handle_hpd_irq(void *param) β aconnector->hpd_lock at 1083).
Distinct from DF-1153 (reset-function kzalloc-failure UAF): different functions, different lifetime owners (DRM core lists vs crtc/plane state).
Why Low
Triggering requires an allocation or debugfs failure during DM init β an
OOM/fault-injection condition, not attacker-timing-controllable. Impact once
triggered: UAF in drm_mode_config_cleanup / i2c core β memory corruption
class, but the practical exposure is boot/module-load flakiness.
Verification (Phase V)
Guest is virtio-only (env.txt) β amdgpu never attaches; additionally the
trigger needs an induced allocation failure at exactly this window. No
meaningful userspace model exists for a kernel-object ownership bug; verdict
recorded as inconclusive with the source trace as evidence. Fix:
fix.diff (de-register before free / let mode_config own registered
objects); git apply --check clean. fix_status: not_testable.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableRequires amdgpu hardware plus fault injection to validate (baseline UAF vs patched clean teardown). fix.diff implements de-registration/ownership fix; git apply --check verified against sys/; not applied (read-only audit target).
fix.diff in this pack
Confirmed kernel references
Detail
Exploit chain
Fault-injected amdgpu init failure after registration (e.g. amdgpu_irq_add_id failure at 1915, or connector_debugfs_init failure at 4120) -> fail path kfrees registered objects -> amdgpu_dm_fini -> drm_mode_config_cleanup iterates mode_config lists over freed memory -> use-after-free write/read during teardown (or freed i2c adapter dereference on the next DDC access).
Evidence (decisive lines)
VERDICT.md (full ownership trace with registration/free line proofs); fix.diff (de-register i2c before free; only free not-yet-registered objects, let drm_mode_config_cleanup own the rest; git apply --check clean); env.txt (guest has no AMD GPU)
PoC changes
n/a β no seeded PoC; no harness is meaningful for an OOM-timing kernel-object ownership bug
Verified recommended fix
In amdgpu_dm_connector_init failure paths call i2c_del_adapter() before kfree(i2c); in amdgpu_dm_initialize_drm_device's fail path only free objects DRM core does not yet own (sentinel ids) and leave registered objects to drm_mode_config_cleanup().
Verdict
Source-certain error-path UAF, not runnable on the audit guest. (1) amdgpu_dm_connector_init() registers the DDC adapter with i2c_add_adapter (amdgpu_dm.c:4080) but its out_free handler kfrees it on any later failure (4133) without i2c_del_adapter β the adapter stays in the i2c core with adapdata pointing at freed memory; the next DDC/EDID/CEC transaction is a UAF. (2) amdgpu_dm_initialize_drm_device()'s fail path (1937-1942) unconditionally kfrees the last aencoder/aconnector and all mode_info->planes[] even when they were fully registered into dev->mode_config.{encoder,connector,plane}_lists (encoder via amdgpu_dm_encoder_init:4164, connector via drm_connector_init:4089, planes via drm_universal_plane_init:3647+) β and the driver's own error unwind immediately calls drm_mode_config_cleanup() (amdgpu_dm_destroy_drm_device:1947) which walks those lists and invokes the freed objects' destroy hooks; HPD IRQs also use the freed aconnector cookie (handle_hpd_irq -> aconnector->hpd_lock, 1083). Trigger requires an allocation or debugfs failure during DM init (fault-injection class) β hence Low severity. Guest is virtio-only (env.txt) and the trigger additionally needs induced OOM, so runtime reproduction is impossible here: inconclusive, source-certain. Distinct from DF-1153 (reset-function kzalloc failures), different functions and lifetime owners.
No comments yet.