⬢ DragonFlyBSD Kernel Audit
DF-3737 / emx_bounds_harness.c
← back to finding ↓ download raw
/*
 * emx_bounds_harness.c - userspace bound-math replica of if_emx.c RX/TX math.
 *
 * Pass-2 twin of em_bounds_harness.c (DF-3713/DF-3714), line-for-line
 * against sys/dev/netif/emx/if_emx.c:
 *   [B] emx_rxeof (:3337-3468) descriptor-length trust:
 *         len = le16toh(rxd_length) at :3369 (16-bit NIC-DMA'd field,
 *         extended RX descriptors, E1000_RFCTL_EXTEN at :3134;
 *         E1000_RCTL_SECRC at :3283 strips the CRC in HW, so unlike
 *         em(4) there is NO -4 adjustment anywhere) ->
 *         mp->m_len = len at :3401, m_pkthdr.len = len / += len at
 *         :3404/:3413, with NO bound against the posted buffer.
 *         Posted buffer: m_getcl() MCLBYTES=2048 cluster (emx_newbuf
 *         :2908-2916), ETHER_ALIGN front-trim iff
 *         hw.mac.max_frame_size <= 2046 (:2918-2919) -> usable 2046/2048.
 *         desc_len = 0xFFFF -> m_len/m_pkthdr.len claim 65535 bytes in a
 *         2048-byte cluster -> heap OOB read of ~63.5KB into the network
 *         stack (ether_input/BPF/forwarding consumers).
 *   [C] emx_tx_collect (:2808-2868) unvalidated TDH walk math:
 *         tdh = E1000_READ_REG(TDH) at :2817 assigned to *int*;
 *         MMIO read-back of 0xFFFFFFFF (surprise removal / faulty device)
 *         -> tdh = -1; `while (first != tdh)` at :2832 never terminates
 *         (first wraps mod num_tx_desc at :2853-2854 and can never reach
 *         -1 or any value >= num_tx_desc);
 *         KKASSERT(num_avail < num_tx_desc) at :2835 panics INVARIANTS
 *         builds; production builds spin forever holding the TX
 *         serializer, freeing every TX buffer on the first pass
 *         (DMA-after-free window vs. the still-armed device).
 *
 * The [A] ETHER_ALIGN-vs-LPE 2-byte DMA overflow case is NOT replicated
 * here: that is the already-filed DF-1197 (emx) / DF-3712 (em) family and
 * must not be re-filed in this pass.
 *
 * This is *not* a driver exploit - it proves the length math the driver
 * performs, gated behind hardware the audit guest does not have.
 */
#include <stdio.h>
#include <stdlib.h>

/* --- constants mirrored from the kernel/driver --- */
#define MCLBYTES	2048		/* sys/kern/kern_mbuf.c / param.h */
#define ETHER_ALIGN	2		/* net/ethernet.h */
#define ETHERMTU	1500

static int fails;

static void check(const char *what, int bad)
{
	printf("  [%s] %s\n", bad ? "OVERFLOW" : "ok     ", what);
	if (bad)
		fails++;
}

/*
 * [B] emx_rxeof:3369-3413 length trust.  trim mirrors emx_newbuf:2918.
 */
static void case_B(int max_frame_size, unsigned desc_len, int eop)
{
	int trim = (max_frame_size <= MCLBYTES - ETHER_ALIGN) ? ETHER_ALIGN : 0;
	int usable = MCLBYTES - trim;		/* DMA-mapped span of cluster */
	int len = (int)desc_len;		/* emx_rxeof:3369 - used as-is */

	printf("[B] max_frame_size=%d (trim=%d, usable=%d), desc_len=%u "
	       "(%s)\n", max_frame_size, trim, usable, desc_len,
	       eop ? "EOP" : "non-EOP fragment");

	/* mp->m_len = len (:3401); fmp->m_pkthdr.len =/+= len (:3404/:3413) */
	if (len > usable) {
		printf("  [OVERFLOW] m_len=%d claims %d bytes past the "
		       "%d-byte posted cluster: kernel heap OOB read of up "
		       "to %d bytes per descriptor\n",
		       len, len - usable, usable, len - usable);
		fails++;
	} else if (len == 0) {
		printf("  [note]   len==0 also accepted (zero-length mbuf "
		       "into the chain)\n");
	} else {
		check("len within posted span", 0);
	}
}

/*
 * [C] emx_tx_collect:2817-2855 walk math: first advances mod num_tx_desc
 * until == tdh (raw *int*-narrowed TDH register read).  KKASSERT
 * (num_avail < num_tx_desc) at :2835 is INVARIANTS-only; on production
 * builds num_avail overflows int after 2^31 iterations.
 */
static void case_C(int tdh_raw, int ndesc)
{
	int first = 0, num_avail = 36, iters = 0, wraps = 0;
	int assert_would_fire = 0;
	long iter_limit = 1 << 22;

	printf("[C] num_tx_desc=%d, next_tx_to_clean(first)=%d, raw TDH "
	       "register=0x%x -> int tdh=%d\n", ndesc, first,
	       (unsigned)tdh_raw, tdh_raw);
	if (tdh_raw < 0 || tdh_raw >= ndesc) {
		while (first != tdh_raw && iters < iter_limit) {
			num_avail++;
			if (num_avail == ndesc)
				assert_would_fire = iters + 1;
			if (++first == ndesc) {
				first = 0;
				wraps++;
			}
			iters++;
		}
		printf("  TDH out of ring range: walked %d descriptors "
		       "(%d full ring wraps) without reaching TDH -> "
		       "unbounded loop under the TX serializer\n",
		       iters, wraps);
		check("KKASSERT(num_avail<num_tx_desc) fires on INVARIANTS "
		      "builds (:2835); production: serializer-held forever-"
		      "spin + every tx_buf freed on pass 1", 1);
	} else {
		printf("  in-range TDH: normal cleanup, no issue\n");
	}
}

int main(void)
{
	printf("== if_emx.c bound-math harness (guest has no emx hardware; "
	       "source-line replica; [A]=DF-1197 known, excluded) ==\n\n");

	printf("--- [B] emx_rxeof descriptor-length trust "
	       "(if_emx.c:3369-3413) ---\n");
	case_B(1518, 1500, 1);		/* honest std frame: safe */
	case_B(1518, 0xFFFF, 0);	/* faulty/malicious NIC fragment */
	case_B(1518, 0xFFFF, 1);	/* faulty/malicious NIC EOP */
	case_B(9234, 2048, 0);		/* honest jumbo fragment: safe */
	case_B(9234, 0xFFFF, 1);	/* jumbo config, malicious length */

	printf("\n--- [C] emx_tx_collect TDH walk (if_emx.c:2817-2855) ---\n");
	case_C(100, 512);		/* honest TDH */
	case_C(4096, 512);		/* out-of-range TDH (e.g. 0x1000) */
	case_C(-1, 512);		/* TDH register = 0xFFFFFFFF (removal) */

	printf("\n%d overflow/spin class(es) demonstrated in bound math\n",
	    fails);
	return fails ? 1 : 0;
}