linuxkernelriscvxv6

raise Store/AMO page fault when trying write stack in xv6's trampoline


I'm trying to modify xv6 to make it a multi-thread system.One of the modification I made is that I need do some computing oprations in trampoline because I need every thread to find its unique TRAMPFRAME virtual address.Specifically, I need the stack to store regs temporarily in trampoline:uservec

uservec:    
    #
        # trap.c sets stvec to point here, so
        # traps from user space start here,
        # in supervisor mode, but with a
        # user page table.
        #

        # store intermediate registers on the stack
        addi sp, sp, -32
        sd a0, 0(sp)
        sd a1, 8(sp)
        sd a2, 16(sp)

The problem is that every time I try to return from user space (we still use the user page table now), this code will execute. But every time I write to the stack, I get an error and continue executing this code. It's an infinite loop.

So I checked the permissions of the address pointed to by sp. In fact, the address has been mapped in the user page table.

first process, e.g.

  uvmfirst(p->mm.pagetable, initcode, sizeof(initcode));
  p->sz = PGSIZE; 

  // prepare for the very first "return" from kernel to user.

  t->trapframe->epc = 0;      // user program counter
  t->trapframe->sp = PGSIZE;  // user stack pointer

and the uvmfrist()

void
uvmfirst(pagetable_t pagetable, uchar *src, uint sz)
{
  char *mem;

  if(sz >= PGSIZE)
    panic("uvmfirst: more than a page");
  mem = kalloc();
  memset(mem, 0, PGSIZE);
  mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W|PTE_R|PTE_X|PTE_U);
  memmove(mem, src, sz);
}

So, why I have set the correct permissions for that address and still can't write?I do the same for the original version of xv6, and it doesn't work right now!

xv6:

.align 4
.globl uservec
uservec:    
    #
        # trap.c sets stvec to point here, so
        # traps from user space start here,
        # in supervisor mode, but with a
        # user page table.
        #
        
       # for debugging
        addi sp, sp, -32
        sd a0, 0(sp)
        sd a1, 8(sp)
        sd a2, 16(sp)
        addi sp, sp, 32

Solution

  • I think you might be hitting Supervisor mode Access Prevention. U-mode cannot access non-usermode pages, for obvious reason. However M/S-mode also cannot access user-mode pages by default in order to prevent some attack vectors. In a trap code you are running S-mode code but your stack is allocated in user-mode pages like you said. Therefore, your code may not access it. See sstatus.SUM flag for more information about SMAP.

    I must also point out this whole approach may be invalid. By coopting user mode stack for your own purposes you are essentially introducing an ABI requirement all userspace has to follow. Specifically, 24 bytes at sp-56..sp-32 may be trashed at any time (because of paging faults and interrupts) and thus cannot be used. Essentially, you are setting up 32 byte red zone. If xv6's usermode toolchain is configured for, let's say, 128 byte red zone, then your change would break things.

    One more thing to point out with this approach: it is inherently unsafe. The fact you are getting infinite loop now is a pointer to that. There might not be a valid writable page at sp-56 (due to paging or stack overflow or bad sp), and that would break the kernel. It would allow for a trivial local DoS:

    mv sp, x0
    st 0(sp), x0
    

    Worse, it would allow for an arbitrary 24-byte write to the kernel memory. Adversary would just need to setup a0, a1, a2 to data of their choice, set sp to whichever kernel memory location they need, then trigger trap via syscall and wham - kernel memory overwritten.

    There is a sscratch CSR; most trap code uses it for a reason (including risc-v port of xv6...), and that reason is "you can't trust usermode". All registers must be assumed tainted; you need some sane starting point. sscratch allows you to free one register for your own data, and then go from there.