⬢ DragonFlyBSD Kernel Audit
DF-5597 / ioapic_drift.c
← back to finding ↓ download raw
/*
 * DF-5597 drift-simulation PoC: the ioapic_irqinfo stride invariant.
 *
 * sys/platform/pc64/apic/ioapic_abi.h defines IOAPIC_IRQI_SZSHIFT = 4 as a
 * HAND constant with only a comment ("Update IOAPIC_IRQI_SZSHIFT after
 * changing ioapic_irqinfo size") enforcing that
 *     sizeof(struct ioapic_irqinfo) == 1 << IOAPIC_IRQI_SZSHIFT.
 * genassym.c:276-277 emits BOTH numbers into assym.s, but nothing at build
 * time relates them. Consumers:
 *   apic/ioapic_ipl.s:80,97  shll $IOAPIC_IRQI_SZSHIFT, %eax   (hand const)
 *   apic/apic_vector.s:55-59 IOAPIC_IRQI_SIZE * irq + ...       (sizeof)
 *
 * This program demonstrates, in userspace, that (1) today the invariant
 * holds (sizeof==16==1<<4), and (2) a one-field drift (the obvious future
 * edit: adding an owner cpumask for IRQ balancing) makes the two consumers
 * disagree: 1<<4 = 16 != sizeof = 24. ioapic_ipl.s would then compute
 * entry indices into ioapic_irqs[] with the wrong stride and read/write
 * io_addr/io_idx/io_flags of the WRONG entry (or past the array end for
 * high IRQ numbers) while apic_vector.s stays correct -- a silent,
 * build-clean divergence. Compile with -DDRFIFT to see the drifted size.
 */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

struct ioapic_irqinfo_current {
	uint32_t	io_flags;
	int		io_idx;
	volatile void	*io_addr;
};

struct ioapic_irqinfo_drifted {
	uint32_t	io_flags;
	int		io_idx;
	volatile void	*io_addr;
	uint32_t	io_owner_cpumask;	/* the plausible future field */
};

#define IOAPIC_IRQI_SZSHIFT	4	/* hand constant from ioapic_abi.h:58 */

int main(void)
{
#ifdef DRIFT
	size_t sz = sizeof(struct ioapic_irqinfo_drifted);
#else
	size_t sz = sizeof(struct ioapic_irqinfo_current);
#endif
	size_t stride = (size_t)1 << IOAPIC_IRQI_SZSHIFT;

	printf("sizeof(struct ioapic_irqinfo) = %zu\n", sz);
	printf("1 << IOAPIC_IRQI_SZSHIFT      = %zu\n", stride);
	if (sz == stride) {
		printf("INVARIANT HOLDS (matches live kernel: assym.s "
		       "IOAPIC_IRQI_SIZE 0x10 / IOAPIC_IRQI_SZSHIFT 0x4)\n");
		return 0;
	}
	printf("INVARIANT BROKEN: ioapic_ipl.s (shll $%zu) and apic_vector.s "
	       "(SIZE=%zu) index ioapic_irqs[] differently.\n"
	       "e.g. IRQ 100: ipl.s offset %zu vs vector.s offset %zu "
	       "(array is %s)\n",
	       stride, sz, 100 * stride, 100 * sz,
	       (100 * stride < 100 * sz) ? "under-walked" : "over-walked/OOB");
	return 1;
}