blob: f95104900da191db43a1e41b4c9064d704deddb0 [file]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define _BSD_SOURCE /* For MADV_* constants */
#include <camkes/vma.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <utils/util.h>
/** Determine whether an address range is completely backed by mapped memory.
*
* @param addr Range start address
* @param len Range size in bytes
* @return true if the range is completely mapped
*/
static PURE bool covered(const void *addr, size_t len)
{
/* The range wraps memory. */
if (UINTPTR_MAX - (uintptr_t)len < (uintptr_t)addr) {
return false;
}
/* Repeatedly scan the VMAs looking for one covering the starting address. When we find one,
* bump the starting address and reduce the length then loop, scanning for the new starting
* address. This is not particularly efficient, but has the advantage of being relatively
* simple.
*/
size_t i;
do {
for (i = 0; i < camkes_vmas_size; i++) {
if (camkes_vmas[i].start <= addr && camkes_vmas[i].end > addr) {
/* Found a VMA covering the current starting address. */
if (!camkes_vmas[i].read && !camkes_vmas[i].write && !camkes_vmas[i].execute) {
/* This VMA represents *unmapped* memory. */
return false;
}
if (camkes_vmas[i].end - addr > len) {
/* This VMA covers the entire remaining range. */
return true;
} else {
/* This VMA only covers part of the remaining range. */
len -= camkes_vmas[i].end - addr;
addr = camkes_vmas[i].end;
}
break;
}
}
} while (i != camkes_vmas_size);
/* If we reach this point, then we did a sweep through the VMAs and failed to find one
* containing the current starting address.
*/
return false;
}
static long page_size(void)
{
/* Retrieve the page size the first time we run this function. */
static long pagesize;
if (pagesize == 0) {
pagesize = sysconf(_SC_PAGESIZE);
if (pagesize <= 0) {
/* Could not get page size */
pagesize = 0;
}
}
return pagesize;
}
/* There is no dynamic memory management in CAmkES, so `madvise` is no-op. As a nicety, we implement
* `madvise` to validate its inputs to give callers a more rational environment.
*/
long camkes_sys_madvise(va_list ap UNUSED)
{
void *addr = va_arg(ap, void *);
size_t length = va_arg(ap, size_t);
int advice = va_arg(ap, int);
/* Page size of our platform. */
long pagesize = page_size();
if (pagesize == 0) {
/* Could not get page size */
return -EINVAL;
}
/* Check address is page-aligned. */
if ((uintptr_t)addr % (uintptr_t)pagesize != 0) {
return -EINVAL;
}
/* Check length is valid. */
if (length == 0) {
return -EINVAL;
}
/* Check advice is valid. */
if (advice != MADV_NORMAL && advice != MADV_SEQUENTIAL && advice != MADV_RANDOM &&
advice != MADV_WILLNEED && advice != MADV_DONTNEED) {
return -EINVAL;
}
/* Check the range being operated on is entirely mapped memory. */
if (!covered(addr, length)) {
return -ENOMEM;
}
/* Success. */
return 0;
}
long camkes_sys_mincore(va_list ap)
{
void *addr = va_arg(ap, void *);
size_t length = va_arg(ap, size_t);
unsigned char *vec = va_arg(ap, unsigned char *);
/* Check addr is valid. */
long pagesize = page_size();
if (pagesize == 0) {
/* Could not get page size */
return -EINVAL;
}
if ((uintptr_t)addr % (uintptr_t)pagesize != 0) {
return -EINVAL;
}
/* Check length is valid. */
if (UINTPTR_MAX - (uintptr_t)length < (uintptr_t)addr) {
return -ENOMEM;
}
size_t pages = length / pagesize;
if (length % pagesize != 0) {
pages++;
}
/* Check vec is valid. */
if (vec == NULL || (uintptr_t)vec + pages * sizeof(unsigned char) < (uintptr_t)vec) {
return -EFAULT;
}
/* Now there are only two possibilities for the range [addr, addr + length):
*
* 1. It is entirely mapped memory; or
* 2. Some part of it is unmapped.
*
* In the first case, we know the entire region is paged in as there is no paging in CAmkES
* systems. The second case is a mincore error.
*/
if (covered(addr, length)) {
memset(vec, 1, pages * sizeof(unsigned char));
return 0;
} else {
return -ENOMEM;
}
}
/** The core logic of the checks for mlock/munlock.
*
* Note that on CAmkES mlock* and munlock* are all no-ops. This means that the core logic (this
* function) is identical. It simply validates the inputs.
*
* @param addr Address of region start
* @param len Size of region in bytes
* @return 0 on success or an errno value.
*/
static long mlock_internal(const void *addr, size_t len)
{
/* Check for overflow. */
if ((uintptr_t)addr + len < (uintptr_t)addr) {
return -EINVAL;
}
/* Check addr is page-aligned. */
long pagesize = page_size();
if (pagesize == 0) {
/* Could not get page size */
return -EINVAL;
}
if ((uintptr_t)addr % (uintptr_t)pagesize != 0) {
return -EINVAL;
}
if (covered(addr, len)) {
return 0;
} else {
return -ENOMEM;
}
}
long camkes_sys_mlock(va_list ap)
{
const void *addr = va_arg(ap, const void *);
size_t len = va_arg(ap, size_t);
return mlock_internal(addr, len);
}
long camkes_sys_munlock(va_list ap)
{
const void *addr = va_arg(ap, const void *);
size_t len = va_arg(ap, size_t);
return mlock_internal(addr, len);
}
long camkes_sys_mlockall(va_list ap)
{
int flags = va_arg(ap, int);
if ((flags & ~(MCL_CURRENT | MCL_FUTURE)) != 0) {
return -EINVAL;
}
return 0;
}
long camkes_sys_munlockall(va_list ap UNUSED)
{
return 0;
}