[PATCH] lib: sbi_pmu: Do not return uninitialized stack data on FW read errors
Nickolai Zeldovich
nickolai at csail.mit.edu
Sun Oct 11 15:21:14 PDT 2026
sbi_ecall_pmu_handler() passes the uninitialized local temp to
sbi_pmu_ctr_fw_read() and copies it into out->value unconditionally.
sbi_pmu_ctr_fw_read() only writes *cval on success, so every error
return of SBI_EXT_PMU_COUNTER_FW_READ (e.g. a hardware or inactive
counter index) hands an M-mode stack word to the supervisor in a1.
SBI_EXT_PMU_COUNTER_FW_READ_HI does the same with the upper half of
temp on RV32.
Initialize temp to zero so that the error paths return zero in
sbiret.value; the success paths are unchanged.
Fixes: 1664d0efce0b ("lib: sbi_pmu: Replace sbi_pmu_ctr_read() with sbi_pmu_ctr_fw_read()")
Fixes: 51951d9e9af8 ("lib: sbi_pmu: Implement sbi_pmu_counter_fw_read_hi")
Signed-off-by: Nickolai Zeldovich <nickolai at csail.mit.edu>
Co-Authored-By: Claude Fable 5.1 <noreply at anthropic.com>
---
This bug was found in the process of formally verifying the correctness
of OpenSBI on top of the Sail RISC-V semantics using Lean.
Triggering execution: any S-mode caller,
a7 = 0x504D55 (PMU), a6 = 5 (COUNTER_FW_READ), a0 = 0 (mcycle).
sbi_pmu_ctr_fw_read() returns SBI_EINVAL at its first range check
without writing *cval, the handler copies the never-written temp into
out->value, and sbi_ecall_handler() returns it in a1 next to
a0 = SBI_ERR_INVALID_PARAM. temp has its address taken, so it lives
in a stack slot of the M-mode trap stack and a1 receives whatever the
last M-mode code left there. The same holds for a6 = 6 on RV32
(upper half); the RV64 arm of fid 6 already returns 0.
The same verification pass found that SBI_PMU_CFG_FLAG_SKIP_MATCH lets
a supervisor pair a firmware counter with a hardware event (sbi_panic()
via csr_write_num(0xb20)) or a hardware counter with a firmware event
(an out-of-bounds fw_counters_data[] store); that is fixed by the
pending "lib: sbi: reject PMU skip-match counter class mismatches"
from Pengpeng Hou, so it is not repeated here.
Testing:
- make PLATFORM=generic LLVM=1 PLATFORM_RISCV_XLEN=64: builds, no
warnings. PLATFORM_RISCV_XLEN=32 (rv32imac_zicsr_zifencei/ilp32):
sbi_ecall_pmu.c compiles without warnings; the 32-bit build as a
whole stops on an unrelated, pre-existing clang
-Wsometimes-uninitialized error in lib/sbi/sbi_trap_ldst.c that
origin/master has too.
- A userspace model of the handler (fid 5/6, sbi_pmu_ctr_fw_read()
stubbed with the real "write *cval only on success" behaviour) shows
the stale word in a1 on the error path before the patch and 0 after.
- Not run on QEMU (none available here). Suggested check: from an
S-mode test payload (or kvm-unit-tests' SBI PMU test), call
sbi_ecall(0x504D55, 5, 0, ...) right after a successful fw read of
a firmware counter configured with COUNTER_CFG_MATCH; before the
patch a1 holds the previous call's counter value (whatever is in
the stack slot), after it a1 = 0.
lib/sbi/sbi_ecall_pmu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/sbi/sbi_ecall_pmu.c b/lib/sbi/sbi_ecall_pmu.c
index 41bb1624..0762cce5 100644
--- a/lib/sbi/sbi_ecall_pmu.c
+++ b/lib/sbi/sbi_ecall_pmu.c
@@ -22,7 +22,7 @@ static int sbi_ecall_pmu_handler(unsigned long extid, unsigned long funcid,
struct sbi_ecall_return *out)
{
int ret = 0;
- uint64_t temp;
+ uint64_t temp = 0;
switch (funcid) {
case SBI_EXT_PMU_NUM_COUNTERS:
base-commit: 6ad246a1494807ee91e36ffcf60e4c30bb309d45
--
2.53.0
More information about the opensbi
mailing list