From 3bfd7de34d02a810aafbee0e338f03bc5b8d57db Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 9 Jun 2026 09:08:03 +0200 Subject: [PATCH] F-5674: cap PCI IO BAR allocator at the 16-bit IO ceiling A device advertising a 64KB IO BAR pushed the IO allocator cursor (info->io) past 0x10000 because pci_program_bar used 0xffffffff as the IO BAR limit. The PCI-to-PCI bridge IO base/limit registers are 8-bit and only carry address bits [15:8], so io_start >> 8 silently narrows a 0x20000 cursor to 0x00, programming a bogus bridge IO window 0x0000-0x0FFF that forwards legacy IO (8259A PIC, 8254 PIT, MC146818 RTC) to the secondary bus. x86 IO space is 16-bit, so IO BARs must never be allocated above 0xFFFF. Cap the IO BAR allocator limit at PCI_IO32_LIMIT (0x10000): oversized IO BARs are now skipped at allocation time and the bridge IO window is programmed from the real cursor, never narrowing onto legacy IO. Add test_program_bridge_io_64k_no_narrow proving the bridge IO window is no longer mis-programmed. The post-enum IO OOM case is removed as the cap makes that wrap unreachable. --- src/pci.c | 10 +++++- tools/unit-tests/unit-pci.c | 65 +++++++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/pci.c b/src/pci.c index 83f5f83f..6482b7a2 100644 --- a/src/pci.c +++ b/src/pci.c @@ -70,6 +70,14 @@ #define PCI_IO32_BASE 0x2000 #endif /* PCI_IO32_BASE */ +#ifndef PCI_IO32_LIMIT +/* x86 IO space is 16-bit: valid IO addresses are 0x0000-0xFFFF. IO BARs must + * not be allocated past this ceiling, otherwise the 8-bit bridge IO base/limit + * registers (which only carry address bits [15:8]) silently truncate the high + * bits, mis-programming the window onto the legacy IO range (PIC/PIT/RTC). */ +#define PCI_IO32_LIMIT 0x10000 +#endif /* PCI_IO32_LIMIT */ + #define PCI_ENUM_MAX_DEV 32 #define PCI_ENUM_MAX_FUN 8 #define PCI_ENUM_MAX_BARS 6 @@ -474,7 +482,7 @@ static int pci_program_bar(uint8_t bus, uint8_t dev, uint8_t fun, if ((bar_align & PCI_DATA_HI16_MASK) == 0) bar_align |= PCI_DATA_HI16_MASK; base = &info->io; - limit = 0xffffffff; + limit = PCI_IO32_LIMIT; } PCI_DEBUG_PRINTF("PCI enum: %s %x:%x.%x bar: %d val: %x (%s %s)\r\n", diff --git a/tools/unit-tests/unit-pci.c b/tools/unit-tests/unit-pci.c index bf1196ae..1ff40212 100644 --- a/tools/unit-tests/unit-pci.c +++ b/tools/unit-tests/unit-pci.c @@ -1243,6 +1243,55 @@ START_TEST(test_program_bridge) } END_TEST +/* test_program_bridge_io_64k_no_narrow: a device advertising a 64KB IO BAR + * must not push the IO allocator past the 16-bit IO space (0xFFFF). The PCI + * bridge IO base/limit registers carry only address bits [15:8] in a uint8_t, + * so an io cursor >= 0x10000 truncates: io_start 0x20000 -> base reg 0x00, + * programming a bogus window 0x0000-0x0FFF that forwards legacy IO (8259A PIC, + * 8254 PIT, MC146818 RTC) to the secondary bus. With the allocator capped at + * the 16-bit IO ceiling the oversized BAR is skipped and the bridge window is + * programmed from the real cursor. */ +START_TEST(test_program_bridge_io_64k_no_narrow) +{ + struct test_pci_topology t; + struct pci_enum_info info; + int dev0, br, ep; + uint8_t iobase, iolimit; + + test_pci_init(&t); + /* dev 0 on bus 0: hostile/oversized 64KB IO BAR (decodes only 16 bits) */ + dev0 = test_pci_add_dev(&t, 0, 0, 0x1111, 0x2222, TEST_PCI_ROOT_BUS); + test_pci_dev_set_bar(&t, dev0, 0, 0x10000, TEST_PCI_BAR_IO); + t.nodes[dev0].bars[0].io_hi16_zero = 1; + /* dev 1 on bus 0: bridge with a small 256B IO device behind it */ + br = test_pci_add_bridge(&t, 1, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS); + ep = test_pci_add_dev(&t, 0, 0, 0xCCCC, 0xDDDD, br); + test_pci_dev_set_bar(&t, ep, 0, 256, TEST_PCI_BAR_IO); + test_pci_commit(&t); + + memset(&info, 0, sizeof(info)); + info.mem = 0x80000000; + info.mem_limit = 0x88000000; + info.mem_pf = 0x90000000; + info.mem_pf_limit = 0xFFFFFFFF; + info.io = 0x2000; + info.curr_bus_number = 0; + + pci_enum_bus(0, &info); + + /* The oversized IO BAR must be skipped, leaving the cursor in 16-bit IO + * space; the bridge IO window must reflect the real device (0x2000-0x2FFF) + * and never decode down to 0x0000 over the legacy IO range. */ + iobase = pci_config_read8(0, 1, 0, PCI_IO_BASE_OFF); + iolimit = pci_config_read8(0, 1, 0, PCI_IO_LIMIT_OFF); + ck_assert_uint_eq(iobase, 0x20); + ck_assert_uint_eq(iolimit, 0x2F); + ck_assert_uint_le(info.io, 0x10000); + + test_pci_cleanup(&t); +} +END_TEST + /* test_program_bridge_oom_initial: initial alignment failures */ START_TEST(test_program_bridge_oom_initial) { @@ -1335,13 +1384,11 @@ START_TEST(test_program_bridge_oom_post_enum) .mem_pf = 0x90000000, .mem_pf_limit = 0xFFFFFFFF, .io = 0x2000 } }, - { - "io: post-enum 4KB align wraps 32-bit space", - 256, TEST_PCI_BAR_IO, - { .mem = 0x80000000, .mem_limit = 0x88000000, - .mem_pf = 0x90000000, .mem_pf_limit = 0xFFFFFFFF, - .io = 0xFFFFF000 } - }, + /* No "io: post-enum align wraps" case: the IO allocator is capped at + * the 16-bit IO ceiling (PCI_IO32_LIMIT), so info.io can never reach + * the top of the 32-bit range and the post-enum IO alignment cannot + * wrap. Oversized IO BARs are now skipped at allocation time, covered + * by test_program_bridge_io_64k_no_narrow. */ }; int i; @@ -1751,6 +1798,10 @@ Suite *wolfboot_suite(void) tcase_add_test(tc_bridge, test_program_bridge); suite_add_tcase(s, tc_bridge); + TCase *tc_io_64k = tcase_create("bridge-io-64k-no-narrow"); + tcase_add_test(tc_io_64k, test_program_bridge_io_64k_no_narrow); + suite_add_tcase(s, tc_io_64k); + TCase *tc_oom_init = tcase_create("bridge-oom-initial"); tcase_add_test(tc_oom_init, test_program_bridge_oom_initial); suite_add_tcase(s, tc_oom_init);