/* * Copyright IBM Corp. * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2.1 of the GNU Lesser General Public License * as published by the Free Software Foundation. * * This program is distributed in the hope that it would be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * */ #include <assert.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/ptrace.h> #include <sys/syscall.h> #include <ucontext.h> #include <unistd.h> #include "utils.h" char *file_name; int in_test; volatile int faulted; volatile void *dar; int errors; static void segv(int signum, siginfo_t *info, void *ctxt_v) { ucontext_t *ctxt = (ucontext_t *)ctxt_v; struct pt_regs *regs = ctxt->uc_mcontext.regs; if (!in_test) { fprintf(stderr, "Segfault outside of test !\n"); exit(1); } faulted = 1; dar = (void *)regs->dar; regs->nip += 4; } static inline void do_read(const volatile void *addr) { int ret; asm volatile("lwz %0,0(%1); twi 0,%0,0; isync;\n" : "=r" (ret) : "r" (addr) : "memory"); } static inline void do_write(const volatile void *addr) { int val = 0x1234567; asm volatile("stw %0,0(%1); sync; \n" : : "r" (val), "r" (addr) : "memory"); } static inline void check_faulted(void *addr, long page, long subpage, int write) { int want_fault = (subpage == ((page + 3) % 16)); if (write) want_fault |= (subpage == ((page + 1) % 16)); if (faulted != want_fault) { printf("Failed at %p (p=%ld,sp=%ld,w=%d), want=%s, got=%s !\n", addr, page, subpage, write, want_fault ? "fault" : "pass", faulted ? "fault" : "pass"); ++errors; } if (faulted) { if (dar != addr) { printf("Fault expected at %p and happened at %p !\n", addr, dar); } faulted = 0; asm volatile("sync" : : : "memory"); } } static int run_test(void *addr, unsigned long size) { unsigned int *map; long i, j, pages, err; pages = size / 0x10000; map = malloc(pages * 4); assert(map); /* * for each page, mark subpage i % 16 read only and subpage * (i + 3) % 16 inaccessible */ for (i = 0; i < pages; i++) { map[i] = (0x40000000 >> (((i + 1) * 2) % 32)) | (0xc0000000 >> (((i + 3) * 2) % 32)); } err = syscall(__NR_subpage_prot, addr, size, map); if (err) { perror("subpage_perm"); return 1; } free(map); in_test = 1; errors = 0; for (i = 0; i < pages; i++) { for (j = 0; j < 16; j++, addr += 0x1000) { do_read(addr); check_faulted(addr, i, j, 0); do_write(addr); check_faulted(addr, i, j, 1); } } in_test = 0; if (errors) { printf("%d errors detected\n", errors); return 1; } return 0; } static int syscall_available(void) { int rc; errno = 0; rc = syscall(__NR_subpage_prot, 0, 0, 0); return rc == 0 || (errno != ENOENT && errno != ENOSYS); } int test_anon(void) { unsigned long align; struct sigaction act = { .sa_sigaction = segv, .sa_flags = SA_SIGINFO }; void *mallocblock; unsigned long mallocsize; SKIP_IF(!syscall_available()); if (getpagesize() != 0x10000) { fprintf(stderr, "Kernel page size must be 64K!\n"); return 1; } sigaction(SIGSEGV, &act, NULL); mallocsize = 4 * 16 * 1024 * 1024; FAIL_IF(posix_memalign(&mallocblock, 64 * 1024, mallocsize)); align = (unsigned long)mallocblock; if (align & 0xffff) align = (align | 0xffff) + 1; mallocblock = (void *)align; printf("allocated malloc block of 0x%lx bytes at %p\n", mallocsize, mallocblock); printf("testing malloc block...\n"); return run_test(mallocblock, mallocsize); } int test_file(void) { struct sigaction act = { .sa_sigaction = segv, .sa_flags = SA_SIGINFO }; void *fileblock; off_t filesize; int fd; SKIP_IF(!syscall_available()); fd = open(file_name, O_RDWR); if (fd == -1) { perror("failed to open file"); return 1; } sigaction(SIGSEGV, &act, NULL); filesize = lseek(fd, 0, SEEK_END); if (filesize & 0xffff) filesize &= ~0xfffful; fileblock = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (fileblock == MAP_FAILED) { perror("failed to map file"); return 1; } printf("allocated %s for 0x%lx bytes at %p\n", file_name, filesize, fileblock); printf("testing file map...\n"); return run_test(fileblock, filesize); } int main(int argc, char *argv[]) { int rc; rc = test_harness(test_anon, "subpage_prot_anon"); if (rc) return rc; if (argc > 1) file_name = argv[1]; else file_name = "tempfile"; return test_harness(test_file, "subpage_prot_file"); }