Repository navigation
BIOS: panic on unknown E820 region type violates ACPI §15 (Table 15-374) #581
Description
Hi — reporting a panic hit on real hardware during a BIOS/CSM boot, with a proposed three-line fix. Happy to open a PR against v0.9-base if you agree with the approach.
Symptom
ASUS ROG AMD board, 32 GiB RAM, AMI BIOS 2.20.1271, booting legacy/CSM from a USB stick. The bootloader runs, reaches the memory map parsing, and stops:
panicked at src/bootinfo/memory_map.rs:225:18:
invalid region type 2954887168
The kernel never starts. Nothing indicates which region is at fault, nor how many there are.
What the specification requires
INT 15h AX=E820h is specified by the ACPI Specification, §15 — System Address Map Interfaces. Table 15-374 (Address Range Types) explicitly covers types the OS does not know:
Ranges marked "Reserved for future use" must be treated by OSPM as if the type returned was
AddressRangeReserved.
So the specification anticipates unknown types, and mandates reserving them rather than failing. The current behaviour is non-conformant: a bootloader refuses to start on firmware that is within spec.
This is not hypothetical — Linux dealt with the same issue ("e820: Undefined type not treated as AddressRangeReserved"), some firmware emitting type 13 where SeaBIOS emits 2.
What 0.11 already does
The next major version handles this well:
// bootloader-x86_64-bios-stage-4/src/memory_descriptor.rs
fn kind(&self) -> MemoryRegionKind {
match self.0.region_type {
1 => MemoryRegionKind::Usable,
other => MemoryRegionKind::UnknownBios(other),
}
}Neither panic nor silence: the region is not reusable, and the raw value is preserved. The proposal below carries that spirit into the constraints of 0.9.
Proposed fix
src/bootinfo/memory_map.rs, around line 219:
impl From<E820MemoryRegion> for MemoryRegion {
fn from(region: E820MemoryRegion) -> MemoryRegion {
let region_type = match region.region_type {
1 => MemoryRegionType::Usable,
2 => MemoryRegionType::Reserved,
3 => MemoryRegionType::AcpiReclaimable,
4 => MemoryRegionType::AcpiNvs,
5 => MemoryRegionType::BadMemory,
- t => panic!("invalid region type {}", t),
+ // ACPI Specification §15, Table 15-374 (Address Range Types): ranges marked
+ // "Reserved for future use" must be treated by OSPM as if the type returned
+ // was AddressRangeReserved. Treating them as fatal makes the bootloader
+ // refuse to start on firmware that is within spec.
+ //
+ // Not reusing the region is both the safe and the conformant behaviour;
+ // `Reserved` expresses exactly that.
+ _ => MemoryRegionType::Reserved,
};Why Reserved rather than a variant carrying the value, as 0.11 does with UnknownBios(u32): MemoryRegionType is #[repr(C)] and crosses the boundary between bootloader and kernel, which are compiled separately. Adding a payload-carrying variant would change its representation and break the ABI for every existing kernel. On a maintenance branch that seems out of the question.
If you would rather preserve the value anyway, two options exist — a payload-free variant plus a separate field in MemoryRegion, or a counter exposed through BootInfo — but both touch the ABI and are your call, not mine.
What this fix does not address
In our case the value received is 2954887168 = 0xB0200000. That is not a region type, however exotic: the specification defines only a handful. It is an address. So there is additionally something wrong with how the map is read upstream of this code — entry size, entry count, or buffer overrun — which I have not characterised yet.
The proposed change does not fix that. It keeps the bootloader usable on conformant hardware, and turns a fatal stop into a merely unused region, which is what the specification asks for. I will keep investigating the root cause and report back.
Reproduction
bootloader = "0.9.34", custom x86_64 target, image produced bycargo bootimage- written with
ddto a USB stick (SanDisk0781:5590), 3,091,968 bytes, checksum verified - ASUS ROG AMD board, AMI BIOS 2.20.1271, legacy/CSM boot
- the same binary runs fine under QEMU: the fault only shows on this real firmware, which is consistent with an E820 map that differs from SeaBIOS's
Activity
Freax13 commentedon Aug 21, 2026
There's an email thread with this title, but AFAICT no patch related to it was merged into the kernel.
This fix looks good to me.
This has me concerned. This isn't just some unknown type, it's likely a corrupted descriptor. Does your proposed fix actually make the bootloader work on your hardware? Is it just this one entry that's corrupted or is the entire memory map made up of corrupted descriptors? Ideally we would like to rule out that this is not a bug in the bootloader especially iff the entire memory map is corrupted.
Linux dumps the E820 memory map to the kernel logs. Could you see what memory map Linux uses? It would be interesting to see if they also report type
0xB0200000for one of the descriptors.batatrax commentedon Aug 21, 2026
2026-08-21_1.zip
Reply to #581 — measured on hardware, 2026-08-21
Thanks for the quick review — and for pushing back on the corrupted-descriptor
question. You were right to insist, but the answer turned out to be neither of the
two options we discussed. I instrumented the loader and ran it on the affected
machine.
The firmware is fine. The bootloader overflows its own stack into the memory map
it is parsing.
Your three questions
"Does your proposed fix actually make the bootloader work on your hardware?"
Yes, it boots. But it should not be merged on its own — see the last section.
"Is it just this one entry that's corrupted, or is the entire memory map made up of corrupted descriptors?"
Neither: the map is clean. I dumped all entries before conversion:
count = 35, every type in1..=4, zero out-of-spec types0x100000 + 0x9C1F000 = 0x9D1F000= next basefed40000,fedc2000,fedd4000,ff000000)and high RAM (
0x82f380000) — an unremarkable 32 GiB mapI also checksummed the entry bytes in real mode, immediately after
do_e820returns, and recomputed the same checksum in long mode just before conversion:
identical (
0x3c35). The buffer is not modified between collection and parsing.And
0xB0200000is absent from the entire 4 KiB buffer."Could you see what memory map Linux uses?"
Not needed to reach a conclusion, but I can still do it if you want it for the record.
What is actually happening
I filled the unused stack with
0xA5right afterdo_e820, then read thehigh-water mark after
create_fromcompletes:linker.ldplaces the stack at_stack_start = 0x6000…_stack_end = 0x7c00(7 168 B), directly above the E820 buffer at
_memory_map = 0x5000. The stackgrows downward, so it grows straight into the map it is about to parse.
Measured cost: ~40 B of stack per E820 entry. On the affected machine the stack
reaches
0x54cawhile the entries end at0x5348— 386 bytes of margin, i.e.roughly 9 more entries before the map itself gets overwritten.
This overflow also happens under QEMU. It is invisible there only because the
region it lands in happens to be empty.
Two contributing defects
MemoryMapis a 1 544-byte local ofcreate_from(64 ×MemoryRegion),i.e. 21 % of the entire stack, allocated on it.
add_region()callsself.sort()on every insertion — a full sort of 64elements repeated once per entry. Measured: with 35 entries the conversion alone
consumes 1 120 B more stack than with 7.
Why the value looked like an address
0xB0200000was never a region type — it is stack residue read back as one.Two runs on the same machine and same firmware, differing only in the size of
the compiled bootloader, produced
2954887168(0xB0200000) and196608(
0x30000). A firmware-supplied value would be stable; a value that changes withthe compiled code comes from the stack. That is what convinced me.
About the ACPI fix
It remains correct and still worth merging (ACPI §15, Table 15-374 — ranges marked
"Reserved for future use" must be treated as
AddressRangeReserved).But merging it alone would turn a hard panic into silence: the loader would boot
with a stack that has already overflowed into the memory map, and nothing would say
so. I would suggest pairing it with one or more of:
MemoryMapoff the stack (static / BSS);_memory_map.Unrelated, but in the same file
mmap_entis declared.word(2 bytes) ine820.sbut read asstatic mmap_ent: usize(8 bytes) inmain.rs. The read pulls in 6 bytes ofneighbouring data;
& 0xffmasks it today, but it is a read outside the symbol.This is observable: after I added a checksum variable next to
mmap_ent, I couldliterally see its value appear in the upper bytes of the "counter".
Raw output
Transcribed from the affected machine (the tool's own labels are in French; values
are verbatim). Photographs of each run are available if you want them.
Run 1 — stock 0.9.34, no patch. Reproduced identically twice, hours apart:
Run 2 — same machine, instrumented build (dump + real-mode checksum + stack
canary + ACPI fix).
count,sum,canaryandstackare the instrumentation:Run 3 — same instrumented build, but with the original
panic!restored(i.e. the ACPI fix removed, everything else identical):
Same machine, same firmware, same E820 map — a different bogus value
(
196608=0x30000instead of0xB0200000). Only the compiled code differed.Under QEMU/SeaBIOS, same instrumented build, for comparison: