DF-4748 / verify.sh
#!/bin/sh # DF-4748 — static lifetime-chain proof # # Claim: amdgpu_dm MST child connectors are kfree()d while a dc_sink they # annotated (dc_sink->priv = aconnector, never cleared) can still be # referenced by a live dc_stream_state; the dm_helpers payload/DPCD helpers # then dereference the freed amdgpu_dm_connector (use-after-free). # # Every step greps the audited tree and asserts an exact line. Exits 0 only # if the whole chain holds. Run from the pack dir: ./verify.sh <repo-root> set -e ROOT="${1:-/home/maxx/dfbsd/dfbsd}" HELPERS="$ROOT/sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c" MST="$ROOT/sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c" DM="$ROOT/sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c" DC="$ROOT/sys/dev/drm/amd/display/dc/core/dc.c" DCL="$ROOT/sys/dev/drm/amd/display/dc/core/dc_link.c" STREAM="$ROOT/sys/dev/drm/amd/display/dc/core/dc_stream.c" TOPO="$ROOT/sys/dev/drm/drm_dp_mst_topology.c" step() { printf 'STEP %s: ' "$1"; } step 1; printf 'sole dc_sink->priv assignment site (mst_types.c:243), never cleared:\n' N=$(grep -rn 'dc_sink->priv *=\|sink->priv *= *NULL' \ "$ROOT/sys/dev/drm/amd/display" 2>/dev/null | grep -v Binary | wc -l) if [ "$N" -eq 1 ]; then grep -rn 'dc_sink->priv =' "$ROOT/sys/dev/drm/amd/display" 2>/dev/null else echo "FAIL: expected exactly one dc_sink->priv assignment, found $N"; exit 1 fi step 2; printf 'helpers deref stream->sink->priv with value-only guard (197/289/317), guard at 199:\n' grep -n 'aconnector = stream->sink->priv;' "$HELPERS" | tr '\n' ' '; echo grep -n 'if (!aconnector || !aconnector->mst_port)' "$HELPERS" | head -1 [ "$(grep -c 'aconnector = stream->sink->priv;' "$HELPERS")" -eq 3 ] || { echo FAIL; exit 1; } step 3; printf 'destroy path drops sink ref + connector ref but never clears sink->priv (mst_types.c:386-397):\n' sed -n '375,397p' "$MST" | grep -n 'port = NULL\|dc_link_remove_remote_sink\|dc_sink_release\|drm_connector_put' step 4; printf 'connector destroy is a raw kfree (amdgpu_dm.c:3187):\n' grep -n 'kfree(connector);' "$DM" step 5; printf 'dc_stream_state holds its own dc_sink reference (dc_stream.c:113), so the sink outlives the connector:\n' grep -n 'dc_sink_release(stream->sink);' "$STREAM" step 6; printf 'dc_link_remove_remote_sink (dc.c:1823) only edits remote_sinks[]; enabled streams stay live in the context:\n' awk 'NR>=1823 && NR<=1846 && (/remote_sinks/ || /sink_count/ || /dc_sink_release/)' "$DC" step 7; printf 'later commits reach the helpers with the old stream (dc_link.c:2621 enable / 2637 disable):\n' grep -n 'allocate_mst_payload(pipe_ctx);\|deallocate_mst_payload(pipe_ctx);' "$DCL" step 8; printf 'destroy runs asynchronously from the topology worker, unsynchronized with dc commits (drm_dp_mst_topology.c:3116):\n' grep -n 'cbs->destroy_connector(mgr, port->connector);' "$TOPO" echo echo 'CHAIN COMPLETE: set-site -> free-without-clear -> ref-outlives -> later deref. QED (static).' |