DragonFlyBSD Kernel Audit
DF-1090 / verify.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-1090 static source-verification script.
# Confirms each affected descriptor type accesses fixed offsets without a length check.
set +e
PASS=0; FAIL=0
check() {
	if eval "$2"; then
		echo "PASS  $1"
		PASS=$((PASS+1))
	else
		echo "FAIL  $1"
		FAIL=$((FAIL+1))
	fi
}

F=sys/bus/isa/pnpparse.c

# Each affected case (unpatched) accesses offsets without len check
check "PNP_TAG_COMPAT_DEVICE bcopy(res,4) without len<4 guard" \
	'awk "/case PNP_TAG_COMPAT_DEVICE/,/break/" '"$F"' | grep -q "bcopy(res, &compat_id, 4)" && ! awk "/case PNP_TAG_COMPAT_DEVICE/,/break/" '"$F"' | grep -q "if (len < 4)"'

check "PNP_TAG_IRQ_FORMAT I16(res) without len<2 guard" \
	'awk "/case PNP_TAG_IRQ_FORMAT/,/case PNP_TAG_DMA_FORMAT/" '"$F"' | grep -q "I16(res) == 0" && ! awk "/case PNP_TAG_IRQ_FORMAT/,/case PNP_TAG_DMA_FORMAT/" '"$F"' | grep -q "if (len < 2)"'

check "PNP_TAG_DMA_FORMAT res[0] without len<1 guard" \
	'awk "/case PNP_TAG_DMA_FORMAT/,/case PNP_TAG_IO_RANGE/" '"$F"' | grep -q "res\[0\] == 0" && ! awk "/case PNP_TAG_DMA_FORMAT/,/case PNP_TAG_IO_RANGE/" '"$F"' | grep -q "if (len < 1)"'

check "PNP_TAG_IO_RANGE res[6]/res[5]=1 without len<7 guard" \
	'awk "/case PNP_TAG_IO_RANGE/,/case PNP_TAG_IO_FIXED/" '"$F"' | grep -qE "res\[6\]|res\[5\] = 1" && ! awk "/case PNP_TAG_IO_RANGE/,/case PNP_TAG_IO_FIXED/" '"$F"' | grep -q "if (len < 7)"'

check "PNP_TAG_IO_FIXED res[2]/I16(res) without len<3 guard" \
	'awk "/case PNP_TAG_IO_FIXED/,/case PNP_TAG_END:/" '"$F"' | grep -q "res\[2\] == 0" && ! awk "/case PNP_TAG_IO_FIXED/,/case PNP_TAG_END:/" '"$F"' | grep -q "if (len < 3)"'

check "PNP_TAG_MEMORY_RANGE I16(res+7) without len<9 guard" \
	'awk "/case PNP_TAG_MEMORY_RANGE/,/case PNP_TAG_MEMORY32_RANGE/" '"$F"' | grep -q "I16(res + 7)" && ! awk "/case PNP_TAG_MEMORY_RANGE/,/case PNP_TAG_MEMORY32_RANGE/" '"$F"' | grep -q "if (len < 9)"'

check "PNP_TAG_MEMORY32_RANGE I32(res+13) without len<17 guard" \
	'awk "/case PNP_TAG_MEMORY32_RANGE/,/case PNP_TAG_MEMORY32_FIXED/" '"$F"' | grep -q "I32(res + 13)" && ! awk "/case PNP_TAG_MEMORY32_RANGE/,/case PNP_TAG_MEMORY32_FIXED/" '"$F"' | grep -q "if (len < 17)"'

check "PNP_TAG_MEMORY32_FIXED I32(res+5) without len<9 guard" \
	'awk "/case PNP_TAG_MEMORY32_FIXED/,/default:/" '"$F"' | grep -q "I32(res + 5)" && ! awk "/case PNP_TAG_MEMORY32_FIXED/,/default:/" '"$F"' | grep -q "if (len < 9)"'

# PNP_TAG_ID_ANSI is the one that DOES clamp len correctly
check "PNP_TAG_ID_ANSI correctly clamps len before bcopy (control case)" \
	'awk "/case PNP_TAG_ID_ANSI/,/device_set_desc_copy/" '"$F"' | grep -q "len > sizeof(buf) - 1"'

# Function signature: len is signed int, no clamp on entry
check "pnp_parse_desc signature has no clamp" \
	'grep -q "pnp_parse_desc(device_t dev, u_char tag, u_char \*res, int len," '"$F"''

# pnpparse.c calls pnp_parse_desc with attacker-controlled len from TLV
check "pnpparse.c passes attacker-controlled TLV length to pnp_parse_desc" \
	'grep -q "pnp_parse_desc(dev, tag, p, l, config, ldn)" sys/bus/isa/pnpparse.c'

echo
echo "PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ]