Classroom Glossary Public page

Lab 8.1: PMP W^X Enforcement

609 words

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):

  1. Execute from code page (expect: permit). Branch to an address in the code segment; verify execution continues.
  2. Read from code page (expect: permit). Load a word from a code-segment address; verify the value is correct.
  3. Write to code page (expect: fault). Store a word to a code-segment address; verify mcause = 7 (store-access fault) fires.
  4. 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:

  1. Calls ECALL SYS_WRITE to display "attempting write to code..." on the OLED.
  2. Stores a word to a code-segment physical address.
  3. (Expected: this instruction is trapped; the program never executes step 4.)
  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:

  1. Writes valid-looking instruction bytes to a data page (e.g., li a0, 0x42; jalr ra).
  2. Attempts to branch to that data page.
  3. (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