โฌข DragonFlyBSD Kernel Audit
DF-3723 / chksum_bounds_demo.c
โ† back to finding โ†“ download raw
/* DF-3723 โ€” bound-math demonstration for ixgbe_checksum_ptr_x550()/ixgbe_calc_checksum_X550()
 *
 * The guest has no X550 NIC, so the HAL cannot be exercised on hardware. This
 * harness replicates the EXACT four guard checks from
 * sys/dev/netif/ix/ixgbe_x550.c (lines 3358, 3368, 3376, 3430 as of this
 * audit) and the checksum accumulation loop (3379-3395), with the NVM reads
 * replaced by a seeded in-memory image. It shows that a caller-provided
 * `buffer/buffer_size` pair that passes every check in the shipped code still
 * makes the loop read past the end of that buffer.
 *
 * Build:  cc -O2 -o chksum_bounds_demo chksum_bounds_demo.c
 * Run:    ./chksum_bounds_demo
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

typedef uint16_t u16;
typedef uint32_t u32;

#define WORD_SIZE        4096   /* hw->eeprom.word_size for a typical X550 NVM */
#define IXGBE_ERR_PARAM  (-12)

/* ---- replica of the shipped checks + loop (buffer != NULL path) ---- */
static int shipped_checksum_ptr(u16 ptr, u16 size, u16 *csum, u16 *buffer,
				u32 buffer_size, u32 *oob_first_index,
				long buffer_len_words)
{
	u16 length, start, i;
	u16 *local_buffer;
	long oob_reads = 0;

	/* ixgbe_x550.c:3358 */
	if (buffer_size < ptr)
		return IXGBE_ERR_PARAM;
	/* ixgbe_x550.c:3360 */
	local_buffer = &buffer[ptr];

	if (size) {
		start = 0;
		length = size;
	} else {
		start = 1;
		/* ixgbe_x550.c:3368 โ€” length word read at buffer[ptr] */
		length = local_buffer[0];
		/* ixgbe_x550.c:3371 โ€” skip if invalid (all pass here) */
		if (length == 0xFFFF || length == 0 ||
		    (ptr + length) >= WORD_SIZE)
			return 0;
	}

	/* ixgbe_x550.c:3376 โ€” THE DEFECT: bounds checked against
	 * start+length while reads occur at buffer[ptr + i] */
	if ((u32)start + (u32)length > buffer_size)
		return IXGBE_ERR_PARAM;

	/* ixgbe_x550.c:3379-3395 */
	for (i = start; length; i++, length--) {
		long idx = (long)ptr + i;           /* actual word touched */
		if (idx >= buffer_len_words) {
			if (oob_reads == 0)
				*oob_first_index = (u32)idx;
			oob_reads++;
		}
		*csum += local_buffer[i];
	}
	return oob_reads ? -100 : 0;   /* -100 == "OOB observed" */
}

/* ---- replica of the fixed checks ---- */
static int fixed_checksum_ptr(u16 ptr, u16 size, u16 *csum, u16 *buffer,
			      u32 buffer_size, u32 *oob_first_index,
			      long buffer_len_words)
{
	u16 length, start, i;
	u16 *local_buffer;
	long oob_reads = 0;

	/* fix #1: reject ptr == buffer_size (local_buffer[0] read at :3368) */
	if (buffer_size <= ptr)
		return IXGBE_ERR_PARAM;
	local_buffer = &buffer[ptr];

	if (size) {
		start = 0;
		length = size;
	} else {
		start = 1;
		length = local_buffer[0];
		if (length == 0xFFFF || length == 0 ||
		    (ptr + length) >= WORD_SIZE)
			return 0;
	}

	/* fix #2: account for the ptr base */
	if ((u32)ptr + (u32)start + (u32)length > buffer_size)
		return IXGBE_ERR_PARAM;

	for (i = start; length; i++, length--) {
		long idx = (long)ptr + i;
		if (idx >= buffer_len_words) {
			if (oob_reads == 0)
				*oob_first_index = (u32)idx;
			oob_reads++;
		}
		*csum += local_buffer[i];
	}
	return oob_reads ? -100 : 0;
}

int main(void)
{
	/* caller buffer of 100 words (200 bytes) โ€” e.g. a caller that read a
	 * 100-word NVM window and calls calc_checksum(hw, buf, 100) */
	long buflen = 100;
	static u16 image[WORD_SIZE];
	u16 csum;
	u32 oob = 0;
	int rc;

	/* seeded "NVM": pointer word at 0x02 (IXGBE_PCIE_ANALOG_PTR_X550)
	 * points at 90 (inside the 100-word buffer), and the word at offset
	 * 90 declares a section length of 50 words */
	image[2] = 90;
	image[90] = 50;

	printf("buffer_size=%ld words, ptr=%u, length-at-ptr=%u\n",
	       buflen, image[2], image[90]);

	csum = 0;
	rc = shipped_checksum_ptr(image[2], 0, &csum, image /* buffer==image
			       is NOT the scenario; use dedicated buf below */,
			       0, &oob, 0);
	(void)rc;

	/* the real scenario: caller buffer is its own 100-word array */
	{
		static u16 callerbuf[100];
		callerbuf[0] = image[90]; /* word at ptr=0? no โ€” ptr=90: */
		memset(callerbuf, 0xAB, sizeof(callerbuf));
		/* place the section-length word at callerbuf[90] like the
		 * NVM image would: the guard reads local_buffer[0] i.e.
		 * buffer[ptr] */
		callerbuf[90] = 50;

		oob = 0; csum = 0;
		rc = shipped_checksum_ptr(90, 0, &csum, callerbuf, 100,
					  &oob, 100);
		if (rc == -100)
			printf("SHIPPED: OOB READ โ€” first out-of-bounds word "
			       "index = %u (buffer holds 0..99); reads run to "
			       "buffer[90+50]=buffer[140]\n", oob);
		else if (rc == IXGBE_ERR_PARAM)
			printf("SHIPPED: rejected (ERR_PARAM) โ€” no OOB\n");
		else
			printf("SHIPPED: accepted, in-bounds only\n");

		oob = 0; csum = 0;
		rc = fixed_checksum_ptr(90, 0, &csum, callerbuf, 100,
					&oob, 100);
		if (rc == -100)
			printf("FIXED:   OOB READ (fix failed!) idx=%u\n", oob);
		else if (rc == IXGBE_ERR_PARAM)
			printf("FIXED:   rejected (ERR_PARAM) โ€” OOB prevented\n");
		else
			printf("FIXED:   accepted, in-bounds only\n");
	}

	/* off-by-one demo: buffer_size == ptr  (ixgbe_x550.c:3358/3368) */
	{
		static u16 small[90];
		memset(small, 0xCD, sizeof(small));
		oob = 0; csum = 0;
		rc = shipped_checksum_ptr(90, 0, &csum, small, 90, &oob, 90);
		printf("OFF-BY-ONE (buffer_size == ptr == 90): SHIPPED %s"
		       " (reads length at buffer[90] โ€” one word past end)\n",
		       rc == IXGBE_ERR_PARAM ? "rejects" :
		       "ACCEPTS and reads buffer[90]");
		rc = fixed_checksum_ptr(90, 0, &csum, small, 90, &oob, 90);
		printf("OFF-BY-ONE: FIXED %s\n",
		       rc == IXGBE_ERR_PARAM ? "rejects" : "accepts");
	}
	return 0;
}