blob: c8f5474358cb72dd7331bd197e37468853001afa [file] [log] [blame]
/*
* Copyright (c) 2012 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "native_client/src/include/nacl_assert.h"
#include "native_client/src/include/nacl/nacl_exception.h"
#define PRINT_HEADER 0
#define TEXT_LINE_SIZE 1024
const char *example_file;
const char *tmp_dir;
/*
* function failed(testname, msg)
* print failure message and exit with a return code of -1
*/
bool failed(const char *testname, const char *msg) {
printf("TEST FAILED: %s: %s\n", testname, msg);
return false;
}
/*
* function passed(testname, msg)
* print success message
*/
bool passed(const char *testname, const char *msg) {
printf("TEST PASSED: %s: %s\n", testname, msg);
return true;
}
static jmp_buf g_jmp_buf;
static void exception_handler(struct NaClExceptionContext *context) {
/* We got an exception as expected. Return from the handler. */
int rc = nacl_exception_clear_flag();
assert(rc == 0);
longjmp(g_jmp_buf, 1);
}
static void assert_addr_is_unreadable(volatile char *addr) {
/*
* TODO(mseaborn): It would be better to use Valgrind annotations to
* turn off the memory access checks temporarily.
*/
if (getenv("RUNNING_ON_VALGRIND") != NULL) {
fprintf(stderr, "Skipping assert_addr_is_unreadable() under Valgrind\n");
return;
}
/*
* Non-SFI Mode nonsfi_loader with host libc does not have signal
* handler implementation.
*/
if (!USE_NEWLIB_NONSFI_LOADER) {
fprintf(stderr, "Skipping assert_addr_is_unreadable() under "
"nonsfi_loader with host libc\n");
return;
}
int rc = nacl_exception_set_handler(exception_handler);
assert(rc == 0);
if (!setjmp(g_jmp_buf)) {
char value = *addr;
/* If we reach here, the assertion failed. */
fprintf(stderr, "Address %p was readable, and contained %i\n",
/* C-style cast, because C++ casts can't cast away volatile */
(void *) addr, value);
exit(1);
}
/*
* Clean up: Unregister the exception handler so that we do not
* accidentally return through g_jmp_buf if an exception occurs.
*/
rc = nacl_exception_set_handler(NULL);
assert(rc == 0);
}
static void assert_addr_is_unwritable(volatile char *addr, char value) {
/*
* TODO(mseaborn): It would be better to use Valgrind annotations to
* turn off the memory access checks temporarily.
*/
if (getenv("RUNNING_ON_VALGRIND") != NULL) {
fprintf(stderr, "Skipping assert_addr_is_unwritable() under Valgrind\n");
return;
}
if (getenv("RUNNING_ON_ASAN") != NULL) {
fprintf(stderr, "Skipping assert_addr_is_unwritable() under ASan\n");
return;
}
/*
* Non-SFI Mode nonsfi_loader with host libc does not have signal
* handler implementation.
*/
if (!USE_NEWLIB_NONSFI_LOADER) {
fprintf(stderr, "Skipping assert_addr_is_unwritable() under "
"nonsfi_loader with host libc\n");
return;
}
int rc = nacl_exception_set_handler(exception_handler);
assert(rc == 0);
if (!setjmp(g_jmp_buf)) {
*addr = value;
/* If we reach here, the assertion failed. */
fprintf(stderr, "Address %p was writable, %i was written\n",
/* C-style cast, because C++ casts can't cast away volatile */
(void *) addr, value);
exit(1);
}
/*
* Clean up: Unregister the exception handler so that we do not
* accidentally return through g_jmp_buf if an exception occurs.
*/
rc = nacl_exception_set_handler(NULL);
assert(rc == 0);
}
static void assert_page_is_allocated(void *addr) {
const int kPageSize = getpagesize();
assert(((uintptr_t) addr & (kPageSize - 1)) == 0);
/*
* Try mapping at addr without MAP_FIXED. If something is already
* mapped there, the system will pick another address. Otherwise,
* we will get the address we asked for.
*/
void *result = mmap(addr, kPageSize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
assert(result != MAP_FAILED);
assert(result != addr);
int rc = munmap(result, kPageSize);
assert(rc == 0);
}
/*
* function test*()
*
* Simple tests follow below. Each test may call one or more
* of the functions above. They all have a boolean return value
* to indicate success (all tests passed) or failure (one or more
* tests failed) Order matters - the parent should call
* test1() before test2(), and so on.
*/
bool test1() {
int size = 64 * 1024; /* we need 64K */
void *zeroes;
// test simple mmap
void *res = NULL;
int rv;
printf("test1\n");
res = mmap(res, size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if (0 >= res) /* define MAP_FAILED */
return false;
printf("mmap done\n");
zeroes = malloc(size);
memset(zeroes, 0, size);
if (memcmp(res, zeroes, size)) {
printf("memcmp failed\n");
return false;
}
rv = munmap(res, 1024);
if (rv != 0) {
printf("munmap failed\n");
return false;
}
printf("munmap good\n");
return true;
}
/*
* Verify that munmap of executable text pages will fail.
*/
bool test2() {
int rv;
printf("test2\n");
if (NONSFI_MODE) {
/*
* Unmapping SFI-NaCl's text page would succeed in non-SFI
* mode. We skip this test case.
*/
printf("test2 skipped\n");
return true;
}
/* text starts at 64K */
rv = munmap(reinterpret_cast<void*>(1<<16), (size_t) (1<<16));
/*
* if the munmap succeeds, we probably won't be able to continue to
* run....
*/
printf("munmap returned %d\n", rv);
if (-1 == rv && EINVAL == errno) {
printf("munmap good (failed as expected)\n");
return true;
}
printf("munmap should not have succeeded, or failed with wrong error\n");
return false;
}
/*
* Verify that mmap into the NULL pointer guard page will fail.
*/
bool test3() {
void *res;
printf("test3\n");
if (NONSFI_MODE) {
/*
* This test checks a security property of the NaCl TCB. However, when
* calling Linux's mmap() syscall directly, we can't necessarily expect
* a specific error value for this case.
*/
printf("test3 skipped\n");
return true;
}
res = mmap(static_cast<void*>(0), (size_t) (1 << 16),
PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
printf("res = %p\n", res);
if (MAP_FAILED == res) {
printf("errno = %d\n", errno);
}
if (MAP_FAILED == res && EINVAL == errno) {
printf("mmap okay\n");
return true;
}
printf("mmap should not have succeeded, or failed with wrong error\n");
return false;
}
/*
* Verify that mmap/MAP_FIXED with a non-page-aligned address will fail.
*/
bool test4() {
printf("test4\n");
/* First reserve some address space in which to perform the experiment. */
char *alloc = (char *) mmap(NULL, 1 << 16, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (MAP_FAILED == alloc) {
printf("mmap failed\n");
return false;
}
void *res = mmap((void *) (alloc + 0x100), 1 << 16, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if (MAP_FAILED == res && EINVAL == errno) {
printf("mmap gave an error as expected\n");
return true;
}
printf("mmap should not have succeeded, or failed with wrong error\n");
return false;
}
/*
* Verify that munmap() leaves virtual addresses inaccessible.
*/
bool test_munmap() {
printf("test_munmap\n");
/*
* Note that this test could fail if it were run concurrently with
* other tests in the same process, because other threads might
* mmap() pages at the address we munmap().
*
* Note that, on Windows, NaCl's munmap() has different code paths
* for anonymous and file-backed mappings. This test case only
* covers the anonymous case. The file-backed case is covered by
* test_mmap_end_of_file().
*/
size_t map_size = 0x20000;
char *addr = (char *) mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(addr != MAP_FAILED);
int rc = munmap(addr, map_size);
assert(rc == 0);
assert_addr_is_unreadable(addr);
assert_addr_is_unreadable(addr + 0x1000);
assert_addr_is_unreadable(addr + 0x10000);
/* Test that munmap() is idempotent. */
rc = munmap(addr, map_size);
assert(rc == 0);
return true;
}
bool test_mmap_zero_size() {
/*
* This test fails under Non-SFI Mode under ARM QEMU, because ARM QEMU
* handles this case incorrectly.
*/
if (NONSFI_MODE)
return true;
void *addr = mmap(NULL, 0, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_EQ(addr, MAP_FAILED);
ASSERT_EQ(errno, EINVAL);
/* Test behaviour when rounding up the size overflows. */
addr = mmap(NULL, ~(size_t) 0, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_EQ(addr, MAP_FAILED);
ASSERT_EQ(errno, EINVAL);
return true;
}
bool test_munmap_zero_size() {
/* Allocate a valid address to test munmap() with. */
size_t map_size = 0x10000;
char *addr = (char *) mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(addr != MAP_FAILED);
int rc = munmap(addr, 0);
ASSERT_EQ(rc, -1);
ASSERT_EQ(errno, EINVAL);
/* Test behaviour when rounding up the size overflows. */
rc = munmap(addr, ~(size_t) 0);
ASSERT_EQ(rc, -1);
ASSERT_EQ(errno, EINVAL);
/* Clean up. */
rc = munmap(addr, map_size);
ASSERT_EQ(rc, 0);
return true;
}
/*
* Verify that mprotect() changes the virtual address protection.
*/
bool test_mprotect() {
printf("test_mprotect\n");
/*
* Note that, on Windows, NaCl's mprotect() has different code paths
* for anonymous and file-backed mappings. This test case only
* covers the anonymous case.
*/
size_t map_size = 0x20000;
char *addr = (char *) mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(addr != MAP_FAILED);
printf("mmap done\n");
/* Change the protection to make the page unreadable. */
int rc = mprotect(addr, map_size, PROT_NONE);
assert(rc == 0);
assert_addr_is_unreadable(addr);
assert_addr_is_unreadable(addr + 0x1000);
assert_addr_is_unreadable(addr + 0x10000);
/* Change the protection to make the page accessible again. */
rc = mprotect(addr, map_size, PROT_READ | PROT_WRITE);
assert(rc == 0);
addr[0] = '5';
/* Change the protection to make the page read-only. */
rc = mprotect(addr, map_size, PROT_READ);
assert(rc == 0);
assert_addr_is_unwritable(addr, '9');
assert('5' == addr[0]);
printf("mprotect good\n");
/* We can still munmap() the memory. */
rc = munmap(addr, map_size);
assert(rc == 0);
return true;
}
bool test_mprotect_offset() {
printf("test_mprotect_offset\n");
/*
* Note that, on Windows, NaCl's mprotect() has different code paths
* for anonymous and file-backed mappings. This test case only
* covers the anonymous case.
*/
size_t map_size = 0x40000;
volatile char *addr = (char *) mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(addr != MAP_FAILED);
printf("mmap done\n");
/* Change the protection to make the pages unreadable. */
int rc = mprotect((char *) addr + 0x20000, 0x20000, PROT_NONE);
assert(rc == 0);
addr[0] = '5';
assert('5' == addr[0]);
assert_addr_is_unreadable(addr + 0x20000);
assert_addr_is_unreadable(addr + 0x30000);
/* Change the protection to make the pages read-only. */
rc = mprotect((char *) addr, 0x20000, PROT_READ);
assert(rc == 0);
assert_addr_is_unwritable(addr, '9');
assert('5' == addr[0]);
/* Change the protection to make the pages accessible again. */
rc = mprotect((char *) addr + 0x10000, 0x20000, PROT_READ | PROT_WRITE);
assert(rc == 0);
*(addr + 0x20000) = '7';
printf("mprotect good\n");
/* We can still munmap() the memory. */
rc = munmap((char *) addr, map_size);
assert(rc == 0);
return true;
}
/*
* Verify that mprotect() fails when changing protection of unmapped
* memory region.
*/
bool test_mprotect_unmapped_memory() {
printf("test_mprotect_unmapped_memory\n");
/*
* Note that, on Windows, NaCl's mprotect() has different code paths
* for anonymous and file-backed mappings. This test case only
* covers the anonymous case.
*/
size_t map_size = 0x20000;
char *addr = (char *) mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(addr != MAP_FAILED);
printf("mmap done\n");
/* Unmap the mapped memory region. */
int rc = munmap(addr, map_size);
assert(rc == 0);
printf("munmap done\n");
/* Change the protection to make the page unreadable. */
rc = mprotect(addr, map_size, PROT_NONE);
const int kExpectedErrno = NONSFI_MODE ? ENOMEM : EACCES;
if (-1 == rc && kExpectedErrno == errno) {
printf("mprotect good (failed as expected)\n");
return true;
}
printf("mprotect should not have succeeded, or failed with wrong error\n");
return false;
}
/*
* Verify that the last page in a file can be mmapped when the file's
* size is not a multiple of the page size.
* Tests for http://code.google.com/p/nativeclient/issues/detail?id=836
*/
bool test_mmap_end_of_file() {
printf("test_mmap_end_of_file\n");
int fd = open(example_file, O_RDONLY);
if (fd < 0) {
printf("open() failed\n");
return false;
}
size_t map_size = 0x20000;
/*
* First, map an address range as readable+writable, in order to
* check that these mappings are properly overwritten by the second
* mmap() call.
*/
char *alloc = (char *) mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(alloc != MAP_FAILED);
char *addr = (char *) mmap((void *) alloc, map_size, PROT_READ,
MAP_PRIVATE | MAP_FIXED, fd, 0);
assert(addr == alloc);
int rc = close(fd);
if (rc != 0) {
printf("close() failed\n");
return false;
}
/* To avoid line ending issues, this test file contains no newlines. */
const char *expected_data =
"Test file for mmapping, less than a page in size.";
if (memcmp(alloc, expected_data, strlen(expected_data)) != 0) {
printf("Unexpected contents: %s\n", alloc);
return false;
}
/* The first 4k page should be readable. */
for (size_t i = strlen(expected_data); i < 0x1000; i++) {
if (alloc[i] != 0) {
printf("Unexpected padding byte: %i\n", alloc[i]);
return false;
}
}
/*
* Addresses beyond the first 4k should not be readable. This is
* one case where we expose a 4k page size rather than a 64k page
* size. Windows forces us to expose a mixture of 4k and 64k page
* sizes for end-of-file mappings.
* See http://code.google.com/p/nativeclient/issues/detail?id=824
*/
assert_addr_is_unreadable(alloc + 0x1000);
assert_addr_is_unreadable(alloc + 0x2000);
assert_addr_is_unreadable(alloc + 0x10000);
assert_addr_is_unreadable(alloc + 0x11000);
assert_page_is_allocated(alloc);
assert_page_is_allocated(alloc + 0x10000);
rc = munmap(alloc, map_size);
if (rc != 0) {
printf("munmap() failed\n");
return false;
}
/* This is similar to test_munmap(), but it covers the file-backed case. */
assert_addr_is_unreadable(alloc);
assert_addr_is_unreadable(alloc + 0x1000);
assert_addr_is_unreadable(alloc + 0x2000);
assert_addr_is_unreadable(alloc + 0x10000);
assert_addr_is_unreadable(alloc + 0x11000);
return true;
}
/*
* Verify mmap with file offsets works properly.
*/
bool test_mmap_offset() {
printf("test_mmap_offset\n");
/*
* Prepare a file which is filled with raw integer values. These
* integer values are their offsets in the file.
*/
char tmp_filename[PATH_MAX] = {};
snprintf(tmp_filename, PATH_MAX - 1, "%s/test.txt", tmp_dir);
int fd = open(tmp_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
ASSERT_GE(fd, 0);
const int map_size = 0x10000;
for (int i = 0; i < map_size * 2; i += sizeof(i)) {
ssize_t written = write(fd, &i, sizeof(i));
ASSERT_EQ(written, sizeof(i));
}
int rc = close(fd);
ASSERT_EQ(rc, 0);
fd = open(tmp_filename, O_RDONLY);
ASSERT_GE(fd, 0);
/* A valid mmap call with an offset specified. */
int file_offset = 0x10000;
char *addr = (char *) mmap(NULL, map_size, PROT_READ,
MAP_PRIVATE, fd, file_offset);
ASSERT_NE(addr, MAP_FAILED);
for (int i = 0; i < map_size; i += sizeof(i)) {
int expected = i + file_offset;
int actual = *(int *) (addr + i);
ASSERT_EQ(expected, actual);
}
rc = munmap(addr, map_size);
ASSERT_EQ(rc, 0);
/* An invalid offset is specified to mmap. */
file_offset = 0x100;
addr = (char *) mmap(NULL, map_size, PROT_READ,
MAP_PRIVATE, fd, file_offset);
ASSERT_MSG(addr == MAP_FAILED && EINVAL == errno,
"mmap should not have succeeded, or failed with wrong error");
rc = close(fd);
ASSERT_EQ(rc, 0);
printf("mmap with offset good\n");
return true;
}
/*
* function testSuite()
*
* Run through a complete sequence of file tests.
*
* returns true if all tests succeed. false if one or more fail.
*/
bool testSuite() {
bool ret = true;
// The order of executing these tests matters!
ret &= test1();
ret &= test2();
ret &= test3();
ret &= test4();
ret &= test_munmap();
ret &= test_mmap_zero_size();
ret &= test_munmap_zero_size();
ret &= test_mprotect();
ret &= test_mprotect_offset();
ret &= test_mprotect_unmapped_memory();
ret &= test_mmap_end_of_file();
ret &= test_mmap_offset();
return ret;
}
/*
* main entry point.
*
* run all tests and call system exit with appropriate value
* 0 - success, all tests passed.
* -1 - one or more tests failed.
*/
int main(const int argc, const char *argv[]) {
bool passed;
if (argc != 3) {
printf("Error: Expected test file and temp dir args\n");
return 1;
}
example_file = argv[1];
tmp_dir = argv[2];
// run the full test suite
passed = testSuite();
if (passed) {
printf("All tests PASSED\n");
exit(0);
}
printf("One or more tests FAILED\n");
exit(-1);
}