⬢ DragonFlyBSD Kernel Audit
DF-4748 / verify.sh
← back to finding ↓ download raw
#!/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).'