[SECURITY] lib: sbi_domain: integer overflow in sbi_domain_check_addr_range() bypasses domain isolation — M-mode arbitrary read/write/execute from S-mode
刘通
liutong at iscas.ac.cn
Sat Apr 18 01:04:33 PDT 2026
Hi,
I am reporting a critical security vulnerability in OpenSBI.
An integer overflow in sbi_domain_check_addr_range() allows
S-mode software to completely bypass domain permission checks,
directly enabling arbitrary read and arbitrary write of M-mode firmware
memory. These primitives can be further chained to achieve M-mode code
execution.
Under RISC-V H-extension virtualization, a VS-mode guest can exploit
the same flaw through the hypervisor SBI call forwarding path — a full
virtual machine escape.
All impacts have been verified on QEMU and Spike with working exploits.
This vulnerability is not mitigated by PMP, SmePMP, or the domain
system itself.
## Affected Code
File: lib/sbi/sbi_domain.c, lines 497-526
```c
bool sbi_domain_check_addr_range(const struct sbi_domain *dom,
unsigned long addr, unsigned long size,
unsigned long mode,
unsigned long access_flags)
{
unsigned long max = addr + size; // [BUG] no overflow check
...
while (addr < max) { // skipped when max wraps
... // ALL permission checks here
}
return true; // fail-open: unchecked = allowed
}
```
## Root Cause
`max = addr + size` is an unprotected unsigned addition. On RV64, when
addr + size >= 2^64, the result wraps to a value less than addr. The
while loop condition `addr < max` is immediately false, so the loop
body — which contains all region permission checks — executes zero
times. The function then falls through to `return true`.
This is a fail-open design: no checks performed = access granted.
There are two trigger conditions:
Condition A — Integer overflow (addr + size wraps to 0):
addr = 0x80000000, size = 0xFFFFFFFF80000000
max = 0 → while (0x80000000 < 0x0) = false → return true
Condition B — Zero-size bypass (size = 0):
addr = 0x80000000, size = 0
max = addr → while (addr < addr) = false → return true
Condition B is side-effect-free and suitable for silent probing.
## Affected SBI Extensions
The following ecall handlers pass S-mode-controlled register values
directly to the vulnerable function:
Extension File S-mode controlled params
───────────────── ────────────────────── ────────────────────────
DBCN CONSOLE_WRITE sbi_ecall_dbcn.c:45 addr=a1, size=a0
DBCN CONSOLE_READ sbi_ecall_dbcn.c:45 addr=a1, size=a0
MPXY set_shmem sbi_mpxy.c:369 addr, size
PMU set_shmem sbi_pmu.c:1070 addr, size
SSE read/write sbi_sse.c:1001 addr, size
SSE register sbi_sse.c:1120 addr, size
DBCN is the most direct attack vector. After the domain check is
bypassed, M-mode operates on the attacker-supplied address with no
further validation:
CONSOLE_WRITE → sbi_nputs(addr, size) → M-mode reads from addr
CONSOLE_READ → sbi_ngets(addr, size) → M-mode writes to addr
## Impact 1: Arbitrary Read of M-mode Memory
Using DBCN CONSOLE_WRITE with Condition A:
ecall(ext=DBCN, fid=CONSOLE_WRITE, a0=-addr, a1=addr, a2=0)
The domain check sees addr + size = 0 (overflow), skips validation,
returns true. M-mode then calls sbi_nputs(addr, size), which reads
from the firmware memory address in a loop and outputs each byte to
the console. The attacker captures the output.
This directly leaks:
- Firmware code (.text) and constants (.rodata)
- All M-mode data: ecall handler tables, domain region configs,
PMP state, platform structures, sbi_scratch per-hart data
- Cryptographic keys or secrets stored in M-mode memory
- The full firmware binary (verified: 64/64 byte-for-byte match)
No shellcode or write primitive is needed. The read works on the
first attempt with 100% reliability.
## Impact 2: Arbitrary Write to M-mode Memory
Using DBCN CONSOLE_READ with Condition A:
ecall(ext=DBCN, fid=CONSOLE_READ, a0=-addr, a1=addr, a2=0)
The domain check is bypassed identically. M-mode then calls
sbi_ngets(addr, size), which reads bytes from the UART receive
buffer and writes them to the firmware memory address. The attacker
controls the UART input (via stdin pipe on emulators, or via
physical serial on real hardware).
sbi_ngets() uses non-blocking sbi_getc(): it writes exactly as many
bytes as are available in the UART buffer, then returns. This gives
the attacker precise control over write length and content.
Verified: ecall_impid at 0x80040cf0 overwritten from 0x1 to
0x4242424241414141 (8 bytes, confirmed via SBI BASE GET_IMPL_ID).
The firmware .data section (0x80040000-0x8005ffff) contains 18 ecall
extension structures (each with a .handle function pointer), the
domain_list security policy, sbi_hart_expected_trap, platform ops,
and 31 other function pointer targets. All are writable.
## Impact 3: M-mode Code Execution
The write primitive enables a standard three-step code execution
chain:
Step 1: Write 16-byte shellcode to 0x80060000 (RAM beyond the
firmware region, accessible to M-mode)
Step 2: Overwrite ecall_rfence.handle (at 0x80040e18) with
0x80060000
Step 3: ecall(ext=SBI_EXT_RFENCE) → OpenSBI dispatches to the
hijacked .handle → shellcode executes in M-mode
The shellcode reads misa CSR (M-mode only; S-mode access causes an
illegal instruction trap). Verified return: misa = 0x800000000014112d.
## Impact 4: VM Escape via H-extension
Under H-extension virtualization, VS-mode ecalls (cause 10) are
delegated to HS-mode via MEDELEG. The hypervisor (e.g., Linux KVM)
forwards guest SBI calls to M-mode by re-issuing ecall from HS-mode
with the guest's original register values.
OpenSBI cannot distinguish this forwarded ecall from a direct HS-mode
call: it reads MSTATUS.MPP (= PRV_S for both) but never checks
MSTATUS.MPV. No GPA-to-HPA translation is performed on the address
parameters. The domain check uses the HS-mode hart's domain, not the
guest's permissions.
Verified on Spike (--isa=rv64imafdc_h) with a minimal HS-mode
hypervisor: VS-mode guest achieves M-mode arbitrary read, write, and
code execution. misa = 0x80000000001411ad (RV64 ACDFHIMSU, H-bit
confirms the hypervisor extension context).
## Verified Test Results
All tests on OpenSBI v1.8.1 (commit 2257e995), generic platform.
Test Environment Result
────────────────────── ─────────────── ─────────────────────────
Domain bypass (×6) QEMU + Spike 3/3 overflow bypass,
3/3 baseline blocked
PMP direct access Spike H-ext ACCESS FAULT (PMP works)
Firmware read (leak) QEMU + Spike 64/64 bytes match fw_jump.bin
Firmware write QEMU + Spike ecall_impid: 0x1 → 0x4242424241414141
M-mode code execution QEMU + Spike misa = 0x800000000014112d
PMP disable shellcode QEMU csrw pmpcfg0/2, zero — succeeded
VM escape (H-ext RCE) Spike rv64gc_h misa = 0x80000000001411ad
## Additional Overflow Sites
The same overflow pattern exists in two other functions in the same
file. These are only called during boot from trusted parameters
(FDT/hardware), so they are not currently exploitable from S-mode,
but should be fixed for defense in depth:
- sbi_domain_root_add_memrange() line 766:
end = addr + size;
while (pos < end) { ... }
- sbi_domain_memregion_init() line 107:
(addr + size - 1UL) underflows when size = 0
## Suggested Fix
```c
bool sbi_domain_check_addr_range(const struct sbi_domain *dom,
unsigned long addr, unsigned long size,
unsigned long mode,
unsigned long access_flags)
{
unsigned long max;
const struct sbi_domain_memregion *reg, *sreg;
if (!dom)
return false;
/* Reject zero-size ranges */
if (!size)
return false;
/* Detect unsigned overflow */
if (addr + size < addr)
return false;
max = addr + size;
while (addr < max) {
/* ... existing logic unchanged ... */
}
return true;
}
```
The two added checks are O(1) with no impact on normal operation:
1. size == 0 → return false
Zero-size address ranges have no legitimate security use.
2. addr + size < addr → return false
Standard unsigned overflow detection: a wrapped sum is always
less than either operand.
## Disclosure Note
OpenSBI does not appear to have a documented security disclosure
policy (no SECURITY.md or security advisory process). I am sending
this to the public mailing list accordingly. If a private channel
exists, I am happy to coordinate disclosure there instead.
Regards
Signed-off-by: liutong at iscas.ac.cn
More information about the opensbi
mailing list