Skip to content

[Bug] Stale hlvx flag after HLVX instruction bypasses pointer masking (PMM) for subsequent stores #1041

Description

@ZhongYic00

Describe the bug
After executing an HLVX instruction, the global hlvx flag at mmu.c:73 is not cleared for subsequent store instructions. This causes get_effective_address() at mmu.c:198 to bypass pointer masking (PMM / Zjpm) entirely for stores that follow an HLVX read.

The problem: rvh_hlvx_check() (which sets/clears hlvx) is called only from vaddr_read_internal() (the read path). It is never called from vaddr_write() (the write path), and nothing else resets hlvx between instructions. So after an HLVX executes, the next store still sees hlvx=true and returns the raw virtual address — bypassing all PMM logic.

Affected code locations:

  1. mmu.c:73-74hlvx and hld_st are global flags, not per-instruction state
  2. priv.c:2987-2991rvh_hlvx_check() sets hlvx from the current instruction's opcode; called only on the read path
  3. vaddr.c:160-162rvh_hlvx_check() is invoked inside vaddr_read_internal() (read only)
  4. vaddr.c:243vaddr_write() calls get_effective_address(addr, MEM_TYPE_WRITE) without resetting hlvx
  5. mmu.c:198if (type == MEM_TYPE_IFETCH || hlvx) return vaddr; — the early return that bypasses all downstream PMM/mode/MPRV logic

Trigger chain:

HLVX.WU a1, (a0)         # rvh_hlvx_check → hlvx = true
                         # get_effective_address(MEM_TYPE_READ) sees hlvx → returns vaddr (correct for HLVX)
sd    t1, 0(t0)          # vaddr_write → get_effective_address(MEM_TYPE_WRITE)
                         # hlvx is STILL true → returns vaddr directly — PMM BYPASSED
                         # (Should have applied PMM, e.g. stripped pointer metadata bits)

PoC design rationale:
Since PMM is meant to strip metadata from the upper masked bits of pointers, the PoC constructs a pointer with metadata in bits [63:48] that a correct PMM implementation would clear:

  • menvcfg.PMM = 3 → masked_width = 16 (unsigned path for BARE mode)
  • Store address: 0xFFFF000080000040 (metadata 0xFFFF in upper 16 bits, real address 0x80000040)
  • Correct PMM (unsigned path): (uint64_t)vaddr << 16 >> 160x00000000_80000040in pmem (MBASE=0x80000000, MSIZE=0x7ff80000000)
  • Stale hlvx bypass: effective address stays 0xFFFF000080000040NOT in pmem → store access fault (mcause=0x7)
    The metadata address 0xFFFF000080000040 is constructed purely via register arithmetic to avoid any intervening data load that would accidentally reset hlvx:
addi  t0, zero, -1      # t0 = 0xFFFFFFFFFFFFFFFF
slli  t0, t0, 48        # t0 = 0xFFFF000000000000
addi  t1, zero, 1       # t1 = 1
slli  t1, t1, 31        # t1 = 0x80000000
addi  t1, t1, 0x40      # t1 = 0x80000040
add   t0, t0, t1        # t0 = 0xFFFF000080000040   (5 insns, zero loads)

To Reproduce
Build the PoC:

source /xs-env/env.sh
cd /testcase/pmm-hlvx
make ARCH=riscv64-xs

Run with standalone NEMU (confirming the bug directly):

$NEMU_HOME/build/riscv64-nemu-interpreter -I 30000 -b \
  /testcase/pmm-hlvx/build/pmm-hlvx-riscv64-xs.bin

Run with XiangShan emu + NEMU .so difftest (shows store commit mismatch):

cd $NOOP_HOME
./build/emu -i /testcase/pmm-hlvx/build/pmm-hlvx-riscv64-xs.elf \
  --diff=$NOOP_HOME/ready-to-run/riscv64-nemu-interpreter-so -C 100000

pmm-hlvx.tar.gz

Expected behavior
The store after HLVX should apply PMM: 0xFFFF0000800000400x80000040, writing to pmem successfully.

Actual behavior

  • Standalone NEMU: _halt(1)trap_mcause = 0x7 (store access fault). PoC's self-check prints === BUG CONFIRMED ===.
  • XiangShan difftest: Store commit mismatch between DUT (XiangShan, PMM correctly applied → commits at 0x80000040) and REF (NEMU .so, hlvx stale → store faults, never commits).
    NEMU standalone output:
[*] PoC for Finding 11: PMM/hlvx stale flag bug
[*]
[*] After HLVX read, global 'hlvx' at mmu.c:73 remains true
[*] on subsequent store (vaddr_write → get_effective_address).
[*] mmu.c:198: if (hlvx) return vaddr; -- bypasses PMM entirely.
[*]
[*] Test: HLVX then store to 0xFFFF000080000040 w/ menvcfg.PMM=3
[*]   PMM applied:  0xFFFF000080000040 -> 0x80000040  (in pmem)
[*]   PMM bypassed: 0xFFFF000080000040 -> 0xFFFF000080000040 (not in pmem!) → access fault
[*]
[*] Entering HS-mode via mret ...
[*] === BUG CONFIRMED ===
[*] Store faulted: hlvx was stale, PMM bypassed,
[*] SD addr 0xFFFF000080000040 was NOT mapped to pmem.
[*] trap_mcause = 0x7 (expect 0x7 = store access fault)
[*] ---
[*] Root cause: mmu.c:198 'if (hlvx) return vaddr' skips PMM
[*] for MEM_TYPE_WRITE calls from vaddr_write (vaddr.c:243).
[*] hlvx is set by rvh_hlvx_check (priv.c:2989) on read path only.
nemu_trap case 1
HIT BAD TRAP at pc = 0x00000000800003ec

XiangShan difftest output (store commit mismatch):

NEMU does not commit any store instruction.
==============  Store Commit Event (Core 0)  ==============
Mismatch for store commits 
  REF commits addr 0x0000000080000040, data 0x00000000deadbeef, mask 0x00ff, pc 0x000000008000019a
  DUT commits addr 0x0000000080000040, data 0x00000000deadbeef, mask 0x00ff, pc 0x00000000800001be, robidx 0x94
Core 0: ABORT at pc = 0x80000ee0

Note: The REF (NEMU) commits at pc=0x8000019a (the store that sets test_result=1, which happened before the critical SD) but the critical SD at pc=0x800001be faulted in REF. DUT (XiangShan RTL) correctly applies PMM and commits both stores at their original PCs.

Necessary information on versions

  • NEMU: commit 8dba04c (standard riscv64-xs-ref_defconfig)
  • XiangShan RTL: commit 7be121c71f
  • PoC sources: /testcase/pmm-hlvx/src/main.c, /testcase/pmm-hlvx/src/entry.S

Additional context
The hlvx flag serves a legitimate purpose: HLVX instructions should bypass PMM (they operate on guest physical addresses). The bug is that the flag's lifetime extends beyond the HLVX instruction itself into subsequent stores, because no code path resets it on the write side. A fix should reset hlvx (and hld_st) at instruction boundaries, or have get_effective_address independently determine the HLVX status for each access type rather than relying on a persistent global.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions