PiyushPatle26 opened a new issue, #20582:
URL: https://github.com/apache/nuttx/issues/20582

   ### Description / Steps to reproduce the issue
   
   In RISC-V SMP kernel builds, unmapping user pages invalidates the TLB only 
on the hart performing the unmap. Other harts running threads of the same 
address environment can retain a valid translation to the unmapped physical 
page.
   
   I reproduced this on rv-virt:ksmp64 under QEMU. After shmdt() and 
shmctl(IPC_RMID), a reader thread on another hart continued accessing the 
unmapped shared-memory page. When the physical page was reallocated to a writer 
process, the stale reader observed the writer's data through the old mapping.
   
   In three first-run-after-boot tests, the reader observed the new writer's 
0xb0b0b0b0 marker in all 1,024 words of the page. The same-hart control faults 
as expected, and a proof of concept using SBI remote SFENCE.VMA makes the 
cross-hart reader fault as expected.
   
   This demonstrates cross-process data exposure through a stale translation in 
the tested configuration. The experiment has so far been conducted in QEMU, not 
on physical hardware.
   
   Root cause
   
   The shared-memory unmap path reaches a local-only TLB invalidation:
   
   ```
   shmdt()                          mm/shm/shmdt.c
     munmap_shm()                   mm/shm/shmat.c
       up_shmdt()                   arch/risc-v/src/common/riscv_addrenv_shm.c
         riscv_unmap_pages()        arch/risc-v/src/common/riscv_addrenv_utils.c
           mmu_ln_clear()           arch/risc-v/src/common/riscv_mmu.c
             sfence.vma x0, va      riscv_mmu.h:284, local hart only
   ```
   
   SFENCE.VMA invalidates translations on the executing hart; it does not 
invalidate the corresponding entries on other harts. The remote harts are not 
notified when the page table is changed.
   
   The SBI RFENCE function IDs are defined in riscv_sbi.h, but the current 
RISC-V code does not use them for this path.
   
   The same riscv_unmap_pages() function is also used by:
   - User pgmap unmapping (riscv_addrenv_pgmap.c).
   - Kernel kmap unmapping (up_addrenv_kunmap_pages()).
   
   These paths therefore warrant investigation for the same local-only 
invalidation problem.
   
   
   ### Steps to reproduce
   Environment
   - NuttX configuration: rv-virt:ksmp64
   - Build options: CONFIG_BUILD_KERNEL=y, CONFIG_SMP=y
   - QEMU: 8.2.2
   - Firmware: OpenSBI v1.3
   - Toolchain: riscv64-unknown-elf-gcc
   - NuttX revisions tested: 5fda288a2a and 284d4d4ea4 (NuttX-13.1.0)
   - Four RISC-V harts, running in S-mode
   
   #### Reproducer
   
   The test application is testing/mm/tlbstale, available on these branches:
   - NuttX: https://github.com/PiyushPatle26/nuttx/tree/riscv-smp-stale-tlb
   - NuttX apps: 
https://github.com/PiyushPatle26/nuttx-apps/tree/riscv-smp-stale-tlb
   The NuttX branch includes a change needed to link the test application in a 
kernel build; details are in the notes below.
   
   To build and run it (with `nuttx` and `apps` checked out side by side):
   
   ```bash
   cd nuttx
   ./tools/configure.sh -l rv-virt:ksmp64
   kconfig-tweak --file .config --enable CONFIG_MM_SHM \
     --set-val CONFIG_ARCH_SHM_VBASE 0xC2000000 \
     --set-val CONFIG_ARCH_SHM_NPAGES 16 \
     --set-val CONFIG_ARCH_SHM_MAXREGIONS 4 \
     --enable CONFIG_TESTING_TLBSTALE
   make olddefconfig
   make -j$(nproc) && make export -j$(nproc)
   cd ../apps
   ./tools/mkimport.sh -z -x ../nuttx/nuttx-export-*.tar.gz
   make import -j$(nproc)
   cd ../nuttx
   qemu-system-riscv64 -semihosting -M virt,aclint=on -cpu rv64 -smp 4 -kernel 
nuttx -nographic
   nsh> tlbstale -x     # as the first command after boot
   nsh> tlbstale -l     # control
   ```
   ##### How the reproducer works
   1. The main thread is pinned to CPU0. It creates a one-page shared-memory 
segment and fills it with 0x5a5aa5a5.
   2. A reader thread in the same process is pinned to CPU1 and continuously 
reads the page.
   3. With -x, three writer processes are created first and blocked on SIGUSR1.
   4. The main thread calls shmdt() and shmctl(IPC_RMID), unmapping the page 
and returning it to the page pool. It then publishes a flag with release 
semantics and signals the writers.
   5. Writer 0 performs shmget() and obtains the freed page. Its allocation 
zeroes the page, after which it fills the page with 0xb0b0b0b0.
   6. The reader observes the flag with acquire semantics and continues 
scanning the virtual address through its stale translation. On a correctly 
invalidated mapping, its first post-unmap access should fault instead.
   
   The -l option runs the reader on the same hart as the unmapper to provide a 
control case.
   
   ### Results
   
   | Case | Result |
   |---|---|
   | `tlbstale` (reader on CPU1), 3 runs | stale: ~39.9M successful loads after 
`shmdt()` returned, every run |
   | `tlbstale -c 2`, `-c 3` | stale: 39.99M / 39.17M loads |
   | **`tlbstale -x`, first run after a fresh boot, 3 of 3 boots (incl. the 
published branches)** | **leak: all 1024 words went `0x5a5aa5a5` → `0` → 
`0xb0b0b0b0`, i.e. the reader saw writer 0's `shmget()` zero the reused page 
and then its marker, ~53M stale loads** |
   | `tlbstale -x`, later runs in the same boot | stale, but the freed page 
wasn't reallocated within the window (see notes) |
   | `tlbstale -l` (control, same hart) | load page fault, SIGSEGV (correct) |
   | PoC: SBI `remote_sfence_vma` added to `riscv_unmap_pages()` | reader on 
CPU1 / CPU3 faults like the control (correct) |
   
   ```
   nsh> tlbstale -x
   tlbstale: reader on CPU1, main on CPU0 (cross-CPU unmap, cross-process reuse)
   tlbstale -w0: filled 16 shm pages and 256 heap blocks with 0xb0b0b0b0
   ...
   tlbstale: FAIL: the reader on CPU1 made 53014550 loads after shmdt() 
returned.
   tlbstale: scanned the page 51772 times: 1024 of 1024 words changed after the 
free, 1024 held the writer's marker 0xb0b0b0b0
   tlbstale:   load #27881 word 210 read 0x00000000 (unknown owner's data)
   ...
   tlbstale: last view of the page: 0 words our old data, 1024 the writer's 
marker, 0 zero, 0 other
   tlbstale: LEAK: the freed page was reallocated to a writer process, and this 
process read the writer's data
   
   nsh> tlbstale -l
   tlbstale: reader on CPU0, main on CPU0 (control: same CPU)
   [CPU0] riscv_exception: EXCEPTION: Load page fault. MCAUSE: 
000000000000000d, EPC: 00000000c000014e, MTVAL: 00000000c2000b90
   [CPU0] riscv_fault_handler: Segmentation fault in tlbstale (PID 29: tlbstale)
   ```
   
   #### Proof of concept
   
   The diagnostic PoC adds an SBI remote `SFENCE.VMA` call to 
`riscv_unmap_pages()`.
   
   It broadcasts to all harts unconditionally, does not probe for RFENCE 
support, has no IPI fallback, and does not support NuttSBI. It confirms the 
suspected cause in the tested OpenSBI configuration but is not proposed as a 
production fix.
   
   #### Proposed direction
   
   Following the discussion on Discord, I suggest addressing this in two stages.
   
   1. Fix RISC-V SMP correctness first
   Ensure that riscv_unmap_pages() does not return until the necessary remote 
invalidations have completed. Use SBI RFENCE when the firmware advertises the 
required extension and provide a synchronous IPI-based fallback for 
configurations without RFENCE, including NuttSBI. The fallback must be reviewed 
for deadlocks involving critical sections and locks.
   
   2. Discuss a generic TLB-maintenance contract separately
   A synchronous architecture-independent interface could allow common unmap 
paths to express the required completion semantics while architecture backends 
implement them appropriately. ARM/ARM64 could use their existing broadcast 
maintenance, while x86_64 would need to ensure remote invalidation has 
completed before returning. This interface would also provide a foundation for 
future RISC-V ASID support.
   
   I would welcome feedback on whether this separation is appropriate and on 
the best place to establish the generic interface.
   
   
   
   ### On which OS does this issue occur?
   
   [OS: Linux]
   
   ### What is the version of your OS?
   
   Ubuntu 24
   
   ### NuttX Version
   
   13.0.1
   
   ### Issue Architecture
   
   [Arch: risc-v]
   
   ### Issue Area
   
   [Area: Memory Management]
   
   ### Host information
   
   _No response_
   
   ### Verification
   
   - [x] I have verified before submitting the report.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to