cosmopolitan/libc/intrin/mprotect.c
Justine Tunney f24c854b28
Write more runtime tests and fix bugs
This change adds tests for the new memory manager code particularly with
its windows support. Function call tracing now works reliably on Silicon
since our function hooker was missing new Apple self-modifying code APIs

Many tests that were disabled a long time ago on aarch64 are reactivated
by this change, now that arm support is on equal terms with x86. There's
been a lot of places where ftrace could cause deadlocks, which have been
hunted down across all platforms thanks to new tests. A bug in Windows's
kill() function has been identified.
2025-01-01 22:25:22 -08:00

236 lines
8.6 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/directmap.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/maps.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/tree.h"
#include "libc/nt/memory.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/errfuns.h"
#define PGUP(x) (((x) + pagesz - 1) & -pagesz)
static int __mprotect_chunk(char *addr, size_t size, int prot, bool iscow) {
if (!IsWindows())
return sys_mprotect(addr, size, prot);
uint32_t op;
if (!VirtualProtect(addr, size, __prot2nt(prot, iscow), &op))
return -1;
return 0;
}
int __mprotect(char *addr, size_t size, int prot) {
// unix checks prot before checking size
if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC | PROT_GUARD))
return einval();
// make new technology consistent with unix
if (!size)
return 0;
// unix checks prot before checking size
int pagesz = __pagesize;
if (((intptr_t)addr & (pagesz - 1)) || (uintptr_t)addr + size < size)
return einval();
// normalize size
size = (size + pagesz - 1) & -pagesz;
// test for signal handler reentry
if (__maps_reentrant())
return edeadlk();
// change mappings
int rc = 0;
bool found = false;
__maps_lock();
struct Map *map;
if (!(map = __maps_floor(addr)))
map = __maps_first();
for (; map && map->addr <= addr + size; map = __maps_next(map)) {
char *map_addr = map->addr;
size_t map_size = map->size;
char *beg = MAX(addr, map_addr);
char *end = MIN(addr + size, map_addr + PGUP(map_size));
if (beg >= end)
continue;
found = true;
if (addr <= map_addr && addr + size >= map_addr + PGUP(map_size)) {
// change protection status of pages
if (!__mprotect_chunk(map_addr, map_size, prot, map->iscow)) {
map->prot = prot;
} else {
rc = -1;
}
} else if (addr <= map_addr) {
// change lefthand side of mapping
size_t left = addr + size - map_addr;
size_t right = map_size - left;
struct Map *leftmap;
if ((leftmap = __maps_alloc())) {
if (!__mprotect_chunk(map_addr, left, prot, false)) {
leftmap->addr = map_addr;
leftmap->size = left;
leftmap->prot = prot;
leftmap->off = map->off;
leftmap->flags = map->flags;
leftmap->iscow = map->iscow;
leftmap->readonlyfile = map->readonlyfile;
leftmap->hand = map->hand;
map->addr += left;
map->size = right;
map->hand = MAPS_SUBREGION;
if (!(map->flags & MAP_ANONYMOUS))
map->off += left;
tree_insert(&__maps.maps, &leftmap->tree, __maps_compare);
__maps.count += 1;
__maps_check();
} else {
__maps_free(leftmap);
rc = -1;
}
} else {
rc = -1;
}
} else if (addr + size >= map_addr + PGUP(map_size)) {
// change righthand side of mapping
size_t left = addr - map_addr;
size_t right = map_addr + map_size - addr;
struct Map *leftmap;
if ((leftmap = __maps_alloc())) {
if (!__mprotect_chunk(map_addr + left, right, prot, false)) {
leftmap->addr = map_addr;
leftmap->size = left;
leftmap->off = map->off;
leftmap->prot = map->prot;
leftmap->flags = map->flags;
leftmap->iscow = map->iscow;
leftmap->readonlyfile = map->readonlyfile;
leftmap->hand = map->hand;
map->addr += left;
map->size = right;
map->prot = prot;
map->hand = MAPS_SUBREGION;
if (!(map->flags & MAP_ANONYMOUS))
map->off += left;
tree_insert(&__maps.maps, &leftmap->tree, __maps_compare);
__maps.count += 1;
__maps_check();
} else {
__maps_free(leftmap);
rc = -1;
}
} else {
rc = -1;
}
} else {
// change middle of mapping
size_t left = addr - map_addr;
size_t middle = size;
size_t right = map_size - middle - left;
struct Map *leftmap;
if ((leftmap = __maps_alloc())) {
struct Map *midlmap;
if ((midlmap = __maps_alloc())) {
if (!__mprotect_chunk(map_addr + left, middle, prot, false)) {
leftmap->addr = map_addr;
leftmap->size = left;
leftmap->off = map->off;
leftmap->prot = map->prot;
leftmap->flags = map->flags;
leftmap->iscow = map->iscow;
leftmap->readonlyfile = map->readonlyfile;
leftmap->hand = map->hand;
midlmap->addr = map_addr + left;
midlmap->size = middle;
midlmap->off = (map->flags & MAP_ANONYMOUS) ? 0 : map->off + left;
midlmap->prot = prot;
midlmap->flags = map->flags;
midlmap->hand = MAPS_SUBREGION;
map->addr += left + middle;
map->size = right;
map->hand = MAPS_SUBREGION;
if (!(map->flags & MAP_ANONYMOUS))
map->off += left + middle;
tree_insert(&__maps.maps, &leftmap->tree, __maps_compare);
tree_insert(&__maps.maps, &midlmap->tree, __maps_compare);
__maps.count += 2;
__maps_check();
} else {
__maps_free(midlmap);
__maps_free(leftmap);
rc = -1;
}
} else {
__maps_free(leftmap);
rc = -1;
}
} else {
rc = -1;
}
}
}
// allow user to change mappings unknown to cosmo runtime
if (!found)
rc = __mprotect_chunk(addr, size, prot, false);
__maps_unlock();
return rc;
}
/**
* Modifies restrictions on virtual memory address range.
*
* POSIX doesn't require mprotect() to be async signal safe. However you
* should be able to call this from a signal handler safely, if you know
* that your signal will never interrupt the cosmopolitan memory manager
* and the only way you can ensure that, is by blocking signals whenever
* you call mmap(), munmap(), mprotect(), etc.
*
* @param addr needs to be page size aligned
* @param size is rounded up to the page size
* @param prot can be PROT_NONE or a combination of PROT_READ,
* PROT_WRITE, and PROT_EXEC
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if `size` is zero
* @raise ENOMEM on tracking memory oom
* @raise EDEADLK if called from signal handler interrupting mmap()
*/
int mprotect(void *addr, size_t size, int prot) {
int rc;
rc = __mprotect(addr, size, prot);
STRACE("mprotect(%p, %'zu, %s) → %d% m", addr, size, DescribeProtFlags(prot),
rc);
return rc;
}