x86: Ensure boot_pgdir's user entries are unmapped
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 12 Jul 2016 18:30:39 +0000 (14:30 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 19 Jul 2016 15:43:10 +0000 (11:43 -0400)
commit7e9ef4d35c9aa707f274b019acaf06b682639f50
tree054e7fa2a4c62a955c4a23298f5aa7847c2ac6f8
parentd7abf316488c4540764f6682632b053b308654fc
x86: Ensure boot_pgdir's user entries are unmapped

For sanity reasons, we should never have anything in boot_pgdir below ULIM.
Technically, we could make it work, but not without some thought.  The
issues is that PML4 entries are pointers, pointing to common PML3s.  Any
entry in boot_pgdir is shared with every process, from the PML3 on down.
The kernel expects to manage and synchronize global access to the kernel
mappings (above ULIM).  But memory below ULIM is managed per-process.

Backstory: I was trying to debug a null function pointer by mapping
something at page 0.  The kernel panicked after decreffing a page too many
times.

What happened was that inserting one page at virtual addr 0 created a PML3,
PML2, and PML1 that was shared between every process - not just the page
mapped at 0.  This PTE reach was 512 GB, including the program binary
(which was in the page cache).

The first process was fine.  However, when we forked, the pages for e.g.
busybox's text segment already had PTEs in the new process's address space.
Technically, they were the same PTEs (and PML3, 2, and 1) as the parent
process, since they were shared data structures.

Anyway, map_page_at_addr() saw that the PTE had a mapping, so it decreffed
the page before inserting a new page.  It just so happened that the new
page was the same as the old one, since it was a fork (duplicate_vmrs,
etc).  That page had a single refcnt, since it was managed by the page
cache, causing it to be freed.

Now the page is freed, but it is in the page tables still, since
map_page_at_addr() wrote the PTE with the value of the page.  Basically, it
was a "replace the PTE with its own value", but it triggered a decref.

Next up was the VMR for busybox's MAP_PRIVATE vmr.  Since it's a private
mapping, it gets its own page.  upage_alloc() gives us the most recently
freed page, which was the one that was still in the address space, right at
the top of the text segment.

Eventually the process page faults, probably because of the madness of its
address space.  When it does, the kernel puts every page in the VMRs.  At
least one page (the one we discussed) was in there twice.  The second
decref triggers the kref assert, since we decreffed something that was
already 0.

Good times.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/arch/x86/pmap64.c
kern/src/env.c