mirror of
https://github.com/ish-app/ish.git
synced 2026-01-25 14:06:40 +00:00
This was causing the random node segfaults. Node does manual ASLR, so it would randomly pick an address for a memory mapping near the brk region, then later something would raise the brk and clobber that mapping, replacing it with zeroes. Shortly afterward something would crash on a null pointer. Took a lot of digging around in rr to find this. The crash is never directly connected to the brk call, of course. I set a watchpoint on the pointer that got clobbered and saw it had been zero since the page was mapped, but it took setting a watchpoint on the pointer to that pointer to find that when it was mapped before that and was valid at that point. At this point I'm ready to say node is fixed! #90
245 lines
7.4 KiB
C
245 lines
7.4 KiB
C
#include <string.h>
|
|
#include "debug.h"
|
|
#include "kernel/calls.h"
|
|
#include "kernel/errno.h"
|
|
#include "kernel/task.h"
|
|
#include "fs/fd.h"
|
|
#include "emu/memory.h"
|
|
#include "kernel/mm.h"
|
|
|
|
struct mm *mm_new() {
|
|
struct mm *mm = malloc(sizeof(struct mm));
|
|
if (mm == NULL)
|
|
return NULL;
|
|
mem_init(&mm->mem);
|
|
mm->start_brk = mm->brk = 0; // should get overwritten by exec
|
|
mm->exefile = NULL;
|
|
mm->refcount = 1;
|
|
return mm;
|
|
}
|
|
|
|
struct mm *mm_copy(struct mm *mm) {
|
|
struct mm *new_mm = malloc(sizeof(struct mm));
|
|
if (new_mm == NULL)
|
|
return NULL;
|
|
*new_mm = *mm;
|
|
// Fix wrlock_init failing because it thinks it's reinitializing the same lock
|
|
memset(&new_mm->mem.lock, 0, sizeof(new_mm->mem.lock));
|
|
new_mm->refcount = 1;
|
|
mem_init(&new_mm->mem);
|
|
fd_retain(new_mm->exefile);
|
|
write_wrlock(&mm->mem.lock);
|
|
pt_copy_on_write(&mm->mem, &new_mm->mem, 0, MEM_PAGES);
|
|
write_wrunlock(&mm->mem.lock);
|
|
return new_mm;
|
|
}
|
|
|
|
void mm_retain(struct mm *mm) {
|
|
mm->refcount++;
|
|
}
|
|
|
|
void mm_release(struct mm *mm) {
|
|
if (--mm->refcount == 0) {
|
|
if (mm->exefile != NULL)
|
|
fd_close(mm->exefile);
|
|
mem_destroy(&mm->mem);
|
|
free(mm);
|
|
}
|
|
}
|
|
|
|
static addr_t do_mmap(addr_t addr, dword_t len, dword_t prot, dword_t flags, fd_t fd_no, dword_t offset) {
|
|
int err;
|
|
pages_t pages = PAGE_ROUND_UP(len);
|
|
if (!pages) return _EINVAL;
|
|
page_t page;
|
|
if (addr != 0) {
|
|
if (PGOFFSET(addr) != 0)
|
|
return _EINVAL;
|
|
page = PAGE(addr);
|
|
if (!(flags & MMAP_FIXED) && !pt_is_hole(current->mem, page, pages)) {
|
|
addr = 0;
|
|
}
|
|
}
|
|
if (addr == 0) {
|
|
page = pt_find_hole(current->mem, pages);
|
|
if (page == BAD_PAGE)
|
|
return _ENOMEM;
|
|
}
|
|
|
|
if (flags & MMAP_SHARED)
|
|
prot |= P_SHARED;
|
|
|
|
if (flags & MMAP_ANONYMOUS) {
|
|
if ((err = pt_map_nothing(current->mem, page, pages, prot)) < 0)
|
|
return err;
|
|
} else {
|
|
// fd must be valid
|
|
struct fd *fd = f_get(fd_no);
|
|
if (fd == NULL)
|
|
return _EBADF;
|
|
if (fd->ops->mmap == NULL)
|
|
return _ENODEV;
|
|
if ((err = fd->ops->mmap(fd, current->mem, page, pages, offset, prot, flags)) < 0)
|
|
return err;
|
|
mem_pt(current->mem, page)->data->fd = fd_retain(fd);
|
|
mem_pt(current->mem, page)->data->file_offset = offset;
|
|
}
|
|
return page << PAGE_BITS;
|
|
}
|
|
|
|
static addr_t mmap_common(addr_t addr, dword_t len, dword_t prot, dword_t flags, fd_t fd_no, dword_t offset) {
|
|
STRACE("mmap(0x%x, 0x%x, 0x%x, 0x%x, %d, %d)", addr, len, prot, flags, fd_no, offset);
|
|
if (len == 0)
|
|
return _EINVAL;
|
|
if (prot & ~P_RWX)
|
|
return _EINVAL;
|
|
if ((flags & MMAP_PRIVATE) && (flags & MMAP_SHARED))
|
|
return _EINVAL;
|
|
|
|
write_wrlock(¤t->mem->lock);
|
|
addr_t res = do_mmap(addr, len, prot, flags, fd_no, offset);
|
|
write_wrunlock(¤t->mem->lock);
|
|
return res;
|
|
}
|
|
|
|
addr_t sys_mmap2(addr_t addr, dword_t len, dword_t prot, dword_t flags, fd_t fd_no, dword_t offset) {
|
|
return mmap_common(addr, len, prot, flags, fd_no, offset << PAGE_BITS);
|
|
}
|
|
|
|
struct mmap_arg_struct {
|
|
dword_t addr, len, prot, flags, fd, offset;
|
|
};
|
|
|
|
addr_t sys_mmap(addr_t args_addr) {
|
|
struct mmap_arg_struct args;
|
|
if (user_get(args_addr, args))
|
|
return _EFAULT;
|
|
return mmap_common(args.addr, args.len, args.prot, args.flags, args.fd, args.offset);
|
|
}
|
|
|
|
int_t sys_munmap(addr_t addr, uint_t len) {
|
|
STRACE("munmap(0x%x, 0x%x)", addr, len);
|
|
if (PGOFFSET(addr) != 0)
|
|
return _EINVAL;
|
|
if (len == 0)
|
|
return _EINVAL;
|
|
write_wrlock(¤t->mem->lock);
|
|
int err = pt_unmap_always(current->mem, PAGE(addr), PAGE_ROUND_UP(len));
|
|
write_wrunlock(¤t->mem->lock);
|
|
if (err < 0)
|
|
return _EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
#define MREMAP_MAYMOVE_ 1
|
|
#define MREMAP_FIXED_ 2
|
|
|
|
int_t sys_mremap(addr_t addr, dword_t old_len, dword_t new_len, dword_t flags) {
|
|
STRACE("mremap(%#x, %#x, %#x, %d)", addr, old_len, new_len, flags);
|
|
if (PGOFFSET(addr) != 0)
|
|
return _EINVAL;
|
|
if (flags & ~(MREMAP_MAYMOVE_ | MREMAP_FIXED_))
|
|
return _EINVAL;
|
|
if (flags & MREMAP_FIXED_) {
|
|
FIXME("missing MREMAP_FIXED");
|
|
return _EINVAL;
|
|
}
|
|
pages_t old_pages = PAGE(old_len);
|
|
pages_t new_pages = PAGE(new_len);
|
|
|
|
// shrinking always works
|
|
if (new_pages <= old_pages) {
|
|
int err = pt_unmap(current->mem, PAGE(addr) + new_pages, old_pages - new_pages);
|
|
if (err < 0)
|
|
return _EFAULT;
|
|
return addr;
|
|
}
|
|
|
|
struct pt_entry *entry = mem_pt(current->mem, PAGE(addr));
|
|
if (entry == NULL)
|
|
return _EFAULT;
|
|
dword_t pt_flags = entry->flags;
|
|
for (page_t page = PAGE(addr); page < PAGE(addr) + old_pages; page++) {
|
|
entry = mem_pt(current->mem, page);
|
|
if (entry == NULL && entry->flags != pt_flags)
|
|
return _EFAULT;
|
|
}
|
|
if (!(pt_flags & P_ANONYMOUS)) {
|
|
FIXME("mremap grow on file mappings");
|
|
return _EFAULT;
|
|
}
|
|
page_t extra_start = PAGE(addr) + old_pages;
|
|
pages_t extra_pages = new_pages - old_pages;
|
|
if (!pt_is_hole(current->mem, extra_start, extra_pages))
|
|
return _ENOMEM;
|
|
int err = pt_map_nothing(current->mem, extra_start, extra_pages, pt_flags);
|
|
if (err < 0)
|
|
return err;
|
|
return addr;
|
|
}
|
|
|
|
int_t sys_mprotect(addr_t addr, uint_t len, int_t prot) {
|
|
STRACE("mprotect(0x%x, 0x%x, 0x%x)", addr, len, prot);
|
|
if (PGOFFSET(addr) != 0)
|
|
return _EINVAL;
|
|
if (prot & ~P_RWX)
|
|
return _EINVAL;
|
|
pages_t pages = PAGE_ROUND_UP(len);
|
|
write_wrlock(¤t->mem->lock);
|
|
int err = pt_set_flags(current->mem, PAGE(addr), pages, prot);
|
|
write_wrunlock(¤t->mem->lock);
|
|
return err;
|
|
}
|
|
|
|
dword_t sys_madvise(addr_t UNUSED(addr), dword_t UNUSED(len), dword_t UNUSED(advice)) {
|
|
// portable applications should not rely on linux's destructive semantics for MADV_DONTNEED.
|
|
return 0;
|
|
}
|
|
|
|
dword_t sys_mbind(addr_t UNUSED(addr), dword_t UNUSED(len), int_t UNUSED(mode),
|
|
addr_t UNUSED(nodemask), dword_t UNUSED(maxnode), uint_t UNUSED(flags)) {
|
|
return 0;
|
|
}
|
|
|
|
int_t sys_mlock(addr_t UNUSED(addr), dword_t UNUSED(len)) {
|
|
return 0;
|
|
}
|
|
|
|
int_t sys_msync(addr_t UNUSED(addr), dword_t UNUSED(len), int_t UNUSED(flags)) {
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_brk(addr_t new_brk) {
|
|
STRACE("brk(0x%x)", new_brk);
|
|
struct mm *mm = current->mm;
|
|
|
|
write_wrlock(&mm->mem.lock);
|
|
if (new_brk < mm->start_brk)
|
|
goto out;
|
|
addr_t old_brk = mm->brk;
|
|
|
|
if (new_brk > old_brk) {
|
|
// expand heap: map region from old_brk to new_brk
|
|
// round up because of the definition of brk: "the first location after the end of the uninitialized data segment." (brk(2))
|
|
// if the brk is 0x2000, page 0x2000 shouldn't be mapped, but it should be if the brk is 0x2001.
|
|
page_t start = PAGE_ROUND_UP(old_brk);
|
|
pages_t size = PAGE_ROUND_UP(new_brk) - PAGE_ROUND_UP(old_brk);
|
|
if (!pt_is_hole(&mm->mem, start, size))
|
|
goto out;
|
|
int err = pt_map_nothing(&mm->mem, start, size, P_WRITE);
|
|
if (err < 0)
|
|
goto out;
|
|
} else if (new_brk < old_brk) {
|
|
// shrink heap: unmap region from new_brk to old_brk
|
|
// first page to unmap is PAGE(new_brk)
|
|
// last page to unmap is PAGE(old_brk)
|
|
pt_unmap_always(&mm->mem, PAGE(new_brk), PAGE(old_brk) - PAGE(new_brk));
|
|
}
|
|
|
|
mm->brk = new_brk;
|
|
out:;
|
|
addr_t brk = mm->brk;
|
|
write_wrunlock(&mm->mem.lock);
|
|
return brk;
|
|
}
|