blob: 02ae2300af149f337c0bdc21ffff5997dfa41441 [file] [log] [blame]
/*
* Copyright (c) 2013 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 <setjmp.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "native_client/src/include/nacl_macros.h"
#include "native_client/src/shared/platform/nacl_check.h"
#include "native_client/src/include/nacl_compiler_annotations.h"
#include "native_client/src/include/nacl/nacl_exception.h"
#include "native_client/src/trusted/service_runtime/nacl_config.h"
#define NUM_FILE_BYTES 3 * NACL_MAP_PAGESIZE
static jmp_buf g_jmp_buf;
static void test_handler(struct NaClExceptionContext *context) {
/* We got an exception as expected. Return from the handler. */
int rc = nacl_exception_clear_flag();
CHECK(rc == 0);
longjmp(g_jmp_buf, 1);
}
static void check_addr_is_unreadable(volatile char *addr) {
int rc;
if (getenv("RUNNING_ON_VALGRIND") != NULL) {
fprintf(stderr, "Skipping assert_addr_is_unreadable() under Valgrind\n");
return;
}
rc = nacl_exception_set_handler(test_handler);
CHECK(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",
(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);
CHECK(rc == 0);
}
static void check_addr_is_unwritable(volatile char *addr, char value) {
int rc;
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;
}
rc = nacl_exception_set_handler(test_handler);
CHECK(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",
(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);
CHECK(rc == 0);
}
static unsigned char const g_test_data[] =
"Test file for mmapping, less than a page in size.";
struct prot_basic_specs {
int map_prot;
int map_flags;
int expected_errno; /* if expected mmap to fail */
int target_prot;
};
int test_prot_basic(int fd, size_t map_size, void *test_spec) {
struct prot_basic_specs *spec = (struct prot_basic_specs *) test_spec;
void *addr;
addr = mmap(NULL, map_size, spec->map_prot, spec->map_flags, fd, 0);
if (MAP_FAILED == addr) {
fprintf(stderr, "test_prot_basic: mmap failed, errno %d\n", errno);
return 1;
}
if (0 != spec->expected_errno) {
if (MAP_FAILED != addr) {
fprintf(stderr,
"test_prot_basic: expected mmap to fail but did not.\n");
return 1;
}
if (spec->expected_errno != errno) {
fprintf(stderr,
"test_prot_basic: expected mmap to fail with errno %d,"
" got %d instead.\n", spec->expected_errno, errno);
return 1;
}
} else {
if (MAP_FAILED == addr) {
fprintf(stderr, "test_prot_basic: mmap failed, errno %d\n", errno);
return 1;
}
}
printf("test_prot_basic: mmap done\n");
/* Change the protection to make the page accessible. */
CHECK(0 == mprotect(addr, map_size, spec->target_prot));
printf("test_prot_basic: mprotect good\n");
/* Check that memory contains the expected content. */
CHECK(0 == memcmp(addr, g_test_data, sizeof g_test_data));
/* Unmap the memory. */
CHECK(0 == munmap(addr, map_size));
printf("test_prot_basic: munmap done\n");
return 0;
}
struct prot_access_specs {
int map_prot;
int map_flags;
int map_offset;
};
int test_prot_access(int fd, size_t map_size, void *test_spec) {
struct prot_access_specs *spec = (struct prot_access_specs *) test_spec;
char *addr;
addr = (char *) mmap(NULL,
map_size,
spec->map_prot,
spec->map_flags,
fd,
spec->map_offset);
if (MAP_FAILED == addr) {
fprintf(stderr, "test_prot_access: mmap failed, errno %d\n", errno);
return 1;
}
printf("test_prot_access: mmap done\n");
/* Change the protection to make the page unreadable. */
CHECK(0 == mprotect(addr, map_size, PROT_NONE));
check_addr_is_unreadable(addr);
check_addr_is_unreadable(addr + 0x1000);
check_addr_is_unreadable(addr + 0x10000);
check_addr_is_unreadable(addr + 0x20000);
/* Change the protection to make the page accessible again. */
CHECK(0 == mprotect(addr, map_size, PROT_READ | PROT_WRITE));
addr[0] = '5';
/* Change the protection to make the page read-only. */
CHECK(0 == mprotect(addr, map_size, PROT_READ));
check_addr_is_unwritable(addr, '9');
CHECK('5' == addr[0]);
printf("test_prot_access: mprotect good\n");
/* We can still munmap() the memory. */
CHECK(0 == munmap(addr, map_size));
printf("test_prot_access: munmap done\n");
return 0;
}
struct prot_unmap_specs {
int map_prot;
int map_flags;
off_t unmap_offset;
};
int test_prot_unmap(int fd, size_t map_size, void *test_spec) {
struct prot_unmap_specs *spec = (struct prot_unmap_specs *) test_spec;
char *addr;
addr = (char *) mmap(NULL, map_size, spec->map_prot, spec->map_flags, fd, 0);
if (MAP_FAILED == addr) {
fprintf(stderr, "test_prot_unmap: mmap failed, errno %d\n", errno);
return 1;
}
printf("test_prot_unmap: mmap done\n");
/* Change the protection to make the page unreadable. */
CHECK(0 == mprotect(addr, spec->unmap_offset, PROT_NONE));
printf("test_prot_unmap: mprotect done\n");
/* Unmap the memory. */
CHECK(0 == munmap(addr, spec->unmap_offset));
CHECK(0 == munmap(addr + spec->unmap_offset, map_size - spec->unmap_offset));
printf("test_prot_unmap: munmap done\n");
return 0;
}
struct prot_unmapped_specs {
int map_prot;
int map_flags;
off_t unmap_offset;
};
int test_prot_unmapped(int fd, size_t map_size, void *test_spec) {
struct prot_unmapped_specs *spec = (struct prot_unmapped_specs *) test_spec;
char *addr;
addr = (char *) mmap(NULL, map_size, spec->map_prot, spec->map_flags, fd, 0);
if (MAP_FAILED == addr) {
fprintf(stderr, "test_prot_unmapped: mmap failed, errno %d\n", errno);
return 1;
}
printf("test_prot_unmapped: mmap done\n");
/* Now unmap the region so that we know the memory is unmapped. */
CHECK(0 == munmap(addr + spec->unmap_offset, map_size - spec->unmap_offset));
printf("test_prot_unmapped: munmap done\n");
/* Try to change the region protection which should fail. */
CHECK(0 != mprotect(addr, map_size, PROT_READ | PROT_WRITE));
CHECK(EACCES == errno);
printf("test_prot_unmapped: mprotect good\n");
return 0;
}
struct prot_none_specs {
int map_prot;
int map_flags;
};
/*
* Verify that mprotect() changes the virtual address protection.
* Tests for http://code.google.com/p/nativeclient/issues/detail?id=3354
*/
int test_prot_none(int fd, size_t map_size, void *test_spec) {
static unsigned char const expected_data[] =
"Changed test data, less than a page in size.";
struct prot_unmap_specs *spec = (struct prot_unmap_specs *) test_spec;
char *addr;
addr = mmap(NULL, map_size, spec->map_prot, spec->map_flags, fd, 0);
if (MAP_FAILED == addr) {
fprintf(stderr, "test_prot_none: mmap failed, errno %d\n", errno);
return 1;
}
printf("test_prot_none: mmap done\n");
/* Change the memory content. */
memcpy(addr, expected_data, sizeof expected_data);
/* Check that memory contains the modified content. */
CHECK(0 == memcmp(addr, expected_data, sizeof expected_data));
/* Check that the rest of the region is inaccessible. */
check_addr_is_unreadable(addr + 0x1000);
check_addr_is_unreadable(addr + 0x10000);
printf("test_prot_none: memcpy good\n");
/* Change the protection to make the page unreadable. */
CHECK(0 == mprotect(addr, map_size, PROT_NONE));
check_addr_is_unreadable(addr);
check_addr_is_unreadable(addr + 0x1000);
check_addr_is_unreadable(addr + 0x10000);
/* Change the protection to make the page accessible again. */
CHECK(0 == mprotect(addr, map_size, PROT_READ | PROT_WRITE));
/* Check that the modified content is still there. */
CHECK(0 == memcmp(addr, expected_data, sizeof expected_data));
/* Check that the rest of the region is still inaccessible. */
check_addr_is_unreadable(addr + 0x1000);
check_addr_is_unreadable(addr + 0x10000);
printf("test_prot_none: mprotect good\n");
/* Unmap the memory. */
CHECK(0 == munmap(addr, map_size));
printf("test_prot_none: munmap done\n");
return 0;
}
struct prot_mapping_specs {
size_t map_offset;
};
int test_prot_mapping(int fd, size_t map_size, void *test_spec) {
struct prot_mapping_specs *spec = (struct prot_mapping_specs *) test_spec;
size_t region_size = map_size + spec->map_offset;
char *alloc;
char *view1;
char *view2;
/* Allocate memory to make sure we have enough contiguous space. */
alloc = (char *) mmap(NULL, region_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
CHECK(alloc != MAP_FAILED);
/* Now create both mappings adjacent to each other. */
view1 = (char *) mmap(alloc, map_size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, fd, /* offset*/ 0);
CHECK(view1 == alloc);
view2 = (char *) mmap(alloc + spec->map_offset, map_size, PROT_READ,
MAP_PRIVATE | MAP_FIXED, fd, /* offset */ 0);
CHECK(view2 == alloc + spec->map_offset);
printf("test_prot_view: mmap done\n");
view1[0] = '5';
CHECK('5' == view1[0]);
check_addr_is_unwritable(view2, '9');
/* Make both regions inaccessible. */
CHECK(0 == mprotect(view2, map_size, PROT_READ | PROT_WRITE));
view2[0] = '7';
CHECK('5' == view1[0]);
CHECK('7' == view2[0]);
printf("test_prot_view: mprotect good\n");
/* Check that we can still unmap both regions. */
CHECK(0 == munmap(alloc, region_size));
printf("test_prot_view: munmap done\n");
return 0;
}
int test_prot_ronly_rw(int fd, size_t map_size, void *test_spec) {
char *addr;
UNREFERENCED_PARAMETER(test_spec);
addr = (char *) mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0);
CHECK(addr != MAP_FAILED);
printf("test_prot_ronly_rw: mmap done\n");
/* Try to make the region read/write which should fail. */
CHECK(0 != mprotect(addr, map_size, PROT_READ | PROT_WRITE));
CHECK(EACCES == errno);
printf("test_prot_ronly_rw: mprotect good\n");
/* Check that we can still unmap both regions. */
CHECK(0 == munmap(addr, map_size));
printf("test_prot_ronly_rw: munmap done\n");
return 0;
}
struct test_params {
char const *test_name;
int (*test_func)(int fd, size_t map_size, void *test_specifics);
int open_flags;
int file_mode;
size_t file_size;
size_t map_size;
unsigned char const *test_data_start;
size_t test_data_size;
void *test_specifics;
};
#define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask))
#define ALIGN(x, n) __ALIGN_MASK(x, (__typeof__(x))(n) - 1)
#define IS_ALIGNED(x, n) (((x) & ((__typeof__(x))(n) - 1)) == 0)
int test_data_create(int fd, size_t num_bytes) {
static int buffer[1024];
size_t nbytes;
size_t written = 0;
ssize_t result;
CHECK(IS_ALIGNED(num_bytes, 4));
memset(buffer, 0, sizeof buffer);
while (written < num_bytes) {
nbytes = num_bytes - written;
if (nbytes > sizeof buffer) {
nbytes = sizeof buffer;
}
result = write(fd, buffer, nbytes);
if (result != nbytes) {
return -1;
}
written += nbytes;
}
return 0;
}
int test_file_create(char const *pathname, struct test_params *params) {
int fd;
struct stat stbuf;
off_t off;
size_t nbytes;
ssize_t result;
if (-1 == (fd = open(pathname,
O_WRONLY | O_CREAT | O_TRUNC,
params->file_mode))) {
fprintf(stderr,
"mmap_prot_test: could not open test file, errno %d\n",
errno);
exit(1);
}
if (0 != test_data_create(fd, params->file_size)) {
fprintf(stderr,
"mmap_prot_test: could not write test data, errno %d\n",
errno);
exit(1);
}
if (-1 == fstat(fd, &stbuf)) {
fprintf(stderr, "mmap_prot_test: fstat failed, errno %d\n", errno);
exit(1);
}
if (stbuf.st_size != params->file_size) {
fprintf(stderr,
"mmap_prot_test: file size incorrect:"
" should be %d, actual %d\n",
(int) params->file_size, (int) stbuf.st_size);
exit(1);
}
if (NULL != params->test_data_start) {
off = lseek(fd, 0LL, 0);
if (off < 0) {
fprintf(stderr,
"mmap_prot_test: could not seek, errno %d\n",
errno);
exit(1);
}
nbytes = params->test_data_size;
result = write(fd, params->test_data_start, nbytes);
if (result < 0) {
fprintf(stderr,
"mmap_prot_test: could not write test data, errno %d\n",
errno);
exit(1);
}
if ((size_t) result != nbytes) {
fprintf(stderr,
"mmap_prot_test: error while writing test data, errno %d\n",
errno);
exit(1);
}
}
CHECK(0 == close(fd));
if (-1 == (fd = open(pathname, params->open_flags, 0777))) {
fprintf(stderr,
"mmap_prot_test: failed to reopen test file, errno %d\n",
errno);
exit(1);
}
return fd;
}
void test_file_close(int d) {
CHECK(0 == close(d));
}
struct prot_basic_specs prot_basic_none_private = {
.map_prot = PROT_NONE,
.map_flags = MAP_PRIVATE,
.expected_errno = 0,
.target_prot = PROT_READ,
};
struct prot_basic_specs prot_basic_none_shared = {
.map_prot = PROT_NONE,
.map_flags = MAP_SHARED,
.expected_errno = 0,
.target_prot = PROT_READ,
};
struct prot_access_specs prot_access_private = {
.map_prot = PROT_READ | PROT_WRITE,
.map_flags = MAP_PRIVATE,
.map_offset = 0x0,
};
struct prot_access_specs prot_access_shared = {
.map_prot = PROT_READ | PROT_WRITE,
.map_flags = MAP_SHARED,
.map_offset = 0x0,
};
struct prot_access_specs prot_access_offset = {
.map_prot = PROT_READ | PROT_WRITE,
.map_flags = MAP_PRIVATE,
.map_offset = NACL_MAP_PAGESIZE,
};
struct prot_unmap_specs prot_unmap_private = {
.map_prot = PROT_READ | PROT_WRITE,
.map_flags = MAP_PRIVATE,
.unmap_offset = NACL_MAP_PAGESIZE,
};
struct prot_unmapped_specs prot_unmapped_full = {
.map_prot = PROT_READ | PROT_WRITE,
.map_flags = MAP_PRIVATE,
.unmap_offset = 0,
};
struct prot_unmapped_specs prot_unmapped_partial = {
.map_prot = PROT_READ | PROT_WRITE,
.map_flags = MAP_PRIVATE,
.unmap_offset = NACL_MAP_PAGESIZE,
};
struct prot_none_specs prot_none_private = {
.map_prot = PROT_READ | PROT_WRITE,
.map_flags = MAP_PRIVATE,
};
struct prot_mapping_specs prot_mapping_adjacent = {
.map_offset = NUM_FILE_BYTES,
};
struct prot_mapping_specs prot_mapping_overlapped = {
.map_offset = NACL_MAP_PAGESIZE,
};
struct test_params tests[] = {
{
.test_name = "PROT_NONE, MAP_PRIVATE, change to accessible",
.test_func = test_prot_basic,
.open_flags = O_RDONLY,
.file_mode = 0666,
.file_size = ALIGN(sizeof g_test_data, 4),
.map_size = NUM_FILE_BYTES,
.test_data_start = g_test_data,
.test_data_size = sizeof g_test_data,
.test_specifics = &prot_basic_none_private,
}, {
.test_name = "PROT_NONE, MAP_SHARED, change to accessible",
.test_func = test_prot_basic,
.open_flags = O_RDONLY,
.file_mode = 0666,
.file_size = ALIGN(sizeof g_test_data, 4),
.map_size = NUM_FILE_BYTES,
.test_data_start = g_test_data,
.test_data_size = sizeof g_test_data,
.test_specifics = &prot_basic_none_shared,
}, {
.test_name = "MAP_PRIVATE access test",
.test_func = test_prot_access,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = NUM_FILE_BYTES,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = &prot_access_private,
}, {
.test_name = "MAP_SHARED access test",
.test_func = test_prot_access,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = NUM_FILE_BYTES,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = &prot_access_shared,
}, {
.test_name = "MAP_PRIVATE access (non-aligned) test",
.test_func = test_prot_access,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = 0x21000,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = &prot_access_private,
}, {
.test_name = "MAP_PRIVATE offset access test",
.test_func = test_prot_access,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = NUM_FILE_BYTES,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = &prot_access_offset,
}, {
.test_name = "MAP_PRIVATE unmap test",
.test_func = test_prot_unmap,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = NUM_FILE_BYTES,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = &prot_unmap_private,
}, {
.test_name = "Unmapped (fully) protection test",
.test_func = test_prot_unmapped,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = NUM_FILE_BYTES,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = &prot_unmapped_full,
}, {
.test_name = "Unmapped (partially) protection test",
.test_func = test_prot_unmapped,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = NUM_FILE_BYTES,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = &prot_unmapped_partial,
}, {
.test_name = "PROT_NONE read/write test",
.test_func = test_prot_none,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = ALIGN(sizeof g_test_data, 4),
.map_size = NUM_FILE_BYTES,
.test_data_start = g_test_data,
.test_data_size = sizeof g_test_data,
.test_specifics = &prot_none_private,
}, {
.test_name = "PROT_NONE read-only test",
.test_func = test_prot_none,
.open_flags = O_RDONLY,
.file_mode = 0666,
.file_size = ALIGN(sizeof g_test_data, 4),
.map_size = NUM_FILE_BYTES,
.test_data_start = g_test_data,
.test_data_size = sizeof g_test_data,
.test_specifics = &prot_none_private,
}, {
.test_name = "Adjacent Mapping Test",
.test_func = test_prot_mapping,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = ALIGN(sizeof g_test_data, 4),
.map_size = NUM_FILE_BYTES,
.test_data_start = g_test_data,
.test_data_size = sizeof g_test_data,
.test_specifics = &prot_mapping_adjacent,
}, {
.test_name = "Overlapped Mapping Test",
.test_func = test_prot_mapping,
.open_flags = O_RDWR,
.file_mode = 0666,
.file_size = ALIGN(sizeof g_test_data, 4),
.map_size = NUM_FILE_BYTES,
.test_data_start = g_test_data,
.test_data_size = sizeof g_test_data,
.test_specifics = &prot_mapping_overlapped,
}, {
.test_name = "PROT_READ, MAP_SHARED, O_RDONLY, change to PROT_WRITE",
.test_func = test_prot_ronly_rw,
.open_flags = O_RDONLY,
.file_mode = 0666,
.file_size = NUM_FILE_BYTES,
.map_size = NUM_FILE_BYTES,
.test_data_start = NULL,
.test_data_size = 0,
.test_specifics = NULL,
},
};
int main(int argc, char **argv) {
static char const *test_file_dir = "/tmp/nacl_mmap_prot_test";
int fd;
size_t err_count;
size_t test_errors;
size_t ix;
int opt;
int num_runs = 1;
int test_run;
while (EOF != (opt = getopt(argc, argv, "c:t:"))) {
switch (opt) {
case 'c':
num_runs = atoi(optarg);
break;
case 't':
test_file_dir = optarg;
break;
default:
fprintf(stderr,
"Usage: mmap_prot_test [-c run_count]\n"
" [-t test_temporary_dir]\n");
exit(1);
}
}
err_count = 0;
for (test_run = 0; test_run < num_runs; ++test_run) {
printf("Test run %d\n\n", test_run);
for (ix = 0; ix < NACL_ARRAY_SIZE(tests); ++ix) {
char test_file_name[PATH_MAX];
printf("%s\n", tests[ix].test_name);
snprintf(test_file_name, sizeof test_file_name,
"%s/f%d.%u", test_file_dir, test_run, ix);
fd = test_file_create(test_file_name, &tests[ix]);
test_errors = (*tests[ix].test_func)(fd,
tests[ix].map_size,
tests[ix].test_specifics);
printf("%s\n", (0 == test_errors) ? "PASS" : "FAIL");
err_count += test_errors;
test_file_close(fd);
}
}
return (err_count > 0) ? -1 : 0;
}