Total points: 25
Estimated time: 3.5 hours
Prerequisites: Lab 7.1 complete; Sv32 MMU working; SignalTap configured for DE10-Nano
Overview
This lab integrates pmp.v from the reference implementation, configures a W^X policy for the user address space, and demonstrates that the Ch 12 §12.11 exploit -- write shellcode to the stack, branch to it -- is blocked before either step completes.
Part A: Integrate pmp.v (8 pts)
A1: Wire three PMP unit instances (5 pts)
The reference implementation uses three separate pmp_unit instances (for fetch, load, and store). Copy pmp.v from the support repo into your hdl/core/ and instantiate three copies in cpu.v:
pmp_unit #(.NUM_ENTRIES(8)) u_pmp_fetch (
.priv(priv),
.access_addr(fetch_paddr), // physical address from mmu.v fetch output
.access_active(fetch_req),
.perm_required(3'b100), // {X, W, R} = execute only
.pmpaddr_packed(pmpaddr_packed),
.pmpcfg_packed(pmpcfg_packed),
.access_ok(fetch_pmp_ok)
);
pmp_unit #(.NUM_ENTRIES(8)) u_pmp_load (
.priv(priv),
.access_addr(data_paddr),
.access_active(data_req & ~data_is_store),
.perm_required(3'b001), // read
...
.access_ok(load_pmp_ok)
);
pmp_unit #(.NUM_ENTRIES(8)) u_pmp_store (
.priv(priv),
.access_addr(data_paddr),
.access_active(data_req & data_is_store),
.perm_required(3'b010), // write
...
.access_ok(store_pmp_ok)
);
When access_ok is low (PMP blocks the access), generate the appropriate fault: instruction-access fault (mcause=1) for fetch, load-access fault (mcause=5) for load, store-access fault (mcause=7) for store.
A2: CSR wiring for pmpcfg/pmpaddr (3 pts)
Add pmpcfg0, pmpcfg1 (CSR 0x3A0, 0x3A1) and pmpaddr0-pmpaddr7 (CSR 0x3B0-0x3B7) to your csrfile. Wire the packed outputs (pmpaddr_packed, pmpcfg_packed) to all three PMP unit instances.
Verify: csrw pmpaddr0, t0 and csrr t1, pmpaddr0 round-trips correctly in a Verilator unit test.
Part B: Configure W^X policy (8 pts)
B1: Write the PMP setup routine (4 pts)
In your kernel boot code, configure PMP entries 0 and 1:
Entry 0 (code segment -- execute + read, not write):
li t0, (CODE_END_PHYS >> 2) # top of code region
csrw pmpaddr0, t0
li t0, (1 << 3) | (1 << 2) | (1 << 0) # A=TOR(01<<3), X=1, W=0, R=1
csrw pmpcfg0, t0 # cfg0 in byte 0 of pmpcfg0
Entry 1 (data segment -- read + write, not execute):
li t1, (DATA_END_PHYS >> 2) # top of data region
csrw pmpaddr1, t1
li t1, (1 << 11) | (1 << 9) | (1 << 8) # A=TOR(01<<3) in byte 1, W=1, R=1
# cfg1 is in bits 15:8 of pmpcfg0
csrrs t2, pmpcfg0, t1
B2: Verify W^X in Verilator (4 pts)
Write four test cases (in assembly or C):
- Execute from code page (expect: permit). Branch to an address in the code segment; verify execution continues.
- Read from code page (expect: permit). Load a word from a code-segment address; verify the value is correct.
- Write to code page (expect: fault). Store a word to a code-segment address; verify mcause = 7 (store-access fault) fires.
- Execute from data page (expect: fault). Branch to a data-segment address; verify mcause = 1 (instruction-access fault) fires.
All four tests must pass before proceeding to Part C.
Part C: Demonstrate on DE10-Nano with SignalTap (9 pts)
C1: Synthesize with SignalTap (3 pts)
Add a SignalTap Logic Analyzer tap in Quartus:
- Signal:
store_pmp_ok(output of u_pmp_store) - Signal:
fetch_pmp_ok(output of u_pmp_fetch) - Signal:
data_paddr[31:0] - Signal:
fetch_paddr[31:0] - Trigger condition:
store_pmp_ok == 0(fire when a store is blocked)
Synthesize and flash the updated bitstream to DE10-Nano.
C2: Demonstrate blocked write to code segment (3 pts)
Write a user-mode program that:
- Calls ECALL SYS_WRITE to display "attempting write to code..." on the OLED.
- Stores a word to a code-segment physical address.
- (Expected: this instruction is trapped; the program never executes step 4.)
- Calls ECALL SYS_WRITE to display "write succeeded" (should NOT appear).
Run the program on DE10-Nano with SignalTap armed. Capture the waveform showing store_pmp_ok transitioning to 0 at the moment of the store. Submit the SignalTap capture screenshot.
Verify on the OLED: "attempting write to code..." appears; "write succeeded" does NOT appear; the OS kills the process and displays a "PMP fault" message.
C3: Demonstrate blocked execute from data segment (3 pts)
Write a second user-mode program that:
- Writes valid-looking instruction bytes to a data page (e.g.,
li a0, 0x42; jalr ra). - Attempts to branch to that data page.
- (Expected: the fetch is blocked; mcause = 1.)
Run with SignalTap armed; capture fetch_pmp_ok == 0 at the faulting fetch address. Submit the capture.
Grading
| Part | Criteria | Points |
|---|---|---|
| A1 | Three PMP units instantiated; fault causes wired correctly | 5 |
| A2 | pmpcfg/pmpaddr CSRs readable and writable; packed outputs wired | 3 |
| B1 | PMP setup routine configures entries 0 and 1 correctly | 4 |
| B2 | All four Verilator test cases pass | 4 |
| C1 | SignalTap project synthesizes; taps on pmp_ok signals | 3 |
| C2 | Blocked write demonstrated on silicon; OLED confirms; SignalTap capture | 3 |
| C3 | Blocked fetch demonstrated on silicon; SignalTap capture | 3 |
| Total | 25 |