├── DonkyLib ├── pk │ ├── arch │ │ ├── riscv │ │ │ ├── pku_handler.S │ │ │ ├── pku_wrapper.hpp │ │ │ ├── pku_handler_c.c │ │ │ ├── pk_handler.h │ │ │ ├── pk_handler.S │ │ │ └── pk_arch.h │ │ └── x86_64 │ │ │ ├── pku_handler.S │ │ │ ├── pku_handler_c.c │ │ │ ├── pku_wrapper.h │ │ │ ├── pk_handler.h │ │ │ ├── pk_arch.h │ │ │ ├── pk_handler.S │ │ │ └── pk_handler_c.c │ ├── sysfilter.h │ ├── mprotect.h │ ├── pk_defs.h │ ├── mprotect.c │ ├── pku_api_wrapper.S │ ├── pk_debug.h │ └── pku_handler_generic.c ├── user │ ├── test1_api.h │ ├── test6.h │ ├── test4_pthread.h │ ├── bench.h │ ├── test0.h │ ├── arch │ │ ├── riscv │ │ │ ├── tests.h │ │ │ ├── test2_ecall.h │ │ │ ├── tests.c │ │ │ ├── test2_ecall.S │ │ │ └── test2.c │ │ └── x86_64 │ │ │ ├── tests.h │ │ │ ├── test2_ecall.h │ │ │ ├── test2.c │ │ │ ├── tests.c │ │ │ └── test2_ecall.S │ ├── test3_ecall.h │ ├── test_ecalls.S │ ├── test5.h │ ├── test_ecalls.h │ ├── test3.c │ ├── test6.c │ ├── test4_pthread.c │ ├── test0.c │ ├── bench.c │ ├── test1_api.c │ └── test5.c ├── rename_pk.lst ├── strip_linker.awk ├── patch_ld.sh ├── gdbscreen.sh ├── linker_common.ld ├── main.c └── Makefile ├── syscall_hook ├── .gitignore ├── Makefile ├── sysfilter.h ├── test.c └── sysfilter.c ├── .gitmodules ├── sample_xml ├── test.xml ├── Makefile └── main.cc ├── LICENSE └── README.md /DonkyLib/pk/arch/riscv/pku_handler.S: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/riscv/pku_wrapper.hpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DonkyLib/user/test1_api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test1_api(void); 4 | -------------------------------------------------------------------------------- /DonkyLib/user/test6.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test6_signals(void); 4 | -------------------------------------------------------------------------------- /DonkyLib/user/test4_pthread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test4_pthread(void); 4 | -------------------------------------------------------------------------------- /syscall_hook/.gitignore: -------------------------------------------------------------------------------- 1 | *.order 2 | *.symvers 3 | *.ko* 4 | *.mod* 5 | *.o* 6 | test 7 | -------------------------------------------------------------------------------- /DonkyLib/user/bench.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void timing_results(void); 4 | void bench(void); 5 | void bench_preinit(void); 6 | -------------------------------------------------------------------------------- /DonkyLib/user/test0.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test0(void); 4 | 5 | extern void ecall_test0_child(volatile uintptr_t* mem, int allowed); 6 | 7 | extern int ecall_register_test0_child(int did); 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ariane-sdk"] 2 | path = ariane-sdk 3 | url = git@github.com:davidschrammel/ariane-sdk.git 4 | branch = donky 5 | [submodule "cva6"] 6 | path = cva6 7 | url = git@github.com:davidschrammel/cva6.git 8 | branch = donky 9 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/riscv/tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef __ASSEMBLY__ 4 | 5 | #include 6 | 7 | void test2(void); 8 | void ecall_register_test2(int dom); 9 | 10 | #else // __ASSEMBLY__ 11 | 12 | 13 | 14 | #endif // __ASSEMBLY__ 15 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/x86_64/tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef __ASSEMBLY__ 4 | 5 | #include 6 | 7 | void test2(void); 8 | void ecall_register_test2(int dom); 9 | 10 | #else // __ASSEMBLY__ 11 | 12 | 13 | 14 | #endif // __ASSEMBLY__ 15 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/x86_64/pku_handler.S: -------------------------------------------------------------------------------- 1 | #define __ASSEMBLY__ 2 | #include "pk_internal.h" 3 | #include "pku_api_wrapper.S" 4 | 5 | # ------------------------------------------------------------------------------ 6 | .section .text 7 | # ------------------------------------------------------------------------------ 8 | # WARNING CODE BELOW is not protected! (outside of the pk section) 9 | -------------------------------------------------------------------------------- /sample_xml/test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 1 name 5 | 1 6 | 7 | 8 | Test 2 name 9 | 2 10 | 11 | 12 | Test 3 name 13 | 3 14 | 15 | 16 | -------------------------------------------------------------------------------- /DonkyLib/rename_pk.lst: -------------------------------------------------------------------------------- 1 | # Specify all libc symbols the pk handler shall intercept. 2 | sigaction pk_sigaction 3 | signal pk_signal 4 | mmap pk_mmap 5 | munmap pk_munmap 6 | mprotect pk_mprotect 7 | pkey_alloc pk_pkey_alloc 8 | pkey_free pk_pkey_free 9 | pkey_mprotect pk_pkey_mprotect 10 | pthread_create pk_pthread_create 11 | #pthread_exit pk_pthread_exit 12 | #malloc pk_malloc 13 | -------------------------------------------------------------------------------- /DonkyLib/user/test3_ecall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "test_ecalls.h" 4 | 5 | #ifndef __ASSEMBLY__ 6 | 7 | #include 8 | #include 9 | 10 | extern void ecall_test3(void); 11 | extern int ecall_register_test3(int did); 12 | 13 | extern int ecall_test3_nested(int arg); 14 | extern int ecall_register_test3_nested(int did); 15 | 16 | extern uint64_t ecall_test3_time(); 17 | extern int ecall_register_test3_time(int did); 18 | 19 | #endif // __ASSEMBLY__ 20 | -------------------------------------------------------------------------------- /DonkyLib/strip_linker.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | HEADER=1 3 | } 4 | 5 | /* Strip header (footer) */ 6 | /===/ { 7 | HEADER=0; 8 | next 9 | } 10 | 11 | /* Strip SEARCH_DIR */ 12 | !HEADER && /SEARCH_DIR/ { 13 | next 14 | } 15 | 16 | /* Insert linker_common.ld before .text segment */ 17 | !HEADER && /^\s*\.text\s*:/ { 18 | print "" 19 | print "INCLUDE linker_common.ld" 20 | print "" 21 | print $0 22 | next 23 | } 24 | 25 | /* Print rest unmodified */ 26 | !HEADER && /.*/ { 27 | print $0 28 | } 29 | -------------------------------------------------------------------------------- /DonkyLib/user/test_ecalls.S: -------------------------------------------------------------------------------- 1 | #define __ASSEMBLY__ 2 | #include "pk.h" 3 | #include "test_ecalls.h" 4 | 5 | GEN_ALL_SIMPLE test0_child ECALL_TEST0_CHILD 6 | GEN_ALL_SIMPLE test3 ECALL_TEST3 7 | GEN_ALL_SIMPLE test3_nested ECALL_TEST3_NESTED 8 | GEN_ALL_SIMPLE test3_time ECALL_TEST3_TIME 9 | GEN_ALL_SIMPLE pkey_isolation_child_alloc ECALL_PKEY_ISOLATION_CHILD_ALLOC 10 | GEN_ALL_SIMPLE pkey_isolation_child_stack ECALL_PKEY_ISOLATION_CHILD_STACK 11 | GEN_ALL_SIMPLE pkey_isolation_child_success ECALL_PKEY_ISOLATION_CHILD_SUCCESS 12 | GEN_ALL_SIMPLE pkey_isolation_child_fail ECALL_PKEY_ISOLATION_CHILD_FAIL 13 | -------------------------------------------------------------------------------- /DonkyLib/user/test5.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_missing_key_exception(void); 4 | void test_pkey_isolation(void); 5 | 6 | extern uintptr_t* ecall_pkey_isolation_child_alloc(); 7 | extern uintptr_t* ecall_pkey_isolation_child_stack(); 8 | extern void ecall_pkey_isolation_child_success(volatile uintptr_t* mem, int shared_pkey); 9 | extern void ecall_pkey_isolation_child_fail(volatile uintptr_t* mem, int shared_pkey); 10 | 11 | extern int ecall_register_pkey_isolation_child_alloc(int did); 12 | extern int ecall_register_pkey_isolation_child_stack(int did); 13 | extern int ecall_register_pkey_isolation_child_success(int did); 14 | extern int ecall_register_pkey_isolation_child_fail(int did); 15 | -------------------------------------------------------------------------------- /DonkyLib/user/test_ecalls.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define ECALL_TEST3 9 4 | #define ECALL_TEST3_NESTED 8 5 | #define ECALL_TEST3_TIME 7 6 | 7 | #define ECALL_TEST_ARGS_ID 10 8 | #define ECALL_TEST_API_ID 11 9 | #define ECALL_TEST_KILL_ALL_REGS_ID 12 10 | #define ECALL_TEST2_NESTED 13 11 | 12 | #define ECALL_PKEY_ISOLATION_CHILD_ALLOC 20 13 | #define ECALL_PKEY_ISOLATION_CHILD_STACK 21 14 | #define ECALL_PKEY_ISOLATION_CHILD_SUCCESS 22 15 | #define ECALL_PKEY_ISOLATION_CHILD_FAIL 23 16 | 17 | #define ECALL_TEST0_CHILD 24 18 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/riscv/test2_ecall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "test_ecalls.h" 4 | 5 | #ifndef __ASSEMBLY__ 6 | 7 | #include 8 | 9 | extern void ecall_register_test_args(int did); 10 | extern void ecall_register_test_api_calls(int did); 11 | extern void ecall_register_test_kill_all_regs(int did); 12 | extern int ecall_register_test2_nested(int arg); 13 | 14 | extern uint64_t ecall_test_args(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f); 15 | extern void ecall_test_api_calls(void); 16 | extern int ecall_test_kill_all_regs(void); 17 | extern int ecall_test2_nested(int); 18 | 19 | extern void ecall_save_frame_prepare(); 20 | extern void ecall_save_frame_overhead(); 21 | #endif // __ASSEMBLY__ 22 | -------------------------------------------------------------------------------- /DonkyLib/patch_ld.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ ! -f "$1" ]]; then 4 | echo "File $1 does not exist" 5 | exit 1 6 | fi 7 | 8 | set -e 9 | echo "Patching (1/3) $1" 10 | grep "===" "$1" && awk -f strip_linker.awk "$1" > "$1.patched" || cp "$1" "$1.patched" 11 | 12 | # sed options: 13 | # -i ... in-place 14 | # -z ... multi-line matching 15 | # 16 | # Insert __tls_static_start at beginning of .tdata 17 | echo "Patching (2/3) $1" 18 | sed -i -zre "s/(\s*\.tdata\s*:\s*\{)/\1\n __tls_static_start = .;\n/" "$1.patched" 19 | 20 | # Insert __tls_static_end at end of .tbss 21 | echo "Patching (3/3) $1" 22 | 23 | sed -i -zre "s/(\s*\.tbss\s*:\s*\{)([^\}]*)\}/\1\n \2\n __tls_static_end = .;\n \}/" "$1.patched" 24 | mv "$1.patched" "$1" 25 | rm -f $1.patched 26 | -------------------------------------------------------------------------------- /sample_xml/Makefile: -------------------------------------------------------------------------------- 1 | SIM?= 2 | PK=../DonkyLib 3 | ARCH=x86_64 4 | CXXFLAGS=-std=c++11 -I$(PK) -I$(PK)/pk -I$(PK)/pk/arch/$(ARCH) -g 5 | export LD_LIBRARY_PATH=$(abspath $(PK)) 6 | 7 | PKARGS=PLATFORM=$(ARCH) SIM=$(SIM) SHARED=1 PRELOAD=1 RELEASE=1 8 | 9 | all: main 10 | 11 | $(PK)/libpku.so: 12 | make -C $(PK) $(PKARGS) libpku.so 13 | 14 | $(PK)/libpk.so: 15 | make -C $(PK) $(PKARGS) libpk.so 16 | 17 | main.o: main.cc 18 | $(CXX) $(CXXFLAGS) -c $^ -o $@ 19 | 20 | main: main.o tinyxml2.o | $(PK)/libpku.so 21 | $(CXX) -o $@ $(CXXFLAGS) $^ -L$(PK) -lpku -lpk -lpthread 22 | 23 | tinyxml2.o: tinyxml2.cpp 24 | $(CXX) -c -o $@ $(CXXFLAGS) $^ 25 | 26 | clean: 27 | make -C $(PK) $(PKARGS) clean 28 | rm -f main main.o tinyxml2.o 29 | 30 | run: main 31 | LD_LIBRARY_PATH=$(abspath $(PK)) ./main 32 | -------------------------------------------------------------------------------- /syscall_hook/Makefile: -------------------------------------------------------------------------------- 1 | obj-m += sysfilter.o 2 | ccflags-y += -Wno-unused-result 3 | all: test sysfilter.ko 4 | 5 | sysfilter.ko: sysfilter.c 6 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 7 | 8 | test: test.c 9 | gcc test.c -o test 10 | 11 | clean: 12 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 13 | rm -f test 14 | 15 | load: sysfilter.ko 16 | @if ( ! test -e /dev/sysfilter ); then sudo insmod sysfilter.ko; else echo "Module already loaded"; fi 17 | @if ( ! lsmod | grep sysfilter ); then echo "Unable to load module!"; exit 1; else echo "Module loaded"; fi 18 | 19 | unload: 20 | @if ( test -e /dev/sysfilter ); then sudo rmmod sysfilter.ko; else echo "No module loaded"; fi 21 | @if ( lsmod | grep sysfilter ); then echo "Unable to unload module!"; exit 1; else echo "Module unloaded"; fi 22 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/x86_64/test2_ecall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "test_ecalls.h" 4 | 5 | /**********************************************************************/ 6 | #ifndef __ASSEMBLY__ 7 | /**********************************************************************/ 8 | #include 9 | 10 | extern int ecall_register_test_args(int did); 11 | extern int ecall_register_test_api_calls(int did); 12 | extern int ecall_register_test_kill_all_regs(int did); 13 | extern int ecall_register_test2_nested(int arg); 14 | 15 | extern uint64_t ecall_test_args(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f); 16 | extern void ecall_test_api_calls(void); 17 | extern int ecall_test_kill_all_regs(void); 18 | extern int ecall_test2_nested(int); 19 | 20 | extern void ecall_save_frame_prepare(); 21 | extern void ecall_save_frame_overhead(); 22 | #endif // __ASSEMBLY__ 23 | -------------------------------------------------------------------------------- /DonkyLib/pk/sysfilter.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for license and copyright information */ 2 | 3 | #ifndef SYSFILTER_MODULE_H 4 | #define SYSFILTER_MODULE_H 5 | 6 | #include 7 | 8 | #define SYSFILTER_DEVICE_NAME "sysfilter" 9 | #define SYSFILTER_DEVICE_PATH "/dev/" SYSFILTER_DEVICE_NAME 10 | 11 | #define SYSFILTER_IOCTL_MAGIC_NUMBER (long)0x1248 12 | 13 | #define SYSFILTER_IOCTL_CMD_BLOCK \ 14 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 1, size_t) 15 | 16 | #define SYSFILTER_IOCTL_CMD_PID \ 17 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 2, size_t) 18 | 19 | #define SYSFILTER_IOCTL_CMD_UNBLOCK \ 20 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 3, size_t) 21 | 22 | #define SYSFILTER_IOCTL_CMD_WRITEKEY \ 23 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 4, size_t) 24 | 25 | #define SYSFILTER_IOCTL_CMD_KILL_ON_VIOLATION \ 26 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 5, size_t) 27 | 28 | #endif // SYSFILTER_MODULE_H 29 | -------------------------------------------------------------------------------- /syscall_hook/sysfilter.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for license and copyright information */ 2 | 3 | #ifndef SYSFILTER_MODULE_H 4 | #define SYSFILTER_MODULE_H 5 | 6 | #include 7 | 8 | #define SYSFILTER_DEVICE_NAME "sysfilter" 9 | #define SYSFILTER_DEVICE_PATH "/dev/" SYSFILTER_DEVICE_NAME 10 | 11 | #define SYSFILTER_IOCTL_MAGIC_NUMBER (long)0x1248 12 | 13 | #define SYSFILTER_IOCTL_CMD_BLOCK \ 14 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 1, size_t) 15 | 16 | #define SYSFILTER_IOCTL_CMD_PID \ 17 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 2, size_t) 18 | 19 | #define SYSFILTER_IOCTL_CMD_UNBLOCK \ 20 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 3, size_t) 21 | 22 | #define SYSFILTER_IOCTL_CMD_WRITEKEY \ 23 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 4, size_t) 24 | 25 | #define SYSFILTER_IOCTL_CMD_KILL_ON_VIOLATION \ 26 | _IOR(SYSFILTER_IOCTL_MAGIC_NUMBER, 5, size_t) 27 | 28 | #endif // SYSFILTER_MODULE_H 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Graz University of Technology 2 | Copyright (c) 2020 David Schrammel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the ""Software""), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /DonkyLib/pk/mprotect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /**********************************************************************/ 4 | // For C only 5 | #ifndef __ASSEMBLY__ 6 | /**********************************************************************/ 7 | 8 | #include // pkey_alloc, pkey_free, mprotect, pkey_mprotect 9 | 10 | #ifndef _GNU_SOURCE 11 | #define _GNU_SOURCE 1 12 | #endif 13 | 14 | #include 15 | #include 16 | 17 | int pkey_alloc(unsigned int flags, unsigned int access_rights); 18 | int pkey_free(int pkey); 19 | int pkey_mprotect(void *addr, size_t len, int prot, int pkey); 20 | 21 | #ifndef PKEY_DISABLE_ACCESS 22 | #define PKEY_DISABLE_ACCESS (0x1) 23 | #endif 24 | 25 | #ifndef PKEY_DISABLE_WRITE 26 | #define PKEY_DISABLE_WRITE (0x2) 27 | #endif 28 | 29 | #ifndef SYS_pkey_mprotect 30 | #if __x86_64__ 31 | #define SYS_pkey_mprotect 329 32 | #else 33 | #define SYS_pkey_mprotect 288 34 | #endif 35 | #endif 36 | 37 | #ifndef SYS_pkey_alloc 38 | 39 | #if __x86_64__ 40 | #define SYS_pkey_alloc 330 41 | #else 42 | #define SYS_pkey_alloc 289 43 | #endif 44 | #endif 45 | 46 | 47 | #ifndef SYS_pkey_free 48 | #if __x86_64__ 49 | #define SYS_pkey_free 331 50 | #else 51 | #define SYS_pkey_free 290 52 | #endif 53 | #endif 54 | 55 | #endif // __ASSEMBLY__ 56 | -------------------------------------------------------------------------------- /DonkyLib/pk/pk_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Max. number of foreign domains that can invoke current domain 4 | #define NUM_SOURCE_DOMAINS 16 5 | 6 | // Max. number of keys a domain can hold 7 | #define NUM_KEYS_PER_DOMAIN 2048 8 | 9 | // Max. number of domains 10 | #define NUM_DOMAINS 256 11 | #define NUM_THREADS 256 12 | 13 | // Max. number of distinct contiguous memory regions pk can track 14 | #define NUM_MPROTECT_RANGES 4096 15 | 16 | #ifndef __ASSEMBLY__ 17 | 18 | // Internal PK code 19 | #define PK_CODE __attribute__((section(".pk"),used)) 20 | // Internal PK data 21 | #define PK_DATA __attribute__((section(".pk_data"),used)) 22 | // PK code/data that is exported via shared library 23 | #define PK_API __attribute__ ((visibility ("default"))) 24 | 25 | // Some functions are used in both, trusted and untrusted code. 26 | // FORCE_INLINE will inline them in the corresponding sections. 27 | // 28 | // If the compiler for some reason decides not to inline, the function 29 | // will be placed in a dead section, and the linker will fail 30 | #define FORCE_INLINE __attribute__((always_inline)) __attribute__((section(".deadcode"))) static inline 31 | 32 | #endif /* __ASSEMBLY__ */ 33 | 34 | #define likely(x) __builtin_expect(!!(x), 1) 35 | #define unlikely(x) __builtin_expect(!!(x), 0) 36 | 37 | -------------------------------------------------------------------------------- /DonkyLib/gdbscreen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SNAME=gdbsession 4 | TTYFILE=.tty 5 | 6 | screen -S ${SNAME} -X quit 7 | 8 | # Screen session with horizontal separator 9 | IFS='' read -r -d '' SCREEN <<"EOF" 10 | layout new 11 | split -v 12 | screen 0 13 | focus next 14 | screen 1 15 | focus next 16 | detach 17 | EOF 18 | 19 | # GDB arguments 20 | IFS='' read -r -d '' GDB <<"EOF" 21 | handle SIGSEGV pass stop 22 | layout split asm 23 | layout reg 24 | EOF 25 | 26 | GDB="set environment LD_PRELOAD $DO_LD_PRELOAD 27 | ${GDB}" 28 | rm -f ${TTYFILE} 29 | 30 | echo "Creating new screen session" 31 | echo "${SCREEN}" > .gdbscreen 32 | screen -S ${SNAME} -c .gdbscreen 33 | 34 | echo "Determining tty" 35 | screen -S ${SNAME} -p0 -X stuff "echo Hallo\n" 36 | screen -S ${SNAME} -p1 -X stuff "tty > ${TTYFILE}\n" 37 | echo "Waiting for screen" 38 | i=0 39 | while [[ ! -f ${TTYFILE} ]]; do 40 | echo -n "." 41 | sleep 0.1 42 | i=$((i+1)) 43 | if [[ "$i" -gt "10" ]]; then 44 | echo "Timeout!" 45 | screen -S ${SNAME} -X quit 46 | exit 1 47 | fi 48 | done 49 | 50 | echo "Running $*" 51 | echo "tty `cat ${TTYFILE}`" > .gdb 52 | echo "${GDB}" >> .gdb 53 | 54 | screen -S ${SNAME} -p0 -X stuff "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH\n" 55 | screen -S ${SNAME} -p0 -X stuff "$* -x .gdb && screen -S ${SNAME} -X quit\n" 56 | 57 | echo "Attaching" 58 | screen -rS ${SNAME} 59 | -------------------------------------------------------------------------------- /DonkyLib/user/test3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test2_ecall.h" 7 | #include "test3_ecall.h" 8 | #include "pk.h" 9 | #include "pk_debug.h" 10 | 11 | void test3() { 12 | //Reading private data (should fail if not called via ecall_test3) 13 | uint64_t * x = (uint64_t*)&test3; 14 | printf("test3: %lx\n", *x); 15 | 16 | pk_print_current_reg(); 17 | 18 | printf("test3: Calling test2 ecall function:\n"); 19 | uint64_t ret = ecall_test_args(0x10, 0x11, 0x12, 0x13, 0x14, 0x15); 20 | printf("ecall_test_args returned %lx\n", ret); 21 | assert(ret == 0xAABBCCDD00112233ULL); 22 | 23 | printf("test3: Calling test2 ecall function which then calls api functions:\n"); 24 | ecall_test_api_calls(); 25 | } 26 | 27 | int test3_nested(int arg){ 28 | DEBUG_MPK("test3_nested(%d)", arg); 29 | //pk_print_current_reg(); 30 | arg--; 31 | if(arg > 0){ 32 | DEBUG_MPK("test3_nested: Calling ecall_test2_nested(%d)\n", arg); 33 | int ret = ecall_test2_nested(arg); 34 | DEBUG_MPK("test3_nested: Successfully called ecall_test2_nested(%d). return value was %d\n", arg, ret); 35 | assert_ifdebug(ret == arg - 1); 36 | }else{ 37 | #ifndef RELEASE 38 | pk_print_debug_info(); 39 | #endif 40 | } 41 | return arg; 42 | } 43 | 44 | uint64_t test3_time(){ 45 | return RDTSC(); 46 | } 47 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/riscv/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test2_ecall.h" 7 | #include "pk.h" 8 | 9 | //TODO ecall_register_test2 and test2 must be unprotected 10 | 11 | void ecall_register_test2(int dom) { 12 | ecall_register_test_args(dom); 13 | ecall_register_test_api_calls(dom); 14 | ecall_register_test_kill_all_regs(dom); 15 | } 16 | 17 | void test2() { 18 | //---------------------------------------------------------------------------- 19 | // Test if function arguments / return values are passed correctly 20 | printf("Calling ecall_test_args\n"); 21 | uint64_t ret; 22 | 23 | ret = ecall_test_args(0x10, 0x11, 0x12, 0x13, 0x14, 0x15); 24 | 25 | printf("ecall_test_args returned %lx\n", ret); 26 | assert(ret == 0xAABBCCDD00112233ULL); 27 | 28 | //---------------------------------------------------------------------------- 29 | // Test if callee-saved registers are preserved by wrapper in case a 30 | // malicious ecall target does not preserve them 31 | printf("Calling ecall_test_kill_all_regs\n"); 32 | ret = (uint64_t)ecall_test_kill_all_regs(); 33 | assert(ret == 0xF0); 34 | 35 | 36 | //---------------------------------------------------------------------------- 37 | // Test API calls within an ecall 38 | printf("Calling ecall_test_api_calls\n"); 39 | ecall_test_api_calls(); 40 | } 41 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/x86_64/test2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test2_ecall.h" 7 | #include "test3_ecall.h" 8 | #include "pk_debug.h" 9 | 10 | 11 | uint64_t test_args(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f) { 12 | //printf("%lx %lx %lx %lx %lx %lx\n", a, b, c, d, e, f); 13 | assert_ifdebug(a == 0x10); 14 | assert_ifdebug(b == 0x11); 15 | assert_ifdebug(c == 0x12); 16 | assert_ifdebug(d == 0x13); 17 | assert_ifdebug(e == 0x14); 18 | assert_ifdebug(f == 0x15); 19 | return 0xAABBCCDD00112233ULL; 20 | } 21 | 22 | int test2_nested(int arg){ 23 | DEBUG_MPK("test2_nested(%d)\n", arg); 24 | //pk_print_current_reg(); 25 | arg--; 26 | if(arg > 0){ 27 | DEBUG_MPK("test2_nested: Calling test3_nested(%d)\n", arg); 28 | int ret = ecall_test3_nested(arg); 29 | DEBUG_MPK("test2_nested: Successfully called ecall_test3_nested(%d). return value was %d\n", arg, ret); 30 | assert_ifdebug(ret == arg - 1); 31 | }else{ 32 | #ifndef RELEASE 33 | pk_print_debug_info(); 34 | #endif 35 | } 36 | return arg; 37 | } 38 | 39 | void test_api_calls() { 40 | pk_print_debug_info(); 41 | pk_print_current_reg(); 42 | } 43 | 44 | int __attribute__((naked)) test_kill_all_regs() { 45 | asm volatile ( 46 | // Write callee-saved registers 47 | "mov $0xFF, %rbx\n" 48 | "mov $0xEE, %r12\n" 49 | "mov $0xDD, %r13\n" 50 | "mov $0xCC, %r14\n" 51 | "mov $0xBB, %r15\n" 52 | // Return 53 | "mov $0xAA, %rax\n" 54 | "mov $0x99, %rdx\n" 55 | "ret\n" 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/riscv/pku_handler_c.c: -------------------------------------------------------------------------------- 1 | #include "pk_internal.h" 2 | 3 | void* _pk_sa_sigaction_c(int sig, siginfo_t *info, void *ucontext); 4 | __thread unsigned char tls_pku_sigstack[PKU_SIGSTACK_SIZE_BYTES] = {0, }; 5 | 6 | void __attribute__((naked)) _pk_sa_sigaction_asm(/*sig, info, ucontext*/) { 7 | // We're entering here either with the caller stack or the sigaltstack 8 | // In either case, this stack might not be accessible yet since the 9 | // kernel resets pkru to 0x55555554 (only key 0) during signal handling 10 | // We need to switch to a special unprotected signal stack (key 0) before 11 | // calling into C wrapper 12 | __asm__ volatile( 13 | // use callee-saved registers to 14 | "mv s0, a0\n" // save sig 15 | "mv s1, a1\n" // save info 16 | "mv s2, a2\n" // save ucontext 17 | "mv s3, sp\n" // save user stack 18 | "ld sp, %0\n" // load special signal stack 19 | "call _pk_sa_sigaction_c\n" 20 | "mv a3, a0\n" // this is our handler address 21 | "mv a0, s0\n" // restore sig 22 | "mv a1, s1\n" // restore info 23 | "mv a2, s2\n" // restore ucontext 24 | "mv sp, s3\n" // restore user stack 25 | "jr a3\n" // call handler(sig, info, ucontext) 26 | : // no output operands 27 | : "X"(&tls_pku_sigstack[PKU_SIGSTACK_SIZE_BYTES-2*WORDSIZE]) 28 | ); 29 | } 30 | //------------------------------------------------------------------------------ 31 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/x86_64/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test2_ecall.h" 7 | #include "pk.h" 8 | 9 | //TODO not actually an ecall... confusing name 10 | void ecall_register_test2(int dom) { 11 | ecall_register_test_args(dom); 12 | ecall_register_test_api_calls(dom); 13 | ecall_register_test_kill_all_regs(dom); 14 | } 15 | 16 | void test2() { 17 | printf("test2\n"); 18 | 19 | //---------------------------------------------------------------------------- 20 | // Test if function arguments / return values are passed correctly 21 | printf("Calling ecall_test_args\n"); 22 | uint64_t ret; 23 | 24 | ret = ecall_test_args(0x10, 0x11, 0x12, 0x13, 0x14, 0x15); 25 | printf("ecall_test_args returned %lx\n", ret); 26 | assert(ret == 0xAABBCCDD00112233ULL); 27 | 28 | //---------------------------------------------------------------------------- 29 | // Test if callee-saved registers are preserved by wrapper in case a 30 | // malicious ecall target does not preserve them 31 | register int rbx asm("rbx"), r12 asm("r12"), r13 asm("r13"), r14 asm("r14"), r15 asm("r15"); 32 | asm volatile( 33 | "mov $0x11, %%rbx\n" 34 | "mov $0x22, %%r12\n" 35 | "mov $0x33, %%r13\n" 36 | "mov $0x44, %%r14\n" 37 | "mov $0x55, %%r15\n" 38 | "call ecall_test_kill_all_regs\n" 39 | : "=r"(rbx), "=r"(r12), "=r"(r13), "=r"(r14), "=r"(r15)); 40 | assert(rbx == 0x11); 41 | assert(r12 == 0x22); 42 | assert(r13 == 0x33); 43 | assert(r14 == 0x44); 44 | assert(r15 == 0x55); 45 | 46 | //---------------------------------------------------------------------------- 47 | // Test API calls within an ecall 48 | printf("Calling ecall_test_api_calls\n"); 49 | ecall_test_api_calls(); 50 | } 51 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/x86_64/test2_ecall.S: -------------------------------------------------------------------------------- 1 | #define __ASSEMBLY__ 2 | #include "pk.h" 3 | #include "test2_ecall.h" 4 | 5 | GEN_ALL_SIMPLE test_args ECALL_TEST_ARGS_ID 6 | GEN_ALL_SIMPLE test_api_calls ECALL_TEST_API_ID 7 | GEN_ALL_SIMPLE test_kill_all_regs ECALL_TEST_KILL_ALL_REGS_ID 8 | GEN_ALL_SIMPLE test2_nested ECALL_TEST2_NESTED 9 | 10 | # Save custom ecall stack into ecall_save_frame_stack_new 11 | .global ecall_save_frame_prepare 12 | .type ecall_save_frame_prepare @function 13 | ecall_save_frame_prepare: 14 | lea PIC(ecall_save_frame_stack), %r10 15 | add $(1023*WORDSIZE), %r10 16 | mov %r10, PIC(ecall_save_frame_stack_new) 17 | ret 18 | 19 | # Measure the overhead of callee-register-saving 20 | .global ecall_save_frame_overhead 21 | .type ecall_save_frame_overhead @function 22 | ecall_save_frame_overhead: 23 | SAVE_CALLEE_REGS 24 | 25 | # Do stack switching 26 | mov %rsp, PIC(ecall_save_frame_stack_original) 27 | mov PIC(ecall_save_frame_stack_new), %rsp 28 | 29 | # We do not do an actual ecall but just call an empty dummy function 30 | call ecall_save_frame_dummy 31 | 32 | # Do stack switching 33 | mov %rsp, PIC(ecall_save_frame_stack_new) 34 | mov PIC(ecall_save_frame_stack_original), %rsp 35 | 36 | RESTORE_CALLEE_REGS 37 | ret 38 | 39 | ecall_save_frame_dummy: 40 | ret 41 | 42 | .bss 43 | .align WORDSIZE 44 | 45 | .type ecall_save_frame_stack_original, @object 46 | .size ecall_save_frame_stack_original, 8 47 | ecall_save_frame_stack_original: 48 | .skip 8 49 | .type ecall_save_frame_stack_new, @object 50 | .size ecall_save_frame_stack_new, 8 51 | ecall_save_frame_stack_new: 52 | .skip 8 53 | 54 | # bottom of stack 55 | .type ecall_save_frame_stack, @object 56 | .size ecall_save_frame_stack, 1024*WORDSIZE 57 | ecall_save_frame_stack: 58 | .skip 1024*WORDSIZE 59 | -------------------------------------------------------------------------------- /DonkyLib/linker_common.ld: -------------------------------------------------------------------------------- 1 | /* MAGIC SECTIONS */ 2 | /* 3 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 4 | .test1 : 5 | { 6 | _test1_start = .; 7 | *test1.o(*) 8 | _test1_end = .; 9 | } 10 | */ 11 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 12 | .pk_d : 13 | { 14 | __start_pk_all = .; 15 | __start_pk_data = .; 16 | __start_pk_key = .; 17 | *(.pk_key) 18 | __stop_pk_key = .; 19 | *(.pk_data) 20 | /**(.dynbss) 21 | *(.bss .bss.* .gnu.linkonce.b.*) 22 | *(COMMON)*/ 23 | 24 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 25 | __stop_pk_data = .; 26 | } 27 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 28 | .pk_t : 29 | { 30 | __start_pk_code = .; 31 | __start_pk_utvec_table = .; 32 | *(.pk_utvec_table) 33 | __stop_pk_utvec_table = .; 34 | *(.pk) 35 | /* *(*) TODO: Currently all non-pk tagged symbols are in unprotected section (caught by the arch-specific linker script) */ 36 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 37 | __stop_pk_code = .; 38 | __stop_pk_all = .; 39 | } 40 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 41 | _test2_start = .; 42 | .test2t : 43 | { 44 | *test2.o(.text) 45 | /* *test2_ecall.S(*) */ 46 | } 47 | /* . = ALIGN(CONSTANT (MAXPAGESIZE));*/ 48 | .test2d : 49 | { 50 | *test2.o(*) 51 | } 52 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 53 | _test2_end = .; 54 | 55 | 56 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 57 | _test3_start = .; 58 | _test3t_start = .; 59 | .test3t : 60 | { 61 | *test3.o(.text) 62 | /* *test3_ecall.S(*) */ 63 | } 64 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 65 | _test3t_end = .; 66 | _test3d_start = .; 67 | .test3d : 68 | { 69 | *test3.o(*) 70 | } 71 | . = ALIGN(CONSTANT (MAXPAGESIZE)); 72 | _test3d_end = .; 73 | _test3_end = .; 74 | 75 | /* MAGIC SECTIONS END */ 76 | -------------------------------------------------------------------------------- /DonkyLib/user/test6.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "pk.h" 10 | #include "pk_debug.h" 11 | #include "test5.h" 12 | 13 | pkru_config_t test6_pkru_config; 14 | 15 | void test6_segfault_handler(int sig, siginfo_t *si, void *unused) { 16 | printf("test6_segfault_handler"); 17 | assert(SIGSEGV == sig); 18 | 19 | pkru_config_t pkru_config = _read_pkru_reg(); 20 | printf("PKRU segfault handler: %lx\n", PKRU_TO_INT(pkru_config)); 21 | if (PKRU_TO_INT(pkru_config) != PKRU_TO_INT(test6_pkru_config)) { 22 | printf("segfault handler running with wrong PKRU setting\n"); 23 | } 24 | 25 | // Don't forget to unblock the signal 26 | sigset_t sigset; 27 | int ret; 28 | ret = sigemptyset(&sigset); assert(0 == ret); 29 | ret = sigaddset(&sigset, SIGSEGV); assert(0 == ret); 30 | ret = sigprocmask(SIG_UNBLOCK, &sigset, NULL); assert(0 == ret); 31 | } 32 | 33 | void test6_signals() { 34 | #ifndef PROXYKERNEL 35 | // Register custom segfault handler 36 | int ret; 37 | struct sigaction sa; 38 | memset(&sa, 0, sizeof(sa)); 39 | sa.sa_flags = SA_SIGINFO; 40 | sa.sa_sigaction = test6_segfault_handler; 41 | #ifdef DLU_HOOKING 42 | // Pre-loading hooks sigaction to pk_sigaction 43 | ret = sigaction(SIGSEGV, &sa, NULL); 44 | #else 45 | ret = pk_sigaction(SIGSEGV, &sa, NULL); 46 | #endif 47 | assert(0 == ret); 48 | 49 | test6_pkru_config = _read_pkru_reg(); 50 | printf("PKRU test6: %lx\n", PKRU_TO_INT(_read_pkru_reg())); 51 | raise(SIGSEGV); 52 | printf("PKRU test6: %lx\n", PKRU_TO_INT(_read_pkru_reg())); 53 | 54 | // Deregister handler 55 | sa.sa_flags = 0; 56 | sa.sa_handler = SIG_DFL; 57 | ret = pk_sigaction(SIGSEGV, &sa, NULL); 58 | assert(0 == ret); 59 | #endif // PROXYKERNEL 60 | } 61 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/riscv/test2_ecall.S: -------------------------------------------------------------------------------- 1 | #define __ASSEMBLY__ 2 | #include "pk.h" 3 | #include "test2_ecall.h" 4 | 5 | GEN_ALL_SIMPLE test_args ECALL_TEST_ARGS_ID 6 | GEN_ALL_SIMPLE test_api_calls ECALL_TEST_API_ID 7 | GEN_ALL_SIMPLE test_kill_all_regs ECALL_TEST_KILL_ALL_REGS_ID 8 | GEN_ALL_SIMPLE test2_nested ECALL_TEST2_NESTED 9 | 10 | # Save custom ecall stack into ecall_save_frame_stack_new 11 | .global ecall_save_frame_prepare 12 | .type ecall_save_frame_prepare @function 13 | ecall_save_frame_prepare: 14 | #la t0, ecall_save_frame_stack 15 | #li t1, 1023*WORDSIZE # 8184 # (1023*WORDSIZE) 16 | #add t0, t0, t1 17 | #la t1, ecall_save_frame_stack_new 18 | #sd t0, 0(t1) 19 | ret 20 | 21 | # Measure the overhead of callee-register-saving 22 | .global ecall_save_frame_overhead 23 | .type ecall_save_frame_overhead @function 24 | ecall_save_frame_overhead: 25 | SAVE_CALLEE_REGS 26 | 27 | # Do stack switching 28 | 29 | # # simulated csrrw sp, CSR_USCRATCH, sp 30 | mv t0, sp # t0 <- sp 31 | mv sp, t0 # sp <- t0 32 | 33 | csrrw zero, CSR_UEPC, ra # "backing up ra" 34 | 35 | # We do not do an actual ecall but just call an empty dummy function 36 | call ecall_save_frame_dummy 37 | 38 | # # simulated csrrw sp, CSR_USCRATCH, sp 39 | mv t0, sp # t0 <- sp 40 | mv sp, t0 # sp <- t0 41 | 42 | RESTORE_CALLEE_REGS 43 | #ret 44 | uret 45 | 46 | .type ecall_save_frame_dummy @function 47 | ecall_save_frame_dummy: 48 | ret 49 | 50 | .bss 51 | .align WORDSIZE 52 | 53 | .type ecall_save_frame_stack_original, @object 54 | .size ecall_save_frame_stack_original, 8 55 | ecall_save_frame_stack_original: 56 | .skip 8 57 | .type ecall_save_frame_stack_new, @object 58 | .size ecall_save_frame_stack_new, 8 59 | ecall_save_frame_stack_new: 60 | .skip 8 61 | 62 | # bottom of stack 63 | .type ecall_save_frame_stack, @object 64 | .size ecall_save_frame_stack, 1024*WORDSIZE 65 | ecall_save_frame_stack: 66 | .skip 1024*WORDSIZE 67 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/riscv/pk_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if __riscv && __riscv_xlen == 64 && __riscv_atomic 4 | #else 5 | #error "Unsupported platform" 6 | #endif //__riscv ... 7 | 8 | #include "pk_debug.h" 9 | #include "pk_arch.h" 10 | 11 | /**********************************************************************/ 12 | // For C only 13 | #ifndef __ASSEMBLY__ 14 | /**********************************************************************/ 15 | 16 | #include 17 | #include 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | extern void (*pk_utvec_table)(void); 23 | 24 | // internal pku functions 25 | void _pk_sa_sigaction_asm(/*sig, info, ucontext*/); 26 | 27 | // internal arch-specific functions 28 | void PK_CODE _pk_exception_handler(void); 29 | void PK_CODE _pk_exception_handler_end(void); 30 | uint64_t PK_CODE _pk_exception_handler_arch_c(uint64_t data, uint64_t id, uint64_t type); 31 | int PK_CODE _pk_init_arch(); 32 | void PK_CODE _pk_setup_exception_stack_arch(void* exception_handler_stack); 33 | void PK_CODE _pk_setup_exception_handler_arch(); 34 | void PK_CODE _pk_setup_domain_arch(int did, pkey_t pkey); 35 | void PK_CODE _pk_domain_switch_arch(int type, int target_did, pkru_config_t config, void* entry_point, uint64_t* target_stack); 36 | int PK_CODE _pk_domain_load_key_arch(int did, pkey_t pkey, int slot, int perm); 37 | bool PK_CODE _pk_is_key_loaded_arch(pkey_t pkey); 38 | void* PK_CODE _pthread_init_function_asm(void *arg); 39 | 40 | 41 | //debug helpers 42 | void FORCE_INLINE PRINT_UREGS(){ 43 | DEBUG_MPK("uie = %zx", CSRR(CSR_UIE)); 44 | DEBUG_MPK("ustatus = %zx", CSRR(CSR_USTATUS)); 45 | DEBUG_MPK("uepc = %zx", CSRR(CSR_UEPC)); 46 | DEBUG_MPK("ucause = %zx", CSRR(CSR_UCAUSE)); 47 | DEBUG_MPK("utval = %zx", CSRR(CSR_UTVAL)); 48 | DEBUG_MPK("uip = %zx", CSRR(CSR_UIP)); 49 | DEBUG_MPK("uscratch = %zx", CSRR(CSR_USCRATCH)); 50 | } 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | 56 | /**********************************************************************/ 57 | // For ASM only 58 | #elif defined __ASSEMBLY__ 59 | /**********************************************************************/ 60 | 61 | 62 | #endif // defined __ASSEMBLY__ 63 | -------------------------------------------------------------------------------- /DonkyLib/user/arch/riscv/test2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test2_ecall.h" 7 | #include "test3_ecall.h" 8 | #include "pk_debug.h" 9 | 10 | 11 | uint64_t test_args(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f) { 12 | #ifndef RELEASE 13 | printf("%lx %lx %lx %lx %lx %lx\n", a, b, c, d, e, f); 14 | #endif 15 | assert_ifdebug(a == 0x10); 16 | assert_ifdebug(b == 0x11); 17 | assert_ifdebug(c == 0x12); 18 | assert_ifdebug(d == 0x13); 19 | assert_ifdebug(e == 0x14); 20 | assert_ifdebug(f == 0x15); 21 | return 0xAABBCCDD00112233ULL; 22 | } 23 | 24 | int test2_nested(int arg){ 25 | DEBUG_MPK("test2_nested(%d)\n", arg); 26 | //pk_print_current_reg(); 27 | arg--; 28 | if(arg > 0){ 29 | DEBUG_MPK("test2_nested: Calling test3_nested(%d)\n", arg); 30 | int ret = ecall_test3_nested(arg); 31 | DEBUG_MPK("test2_nested: Successfully called ecall_test3_nested(%d). return value was %d\n", arg, ret); 32 | assert_ifdebug(ret == arg - 1); 33 | }else{ 34 | #ifndef RELEASE 35 | pk_print_debug_info(); 36 | #endif 37 | } 38 | return arg; 39 | } 40 | 41 | void __attribute__((naked)) test_api_calls() { 42 | asm volatile ( 43 | "addi sp,sp,-8;" 44 | "sd ra,0(sp);" 45 | "call pk_print_debug_info;" 46 | "call pk_print_current_reg;" 47 | "ld ra,0(sp);" 48 | "addi sp,sp,8;" 49 | "ret;" 50 | ); 51 | } 52 | 53 | int __attribute__((naked)) test_kill_all_regs(){ 54 | asm volatile ( 55 | //TODO overwrite sp and all others (except ra) 56 | //TODO destroy entire stack 57 | "li t0, 0xFF;" 58 | "li t1, 0xFF;" 59 | "li t2, 0xFF;" 60 | "li s1, 0xFF;" 61 | "li a0, 0xF0;" 62 | "li a1, 0xF1;" 63 | "li a2, 0xF2;" 64 | "li a3, 0xFF;" 65 | "li a4, 0xFF;" 66 | "li a5, 0xFF;" 67 | "li a6, 0xFF;" 68 | "li a7, 0xFF;" 69 | "li s2, 0xFF;" 70 | "li s3, 0xFF;" 71 | "li s4, 0xFF;" 72 | "li s5, 0xFF;" 73 | "li s6, 0xFF;" 74 | "li s7, 0xFF;" 75 | "li s8, 0xFF;" 76 | "li s9, 0xFF;" 77 | "li s10, 0xFF;" 78 | "li s11, 0xFF;" 79 | "li t3, 0xFF;" 80 | "li t4, 0xFF;" 81 | "li t5, 0xFF;" 82 | "li t6, 0xFF;" 83 | "ret;" 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/x86_64/pku_handler_c.c: -------------------------------------------------------------------------------- 1 | #include "pk_internal.h" 2 | 3 | #ifdef FAKE_MPK_REGISTER 4 | 5 | // should be thread-local, but since this is for emulation only, we don't care 6 | pkru_config_t PK_API emulated_mpk_reg = {0,}; 7 | 8 | #endif 9 | 10 | void* _pk_sa_sigaction_c(int sig, siginfo_t *info, void *ucontext); 11 | __thread unsigned char __attribute((tls_model("initial-exec"))) tls_pku_sigstack[PKU_SIGSTACK_SIZE_BYTES] = {0, }; 12 | 13 | void __attribute__((naked)) _pk_sa_sigaction_asm(/*sig, info, ucontext*/) { 14 | // We're entering here either with the caller stack or the sigaltstack 15 | // In either case, this stack might not be accessible yet since the 16 | // kernel resets pkru to 0x55555554 (only key 0) during signal handling 17 | // We need to switch to a special unprotected signal stack (key 0) before 18 | // calling into C wrapper 19 | __asm__ volatile( 20 | // use callee-saved registers to 21 | "mov %%rdi, %%r12\n" // save sig 22 | "mov %%rsi, %%r13\n" // save info 23 | "mov %%rdx, %%r14\n" // save ucontext 24 | "mov %%rsp, %%r15\n" // save user stack 25 | 26 | "xor %%rax, %%rax \n" 27 | "xor %%rcx, %%rcx \n" 28 | "xor %%rdx, %%rdx \n" 29 | "wrpkru \n" // Give full access rights 30 | // (quick-fix for making signal handlers work) 31 | :: 32 | ); 33 | __asm__ volatile( 34 | "mov %0, %%rsp\n" // load signal stack. The tls access 35 | // destroys rdi, rsi, rdx, so 36 | 37 | "mov %%r12, %%rdi \n" // restore sig 38 | "mov %%r13, %%rsi \n" // restore info 39 | "mov %%r14, %%rdx \n" // restore ucontext 40 | "call _pk_sa_sigaction_c\n" // call _pk_sa_sigaction_c(sig, info, ucontext) 41 | 42 | "mov %%r12, %%rdi \n" // Again, restore sig 43 | "mov %%r13, %%rsi \n" // restore info 44 | "mov %%r14, %%rdx \n" // restore ucontext 45 | "mov %%r15, %%rsp \n" // restore user stack 46 | "jmpq *%%rax\n" // call handler(sig, info, ucontext) 47 | : // no output operands 48 | : "X"(&tls_pku_sigstack[PKU_SIGSTACK_SIZE_BYTES-2*WORDSIZE]) 49 | ); 50 | } 51 | //------------------------------------------------------------------------------ 52 | -------------------------------------------------------------------------------- /syscall_hook/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "sysfilter.h" 10 | 11 | char __attribute__((aligned(4096))) dummy[4096]; 12 | int sysfilter_fd = -1; 13 | 14 | void test() { 15 | char res = 0; 16 | *(char volatile*)dummy; 17 | printf("[~] Executing mincore(%p, 4096, %p)\n", dummy, &res); 18 | int ret = mincore(dummy, 4096, &res); 19 | // printf("PID: %d\nResult: %d, Cache: %d\n", getpid(), ret, res); 20 | if(res) printf("[+] Mincore not blocked\n"); 21 | else printf("[-] Mincore blocked\n"); 22 | } 23 | 24 | void write_pkru(int key) { 25 | if (1) { 26 | printf("[~] Writing protection key 0x%x\n", key); 27 | __asm__ volatile( 28 | "xor %%ecx, %%ecx\n" // clear ecx 29 | "xor %%edx, %%edx\n" // clear edx 30 | "wrpkru" 31 | : /* no outputs */ 32 | : "a"(key) 33 | : "rcx", "rdx" 34 | ); 35 | } else { 36 | printf("[~] Simluate writing protection key 0x%x\n", key); 37 | ioctl(sysfilter_fd, SYSFILTER_IOCTL_CMD_WRITEKEY, key); 38 | } 39 | } 40 | 41 | int main() { 42 | memset(dummy, 1, sizeof(dummy)); 43 | 44 | sysfilter_fd = open(SYSFILTER_DEVICE_PATH, O_RDONLY); 45 | if (sysfilter_fd < 0) { 46 | fprintf(stderr, "[-] Error: Could not open Sysfilter device: %s\n", SYSFILTER_DEVICE_PATH); 47 | return -1; 48 | } 49 | 50 | printf("[~] Apply filter to current PID: %d\n", getpid()); 51 | ioctl(sysfilter_fd, SYSFILTER_IOCTL_CMD_PID, getpid()); 52 | 53 | printf("[~] Block mincore syscall (syscall number 27)\n"); 54 | ioctl(sysfilter_fd, SYSFILTER_IOCTL_CMD_BLOCK, 27); 55 | 56 | test(); 57 | 58 | write_pkru(0xF0000000); 59 | 60 | printf("[~] Syscall should be blocked, nothing more happens\n"); 61 | test(); 62 | 63 | printf("[~] Resetting protection key\n"); 64 | write_pkru(0); 65 | test(); 66 | 67 | printf("[~] Simluate writing protection key 1 and kill on violation\n"); 68 | write_pkru(0xF0000000); 69 | ioctl(sysfilter_fd, SYSFILTER_IOCTL_CMD_KILL_ON_VIOLATION, 1); 70 | test(); 71 | 72 | printf("[~] Unblock mincore syscall\n"); 73 | ioctl(sysfilter_fd, SYSFILTER_IOCTL_CMD_UNBLOCK, 27); 74 | printf("[~] Remove PID filter\n"); 75 | ioctl(sysfilter_fd, SYSFILTER_IOCTL_CMD_PID, 0); 76 | 77 | close(sysfilter_fd); 78 | } 79 | -------------------------------------------------------------------------------- /DonkyLib/pk/mprotect.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 1 7 | #endif 8 | #include 9 | #include 10 | #include "pk_internal.h" 11 | #include "mprotect.h" 12 | #include "pk.h" 13 | 14 | #ifdef FAKE_PKEY_SYSCALLS 15 | 16 | // The first protection key is reserved 17 | unsigned char pkey_alloced[PK_NUM_KEYS] = {1,0,}; 18 | 19 | int PK_API pkey_alloc(unsigned int flags, unsigned int access_rights) { 20 | WARNING("Syscall: Fake pkey_alloc(%u, %u)", flags, access_rights); 21 | if (flags || access_rights & ~(PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE)) { 22 | errno = EINVAL; 23 | return -1; 24 | } 25 | for (size_t i = 0; i < PK_NUM_KEYS; i++) { 26 | if (!pkey_alloced[i]) { 27 | pkey_alloced[i] = 1; 28 | return i; 29 | } 30 | } 31 | WARNING("Syscall: Fake pkey_alloc ran out of keys"); 32 | errno = ENOSPC; 33 | return -1; 34 | } 35 | 36 | int PK_API pkey_free(int pkey) { 37 | WARNING("Syscall: Fake pkey_free(%d)", pkey); 38 | if (pkey <= 0 || pkey >= PK_NUM_KEYS) { 39 | // key 0 cannot be free'd 40 | WARNING("pkey_free: invalid key"); 41 | errno = EINVAL; 42 | return -1; 43 | } 44 | pkey_alloced[pkey] = 0; 45 | return 0; 46 | } 47 | 48 | int PK_API pkey_mprotect(void *addr, size_t len, int prot, int pkey) { 49 | WARNING("Syscall: Fake pkey_mprotect(%p, %zu, %d, %d)", addr, len, prot, pkey); 50 | if (pkey >= 0 && pkey < PK_NUM_KEYS && pkey_alloced[pkey]) { 51 | return syscall(SYS_mprotect, addr, len, prot); 52 | } else { 53 | WARNING("pkey_mprotect: invalid key"); 54 | errno = EINVAL; 55 | return -1; 56 | } 57 | } 58 | 59 | #else // FAKE_PKEY_SYSCALLS 60 | 61 | int PK_API pkey_mprotect(void *ptr, size_t len, int prot, int pkey) 62 | { 63 | DEBUG_MPK("Syscall: pkey_mprotect(%p, %zu, %d, %d)", ptr, len, prot, pkey); 64 | return syscall(SYS_pkey_mprotect, ptr, len, prot, pkey); 65 | } 66 | 67 | int PK_API pkey_alloc(unsigned int flags, unsigned int access_rights) 68 | { 69 | DEBUG_MPK("Syscall: pkey_alloc(%u, %u)", flags, access_rights); 70 | int ret = syscall(SYS_pkey_alloc, flags, access_rights); 71 | if (-1 == ret) { 72 | errno = ENOSPC; // just guessing 73 | } 74 | DEBUG_MPK("Syscall: pkey_alloc(%u, %u). ret = %d", flags, access_rights, ret); 75 | return ret; 76 | } 77 | 78 | int PK_API pkey_free(int pkey) 79 | { 80 | DEBUG_MPK("Syscall: pkey_free(%d)", pkey); 81 | int ret = syscall(SYS_pkey_free, pkey); 82 | if (-1 == ret) { 83 | errno = EINVAL; 84 | } 85 | return ret; 86 | } 87 | 88 | #endif // FAKE_PKEY_SYSCALLS 89 | 90 | -------------------------------------------------------------------------------- /DonkyLib/pk/pku_api_wrapper.S: -------------------------------------------------------------------------------- 1 | #define __ASSEMBLY__ 2 | #include "pk_internal.h" 3 | 4 | .section .text 5 | .global pk_exception_handler # Externally visible 6 | .type pk_exception_handler, @function 7 | 8 | // Auto-generate wrappers for PK API calls 9 | // NOTE: CODE BELOW is not protected! (outside of the pk section) 10 | // Functions that override (e.g., preload) libc/pthread are generated via 11 | // GEN_CALL_WRAPPER_API_FALLBACK. This creates a fallback to the native library 12 | // function in case pk is not initialized yet 13 | 14 | GEN_CALL_WRAPPER_API pk_domain_create _API_pk_domain_create 15 | GEN_CALL_WRAPPER_API pk_domain_free _API_pk_domain_free 16 | GEN_CALL_WRAPPER_API pk_domain_release_child _API_pk_domain_release_child 17 | GEN_CALL_WRAPPER_API_FALLBACK pkey_alloc _API_pk_pkey_alloc 18 | GEN_CALL_WRAPPER_API_FALLBACK pkey_free _API_pk_pkey_free 19 | GEN_CALL_WRAPPER_API_FALLBACK pkey_mprotect _API_pk_pkey_mprotect 20 | GEN_CALL_WRAPPER_API pk_pkey_mprotect2 _API_pk_pkey_mprotect2 21 | GEN_CALL_WRAPPER_API_FALLBACK malloc _API_pk_malloc 22 | GEN_CALL_WRAPPER_API_FALLBACK mmap _API_pk_mmap 23 | GEN_CALL_WRAPPER_API pk_mmap2 _API_pk_mmap2 24 | GEN_CALL_WRAPPER_API pk_mmap3 _API_pk_mmap3 25 | GEN_CALL_WRAPPER_API_FALLBACK munmap _API_pk_munmap 26 | GEN_CALL_WRAPPER_API pk_munmap2 _API_pk_munmap2 27 | GEN_CALL_WRAPPER_API_FALLBACK mprotect _API_pk_mprotect 28 | GEN_CALL_WRAPPER_API pk_mprotect2 _API_pk_mprotect2 29 | GEN_CALL_WRAPPER_API pk_domain_register_ecall _API_pk_domain_register_ecall 30 | GEN_CALL_WRAPPER_API pk_domain_register_ecall2 _API_pk_domain_register_ecall2 31 | GEN_CALL_WRAPPER_API pk_domain_allow_caller _API_pk_domain_allow_caller 32 | GEN_CALL_WRAPPER_API pk_domain_allow_caller2 _API_pk_domain_allow_caller2 33 | GEN_CALL_WRAPPER_API pk_domain_assign_pkey _API_pk_domain_assign_pkey 34 | GEN_CALL_WRAPPER_API pk_domain_default_key _API_pk_domain_default_key 35 | GEN_CALL_WRAPPER_API pk_simple_api_call _API_pk_simple_api_call 36 | GEN_CALL_WRAPPER_API pk_print_debug_info _API_pk_print_debug_info 37 | GEN_CALL_WRAPPER_API pk_current_did _API_pk_current_did 38 | GEN_CALL_WRAPPER_API pk_domain_load_key _API_pk_domain_load_key 39 | GEN_CALL_WRAPPER_API pk_deinit _API_pk_deinit 40 | GEN_CALL_WRAPPER_API_FALLBACK pthread_create _API_pk_pthread_create 41 | GEN_CALL_WRAPPER_API_FALLBACK pthread_exit _API_pk_pthread_exit 42 | GEN_CALL_WRAPPER_API pk_register_exception_handler _API_pk_register_exception_handler 43 | -------------------------------------------------------------------------------- /DonkyLib/user/test4_pthread.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "pk.h" 10 | 11 | #define PATTERN1 0xAFFEAFFE 12 | #define PATTERN2 0xDEADBEEF 13 | #define PATTERN3 0xC0FFFFEE 14 | 15 | void* pthread1(void* arg) { 16 | #if __riscv 17 | //TODO: warning: optimization may eliminate reads and/or writes to register variables [-Wvolatile-register-var] 18 | volatile uint64_t something = 0; 19 | volatile register uint64_t ra asm("ra"); 20 | something = ra; 21 | #endif 22 | DEBUG_MPK("I am thread 1: %p\n", arg); 23 | #if __riscv 24 | DEBUG_MPK("pthread1 ra = 0x%zx", something); 25 | DEBUG_MPK("pthread1 umpk = 0x%zx", CSRR(CSR_MPK)); 26 | DEBUG_MPK("pthread1 utvec = 0x%zx", CSRR(CSR_UTVEC)); 27 | DEBUG_MPK("pthread1 uscratch = 0x%zx", CSRR(CSR_USCRATCH)); 28 | #endif 29 | uintptr_t var = (uintptr_t)arg; 30 | for (size_t i = 0; i < 1000; i++) { 31 | var++; 32 | sched_yield(); 33 | } 34 | DEBUG_MPK("thread1 returning %lx", var); 35 | //pthread_exit((void*)var); 36 | return (void*)var; // same as pthread_exit 37 | } 38 | 39 | void* pthread2(void* arg) { 40 | DEBUG_MPK("I am thread 2: %p\n", arg); 41 | #if __riscv 42 | DEBUG_MPK("pthread2 umpk = 0x%zx", CSRR(CSR_MPK)); 43 | DEBUG_MPK("pthread2 utvec = 0x%zx", CSRR(CSR_UTVEC)); 44 | DEBUG_MPK("pthread2 uscratch = 0x%zx", CSRR(CSR_USCRATCH)); 45 | #endif 46 | uintptr_t var = (uintptr_t)arg; 47 | for (size_t i = 0; i < 1000; i++) { 48 | var+=7; 49 | sched_yield(); 50 | } 51 | DEBUG_MPK("thread2 returning %lx", var); 52 | //pthread_exit((void*)var); 53 | return (void*)var; 54 | } 55 | 56 | void* pthread3(void* arg) { 57 | DEBUG_MPK("I am thread 3: %p\n", arg); 58 | #if __riscv 59 | DEBUG_MPK("pthread3 umpk = 0x%zx", CSRR(CSR_MPK)); 60 | DEBUG_MPK("pthread3 utvec = 0x%zx", CSRR(CSR_UTVEC)); 61 | DEBUG_MPK("pthread3 uscratch = 0x%zx", CSRR(CSR_USCRATCH)); 62 | #endif 63 | uintptr_t var = (uintptr_t)arg; 64 | for (size_t i = 0; i < 1000; i++) { 65 | var+=5; 66 | sched_yield(); 67 | } 68 | DEBUG_MPK("thread3 returning %lx", var); 69 | //pthread_exit((void*)var); 70 | return (void*)var; 71 | } 72 | 73 | void test4_pthread() { 74 | int ret; 75 | DEBUG_MPK("TEST4"); 76 | pthread_t thread1, thread2, thread3; 77 | ret = pk_pthread_create(&thread1, NULL, pthread1, (void*)PATTERN1); 78 | assert(ret == 0); 79 | ret = pk_pthread_create(&thread2, NULL, pthread2, (void*)PATTERN2); 80 | assert(ret == 0); 81 | ret = pk_pthread_create(&thread3, NULL, pthread3, (void*)PATTERN3); 82 | assert(ret == 0); 83 | DEBUG_MPK("Main waiting for other thread"); 84 | 85 | #if __riscv 86 | DEBUG_MPK("main umpk = 0x%zx", CSRR(CSR_MPK)); 87 | DEBUG_MPK("main utvec = 0x%zx", CSRR(CSR_UTVEC)); 88 | DEBUG_MPK("main uscratch = 0x%zx", CSRR(CSR_USCRATCH)); 89 | #endif 90 | 91 | uintptr_t retval; 92 | ret = pthread_join(thread3, (void*)&retval); 93 | assert(ret == 0); 94 | DEBUG_MPK("retval = %lx", retval); 95 | assert(retval == PATTERN3+1000*5); 96 | 97 | ret = pthread_join(thread2, (void*)&retval); 98 | assert(ret == 0); 99 | DEBUG_MPK("retval = %lx", retval); 100 | assert(retval == PATTERN2+1000*7); 101 | 102 | ret = pthread_join(thread1, (void*)&retval); 103 | assert(ret == 0); 104 | DEBUG_MPK("retval = %lx", retval); 105 | assert(retval == PATTERN1+1000); 106 | 107 | DEBUG_MPK("Main done waiting"); 108 | } 109 | -------------------------------------------------------------------------------- /sample_xml/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "pk.h" 7 | #include "pk_debug.h" 8 | 9 | #include "tinyxml2.h" 10 | using namespace tinyxml2; 11 | 12 | class XMLElementIsolate { 13 | private: 14 | XMLElement* xml; 15 | int did_; 16 | Ecall ecall_first_child_element_; 17 | Ecall ecall_get_text_; 18 | public: 19 | XMLElementIsolate(int did, XMLElement* x) : xml(x), 20 | did_(did), 21 | ecall_first_child_element_(this, did_, &XMLElementIsolate::_FirstChildElement), 22 | ecall_get_text_(this, did_, &XMLElementIsolate::_GetText) { 23 | } 24 | 25 | const char* GetText() { return ecall_get_text_.InvokeDomainSwitch(); } 26 | XMLElementIsolate* FirstChildElement(const char* name) { return ecall_first_child_element_.InvokeDomainSwitch(name); } 27 | 28 | private: 29 | const char* _GetText() { 30 | const char* text = xml->GetText(); 31 | if (!text) { 32 | // Donky can handle c++ exceptions across ecalls 33 | // They will be masked to avoid information leakage 34 | throw "NULL exception"; 35 | } 36 | return text; 37 | } 38 | XMLElementIsolate* _FirstChildElement(const char* name) { return new XMLElementIsolate(did_, xml->FirstChildElement(name)); } 39 | }; 40 | 41 | class XMLDocumentIsolate { 42 | private: 43 | int did_; 44 | Ecall ecall_load_file_; 45 | Ecall ecall_first_child_element_; 46 | Ecall ecall_malicious_; 47 | XMLDocument* doc; 48 | 49 | public: 50 | XMLDocumentIsolate(int did) : 51 | did_(did), 52 | ecall_load_file_(this, did_, &XMLDocumentIsolate::_LoadFile), 53 | ecall_first_child_element_(this, did_, &XMLDocumentIsolate::_FirstChildElement), 54 | ecall_malicious_(this, did_, &XMLDocumentIsolate::_Malicious) { 55 | doc = new XMLDocument(); 56 | } 57 | XMLDocumentIsolate() : XMLDocumentIsolate(pk_domain_create(0)) { 58 | } 59 | 60 | ~XMLDocumentIsolate() { delete doc; } 61 | void LoadFile(const char *fname) { ecall_load_file_.InvokeDomainSwitch(fname); } 62 | XMLElementIsolate* FirstChildElement(const char* name) { return ecall_first_child_element_.InvokeDomainSwitch(name); } 63 | int Malicious(const int* ptr) { return ecall_malicious_.InvokeDomainSwitch(ptr); } 64 | 65 | private: 66 | int _LoadFile(const char* fname) { doc->LoadFile(fname); return 0; } 67 | XMLElementIsolate* _FirstChildElement(const char* name) { return new XMLElementIsolate(did_, doc->FirstChildElement(name)); } 68 | int _Malicious(const int* ptr) { return *ptr; } 69 | }; 70 | 71 | #if 0 72 | XMLDocument doc; 73 | #else 74 | XMLDocumentIsolate doc; 75 | #endif 76 | 77 | int global = 1111; 78 | int main(int argc, char* argv[]) { 79 | 80 | doc.LoadFile( "test.xml" ); 81 | 82 | // Test normal XML parsing 83 | const char* title = doc.FirstChildElement( "Tests" )->FirstChildElement( "Test" )->FirstChildElement("Name")->GetText(); 84 | printf( "Name of test (1): %s\n", title ); 85 | 86 | // Test c++ exceptions 87 | try { 88 | printf( "Should not come here: %s\n", doc.FirstChildElement( "Tests" )->GetText() ); 89 | } catch (...) { 90 | printf( "XML element text was NULL\n"); 91 | } 92 | 93 | // Test allowed access. Our global variable is unprotected. 94 | printf( "Should succeed...\n"); 95 | printf( "Unprotected global is: %d\n", doc.Malicious(&global)); 96 | 97 | // Test malicious access. Our stack variable belongs to protected stack 98 | // of root domain. Only works for systems with MPK!! 99 | int secret = 1234; 100 | printf( "Should fail...\n"); 101 | printf( "Secret is: %d\n", doc.Malicious(&secret)); 102 | 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/x86_64/pku_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pk.h" 4 | #include 5 | 6 | /** 7 | * C++ wrapper for ecalls 8 | */ 9 | template class Ecall { 10 | private: 11 | Tclass* self_; 12 | int ecall_id_; 13 | bool exception_; 14 | int did_; 15 | Tret (Tclass::*fptr_)(Targs...); 16 | 17 | public: 18 | Ecall(Tclass* self, int did, Tret (Tclass::*fptr)(Targs...)) : 19 | self_(self), 20 | ecall_id_(-1), 21 | exception_(false), 22 | did_(did), 23 | fptr_(fptr) 24 | { 25 | ecall_id_ = pk_domain_register_ecall2(did_, PK_ECALL_ANY, (void*)&Ecall::ecall_recv); 26 | if (-1 == ecall_id_) { 27 | fprintf(stderr,"Error registering ecall"); 28 | } 29 | } 30 | 31 | /** 32 | * rdi = this 33 | */ 34 | Tret InvokeDomainSwitch(Targs... args) __attribute__((noinline)) { 35 | Tret ret; 36 | exception_ = false; 37 | // We hope the compiler did not use args register till now 38 | asm volatile( 39 | "push %%rbp \n" 40 | "push %%r15 \n" 41 | "push %%r14 \n" 42 | "push %%r13 \n" 43 | "push %%r12 \n" 44 | "push %%rbx \n" 45 | // Commented out since compiler ensures alignment (no naked function) 46 | //"add $-0x8, %%rsp \n" // to avoid psabi misalignment, we need an odd number of pushes 47 | 48 | // Save first two argument registers on stack since we replace them 49 | // with type and call id 50 | "push %%rdi \n" 51 | "push %%rsi \n" 52 | 53 | "mov $%c1, %%rdi \n" // TYPE_CALL 54 | "mov %2, %%rsi \n" // ecall_id_ 55 | 56 | "call pk_exception_handler@plt \n" // handler will restore rdi/rsi from stack before calling into ecall_recv 57 | 58 | // Cleanup stack 59 | "add $16, %%rsp \n" 60 | 61 | // Commented out since compiler ensures alignment (no naked function) 62 | //"add $0x8, %%rsp \n" // to avoid psabi misalignment 63 | "pop %%rbx \n" 64 | "pop %%r12 \n" 65 | "pop %%r13 \n" 66 | "pop %%r14 \n" 67 | "pop %%r15 \n" 68 | "pop %%rbp \n" 69 | : "=A"(ret) 70 | : "i"(TYPE_CALL), "X"((long)ecall_id_) 71 | : "rdi", "rsi", "rdx", "rcx", "r8", "r9", "memory" 72 | ); 73 | if (exception_) { 74 | DEBUG_MPK("Rethrowing exception"); 75 | throw "Domain Exception"; 76 | } 77 | return ret; 78 | } 79 | 80 | private: 81 | 82 | static Tret ecall_recv_cpp(Ecall* obj, Targs... args) { 83 | try { 84 | return ((obj->self_)->*(obj->fptr_))(args...); 85 | } catch (...) { 86 | DEBUG_MPK("Exception caught"); 87 | obj->exception_ = true; 88 | return (Tret)0; 89 | } 90 | } 91 | 92 | static int ecall_get_id(Ecall* obj) { 93 | return obj->ecall_id_; 94 | } 95 | 96 | static void ecall_recv() __attribute__((noinline, noreturn)) { 97 | asm volatile( 98 | // Commented out since compiler ensures alignment (no naked function) 99 | //"add $-0x8, %%rsp \n" // to avoid psabi misalignment 100 | "mov %%rdi, %%r12 \n" // save 'this' pointer 101 | "callq %P0 \n" // call ecall_recv_cpp(this, args) 102 | "mov %%r12, %%rdi \n" // restore 'this' pointer 103 | "mov %%rax, %%r12 \n" // save ecall_recv_cpp return value 104 | "mov %%rdx, %%r13 \n" // 105 | "callq %P1 \n" // call ecall_get_id(this) 106 | "movq $%c2, %%rdi \n" // TYPE_RET 107 | "mov %%rax, %%rsi \n" // ecall_id 108 | "mov %%r12, %%rax \n" // restore Ecall return value 109 | "mov %%r13, %%rdx \n" // 110 | "add $0x8, %%rsp \n" // to avoid psabi misalignment 111 | "call pk_exception_handler@plt \n" 112 | : 113 | : "X"(ecall_recv_cpp), "X"(ecall_get_id), "i"(TYPE_RET) 114 | ); 115 | while(1); 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/x86_64/pk_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if __x86_64__ 4 | #else 5 | #error "Unsupported platform" 6 | #endif //__x86_64__ 7 | 8 | #include "pk_arch.h" 9 | 10 | 11 | /**********************************************************************/ 12 | // For C only 13 | #ifndef __ASSEMBLY__ 14 | /**********************************************************************/ 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "pk_debug.h" 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | //extern uint64_t _pk_scratch; 27 | extern uint64_t _pk_ttls_offset; 28 | 29 | //------------------------------------------------------------------------------ 30 | // Internal definitions 31 | //------------------------------------------------------------------------------ 32 | 33 | // internal pku functions 34 | void _pk_sa_sigaction_asm(/*sig, info, ucontext*/); 35 | 36 | // internal functions 37 | void PK_CODE _pk_exception_handler(void); 38 | void PK_CODE _pk_exception_syscall(void); 39 | void PK_CODE _pk_exception_handler_end(void); 40 | uint64_t PK_CODE _pk_exception_handler_arch_c(uint64_t id, uint64_t type); 41 | int PK_CODE _pk_init_arch(); 42 | void PK_CODE _pk_setup_exception_stack_arch(void* exception_handler_stack); 43 | void PK_CODE _pk_setup_exception_handler_arch(); 44 | void PK_CODE _pk_setup_domain_arch(int did, pkey_t pkey); 45 | void PK_CODE _pk_domain_switch_arch(int type, int target_did, pkru_config_t config, void* entry_point, uint64_t* target_stack); 46 | bool PK_CODE _pk_is_key_loaded_arch(pkey_t pkey); 47 | int PK_CODE _pk_domain_load_key_arch(int did, pkey_t pkey, int slot, int perm); 48 | void* PK_CODE _pthread_init_function_asm(void *arg); 49 | void PK_CODE _pk_assert_c(uintptr_t * assert_stack); 50 | 51 | pkru_config_t PK_CODE _read_pkru(); 52 | void PK_CODE _write_pkru(pkru_config_t new_config); 53 | 54 | #define CURRENT_DID ({assert_ifdebug(DID_INVALID != pk_trusted_tls.current_did); assert_ifdebug(pk_trusted_tls.init); pk_trusted_tls.current_did;}) 55 | 56 | #ifdef __cplusplus 57 | } 58 | #endif 59 | 60 | /**********************************************************************/ 61 | // For ASM only 62 | #elif defined __ASSEMBLY__ 63 | /**********************************************************************/ 64 | 65 | 66 | #ifdef FAKE_MPK_EXCEPTION 67 | 68 | .macro trigger_exception_local 69 | // TODO: differentiate between API wrappers (which are part of shared lib and do not need GOT indirection) 70 | // and ecalls (which are in different library/main) 71 | call PLT(pk_exception_handler) 72 | .endm 73 | 74 | .macro return_from_exception 75 | ret 76 | .endm 77 | 78 | #else // FAKE_MPK_EXCEPTION 79 | 80 | #error "trigger_exception: implement me" 81 | 82 | #endif // FAKE_MPK_EXCEPTION 83 | 84 | /** 85 | * Macro for generating call wrappers 86 | * @param name Name of the generated wrapper function 87 | * @param id Unique integer of the wrapped function 88 | * @param type TYPE_ECALL or TYPE_API 89 | */ 90 | .macro GEN_CALL_WRAPPER_API name id 91 | .global \name 92 | .type \name @function 93 | \name: 94 | // Unlike GEN_CALL_WRAPPER, we do not need to save callee registers 95 | // since we assume that the trusted handler follows calling convention 96 | 97 | // Save first two argument registers on stack since we replace them 98 | // with type and call id 99 | push rdi_type 100 | push rsi_id 101 | 102 | mov $(TYPE_API), rdi_type 103 | mov $(\id), rsi_id 104 | 105 | trigger_exception_local 106 | 107 | _reentry_\name: 108 | // Cleanup stack 109 | add $16, %rsp 110 | ret 111 | .endm 112 | 113 | /** 114 | * Macro for generating call wrappers that fall back to the libc function 115 | * if not initialized yet (i.e. pk_initialized is false) 116 | * 117 | * @param name points to the original libc function. The generated wrapper 118 | * function gets a pk_ prefix. 119 | * @param id Unique integer of the wrapped function 120 | * @param type TYPE_ECALL or TYPE_API 121 | */ 122 | .macro GEN_CALL_WRAPPER_API_FALLBACK name id 123 | .global pk_\name 124 | .type pk_\name @function 125 | pk_\name: 126 | // If pk is not initialized yet, call the native function 127 | movzb PIC(pk_initialized), %r10 128 | test %r10, %r10 129 | jz PLT(\name) 130 | GEN_CALL_WRAPPER_API pk2_\name \id 131 | .endm 132 | 133 | #endif // defined __ASSEMBLY__ 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Donky: Domain Keys – Efficient In-Process Isolation for RISC-V and x86 2 | 3 | This repository contains the source code for the paper [Donky: Domain Keys – Efficient In-Process Isolation for RISC-V and x86](https://www.usenix.org/conference/usenixsecurity20/presentation/schrammel). 4 | 5 | [Donky](https://www.usenix.org/conference/usenixsecurity20/presentation/schrammel) is an efficient hardware-software co-design for strong in-process isolation based on dynamic memory protection domains. The two main components are a secure software framework and a non-intrusive hardware extension. 6 | 7 | Disclaimer: The provided code is only a proof-of-concept. Use at your own risk. Note that the license only applies to `DonkyLib`, since the included submodules have separate licenses. 8 | 9 | ## Directory structure: 10 | 11 | * DonkyLib: Contains the entire userspace library (Donky Monitor, API, self-tests). 12 | * syscall_hook: Contains the Linux module required for x86 syscall filtering. 13 | * sample_xml: Sample application using Donky to isolate xml parsing library TinyXML2. 14 | * cva6: Hardware code for the modified Ariane/CVA6 RISC-V processor. 15 | * ariane-sdk: Contains the tools for cross-compiling to RISC-V and running our library within the ISA simulator. 16 | 17 | ## Getting Started 18 | 19 | This repository can be cloned using the following commands: 20 | ``` 21 | git clone git@github.com:IAIK/Donky.git 22 | cd Donky 23 | git submodule update --init --recursive 24 | ``` 25 | 26 | ## Requirements 27 | 28 | We have tested DonkyLib on Ubuntu 20.04 using the following packages: 29 | 30 | ``` 31 | sudo apt install build-essential clang autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev libusb-1.0-0-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev device-tree-compiler pkg-config libexpat-dev python unzip 32 | ``` 33 | 34 | Furthermore, the following commands need be be executed in order to compile Donky for RISC-V. 35 | 36 | ``` 37 | mkdir toolchain 38 | export RISCV=$(realpath toolchain) 39 | export PATH=$PATH:$RISCV/bin 40 | export SDKBASE=$(realpath ariane-sdk)/ 41 | ``` 42 | 43 | ## Building and running DonkyLib 44 | 45 | Compile and run DonkyLib on x86 CPUs **without MPK**: 46 | (Simulates memory protection keys. No security guarantees and isolation tests disabled.) 47 | 48 | ``` 49 | make -C DonkyLib PLATFORM=x86_64 RELEASE=1 TIMING=1 SIM=pk clean run 50 | ``` 51 | 52 | Compile and run DonkyLib on x86 CPUs **with MPK**: 53 | ``` 54 | make -C DonkyLib PLATFORM=x86_64 RELEASE=1 TIMING=1 clean run 55 | ``` 56 | 57 | The make flag `TIMING=1` also runs the integrated microbenchmarks, which can be omitted to only run self tests. 58 | While omitting the `RELEASE=1` flag is also possible, it is not recommended since it's printing a lot of debug output. `TIMING=1` should only be used in conjunction with `RELEASE=1`. 59 | 60 | ### RISC-V 61 | 62 | DonkyLib can also be compiled for RISC-V (by setting `PLATFORM=riscv`), but it requires the RISC-V compiler toolchain (in ariane-sdk), which takes a very long time to compile. This includes compiling RISC-V compilers, simulators, libc, Linux, and other dependencies. Building this will take several hours. 63 | 64 | To build the RISC-V toolchain, run: 65 | 66 | ``` 67 | make -C ${SDKBASE} all 68 | ``` 69 | 70 | To run DonkyLib with the Proxykernel (not Linux) in the RISC-V ISA simulator, run: 71 | 72 | ``` 73 | make -C DonkyLib PLATFORM=riscv RELEASE=1 TIMING=1 SIM=pk clean run 74 | ``` 75 | 76 | The same can also be done using the real Linux kernel, but this will take a very long time to compile since it needs to download and build the Linux kernel: 77 | 78 | ``` 79 | make -C DonkyLib PLATFORM=riscv RELEASE=1 TIMING=1 clean run 80 | ``` 81 | 82 | Once Linux is booted in in the simulator, you can use `./x.elf` to run the binary. 83 | 84 | ## DonkyLib source code 85 | 86 | Donky's source code can be found in the directory `DonkyLib`. 87 | It is split into two parts: The trusted library resides in `pk`, while the untrusted code lies in `user`. Each of these also have `arch` subdirectories for architecture-specific code, since DonkyLib supports both x86_64 and RISC-V. 88 | Donky API functions can be found in `pk/pk.h`. Its internal functions and metadata structures are defined in `pk/pk_internal.h`. 89 | `user` contains all self-tests and the integrated micro-benchmarks. `main.c` contains the main function, which initializes DonkyLib and starts the integrated testsuite. 90 | 91 | The Makefile compiles the trusted and untrusted part into seperate archives/sections, so that the library can protect its code and data. It can also be compiled into a shared library, which is used in `sample_xml` (see below). 92 | By default, it compiles both the library and the user-side tests/benchmarks into a single executable: `x.elf`. 93 | 94 | 95 | ## sample_xml 96 | 97 | The directory `sample_xml` contains a sample C++ application, which isolates TinyXML2 using DonkyLib. 98 | This test first tests noraml xml parsing functionality when isolated usind DonkyLib. 99 | Then it tests handling of exceptions within child-domains. 100 | And finally it tests an artificial malicious function, which tries to access the stack of the parent, which should fail. 101 | 102 | It can be run with the following commands: 103 | 104 | For Intel CPUs **with MPK**: 105 | 106 | ``` 107 | make -C sample_xml clean run 108 | ``` 109 | 110 | For Intel CPUs **without MPK**: 111 | 112 | ``` 113 | make -C sample_xml SIM=pk clean run 114 | ``` 115 | 116 | Note, that access permissions cannot be enforced for CPUs without MPK. 117 | -------------------------------------------------------------------------------- /DonkyLib/user/test0.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "pk.h" 10 | #include "pk_debug.h" 11 | #include "test0.h" 12 | 13 | jmp_buf test0_exception_buffer; 14 | bool test0_access_test_running = false; 15 | 16 | int test0_current_test = 0; 17 | size_t test0_exc_count = 0; 18 | 19 | uint32_t test0_pkru = 0; 20 | 21 | #define ACCESS_MUST_FAIL(access) do { \ 22 | assert(!test0_access_test_running); \ 23 | test0_access_test_running = true; \ 24 | SAVE_PKRU(); \ 25 | /* Store reentry point for exception handler */ \ 26 | int exc = setjmp(test0_exception_buffer); \ 27 | if (0 == exc) { \ 28 | /* Do critical access */ \ 29 | do { access; } while(0); \ 30 | /* The access did not fail */ \ 31 | assert(!"ACCESS_MUST_FAIL("#access")"); \ 32 | } else { \ 33 | /* We came from the exception handler */ \ 34 | assert(1 == exc); \ 35 | test0_exc_count++; \ 36 | } \ 37 | assert(test0_access_test_running); \ 38 | test0_access_test_running = false; \ 39 | } while(0) 40 | 41 | #define ACCESS_MUST_NOT_FAIL(access) do { \ 42 | assert(!test0_access_test_running); \ 43 | test0_access_test_running = true; \ 44 | SAVE_PKRU(); \ 45 | /* Store reentry point for exception handler */ \ 46 | int exc = setjmp(test0_exception_buffer); \ 47 | if (0 == exc) { \ 48 | /* Do critical access */ \ 49 | do { access; } while(0); \ 50 | /* The access did not fail */ \ 51 | } else { \ 52 | /* We came from the exception handler */ \ 53 | assert(!"ACCESS_MUST_NOT_FAIL("#access")"); \ 54 | } \ 55 | assert(test0_access_test_running); \ 56 | test0_access_test_running = false; \ 57 | } while(0) 58 | 59 | #if __x86_64 60 | 61 | // x86: Linux modifies pkru to kernel's copy before calling segfault handler 62 | // We want to restore our own version of pkru 63 | #define SAVE_PKRU() do { test0_pkru = _read_pkru_reg(); } while(0) 64 | #define RESTORE_PKRU() do { _write_pkru_reg(test0_pkru); } while(0) 65 | 66 | #else 67 | 68 | // riscv does not allow writing pkru from user space, but it should 69 | // also not be needed. 70 | #define SAVE_PKRU() 71 | #define RESTORE_PKRU() 72 | 73 | #endif 74 | 75 | 76 | void test0_child(volatile uint64_t* mem, int allowed) { 77 | //~ int ret; 78 | //~ ret = pk_domain_load_key(shared_pkey, PK_SLOT_ANY, 0); 79 | //~ assert(0 == ret); 80 | assert(mem); 81 | 82 | DEBUG_MPK("Hi, I am child-fail, accessing mem %p", mem); 83 | int i = 0; 84 | //for (size_t i = 0; i < 4096/sizeof(uint64_t); i+=64) { 85 | if (allowed) { 86 | ACCESS_MUST_NOT_FAIL(mem[i] = i); 87 | } else { 88 | ACCESS_MUST_FAIL(mem[i] = i); 89 | } 90 | //} 91 | } 92 | 93 | void test0_exception_handler(void *bad_addr) { 94 | DEBUG_MPK("test0_exception_handler(%p)", bad_addr); 95 | //assert(false); 96 | if (test0_access_test_running) { 97 | DEBUG_MPK("test0_access_test_running: resuming with longjmp"); 98 | longjmp(test0_exception_buffer, 1); 99 | // should not reach here 100 | assert(false); 101 | } else { 102 | ERROR("test0_exception_handler: no test is running!"); 103 | print_maps(); 104 | assert(false); 105 | } 106 | } 107 | 108 | void test0_segfault_handler(int sig, siginfo_t *si, void *unused) { 109 | psiginfo(si, "test0_segfault_handler"); 110 | assert(SIGSEGV == sig); 111 | 112 | //DEBUG_MPK("test0_segfault_handler at %p. Reason: %d", si->si_addr, si->si_code); 113 | 114 | RESTORE_PKRU(); 115 | 116 | // Don't forget to unblock the signal 117 | sigset_t sigset; 118 | int ret; 119 | ret = sigemptyset(&sigset); assert(0 == ret); 120 | ret = sigaddset(&sigset, SIGSEGV); assert(0 == ret); 121 | ret = sigprocmask(SIG_UNBLOCK, &sigset, NULL); assert(0 == ret); 122 | 123 | test0_exception_handler(si->si_addr); 124 | assert(false); 125 | } 126 | 127 | #if __x86_64 128 | 129 | static void __attribute__((naked)) test0_segfault_handler_asm() { 130 | // Linux segfault handler restores pkru to kernel's copy 131 | // Temporarily enable full PKRU permission to give 132 | // test0_segfault_handler stack access 133 | asm volatile( 134 | "xor %eax, %eax\n" // clear eax (full permission) 135 | "xor %ecx, %ecx\n" // clear ecx (needed for wrpkru) 136 | "xor %edx, %edx\n" // clear edx (needed for wrpkru) 137 | "wrpkru\n" 138 | "jmp test0_segfault_handler\n" 139 | ); 140 | } 141 | 142 | #else 143 | 144 | // riscv does not need asm handler 145 | #define test0_segfault_handler_asm test0_segfault_handler 146 | 147 | #endif 148 | 149 | void test0() { 150 | int ret; 151 | 152 | DEBUG_MPK("Preparing for isolation tests"); 153 | 154 | // register segfault handler (needed for x86) 155 | struct sigaction sa; 156 | memset(&sa, 0, sizeof(sa)); 157 | sa.sa_flags = SA_SIGINFO; 158 | sa.sa_sigaction = test0_segfault_handler_asm; 159 | ret = sigaction(SIGSEGV, &sa, NULL); 160 | assert(0 == ret); 161 | 162 | ret = pk_register_exception_handler(test0_exception_handler); 163 | assert(0 == ret); 164 | 165 | int did = pk_domain_create(0); 166 | assert(did > 0); 167 | DEBUG_MPK("Child domain: %d", did); 168 | 169 | ret = ecall_register_test0_child(did); 170 | assert(ret >= 0); 171 | 172 | volatile uintptr_t* mem = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 173 | ecall_test0_child(mem, 0); 174 | 175 | int pkey = 0; 176 | do { 177 | pkey = pkey_alloc(0, 0); 178 | if (pkey <= 0) { 179 | break; 180 | } 181 | DEBUG_MPK("Testing pkey: %d", pkey); 182 | 183 | ret = pkey_mprotect((void*)mem, 4096, PROT_READ | PROT_WRITE, pkey); 184 | assert(0 == ret); 185 | ecall_test0_child(mem, 0); 186 | 187 | ret = pk_domain_assign_pkey(did, pkey, PK_KEY_COPY, 0); 188 | assert(0 == ret); 189 | ecall_test0_child(mem, 1); 190 | 191 | } while (1); 192 | 193 | // Deregister signal handler 194 | memset(&sa, 0, sizeof(sa)); 195 | sa.sa_handler = SIG_DFL; 196 | ret = sigaction(SIGSEGV, &sa, NULL); 197 | assert(0 == ret); 198 | return; 199 | } 200 | -------------------------------------------------------------------------------- /DonkyLib/main.c: -------------------------------------------------------------------------------- 1 | #include "pk.h" 2 | #include "pk_debug.h" 3 | 4 | #include 5 | #include "tests.h" 6 | #include "test_ecalls.h" 7 | #include "test0.h" 8 | #include "test1_api.h" 9 | #include "test2_ecall.h" 10 | #include "test3_ecall.h" 11 | #include "test4_pthread.h" 12 | #include "test5.h" 13 | #include "test6.h" 14 | #include "bench.h" 15 | 16 | //------------------------------------------------------------------------------ 17 | // Addresses of sections to be isolated 18 | extern uintptr_t _test2_start[]; 19 | extern uintptr_t _test2_end[]; 20 | 21 | extern uintptr_t _test3t_start[]; 22 | extern uintptr_t _test3t_end[]; 23 | extern uintptr_t _test3d_start[]; 24 | extern uintptr_t _test3d_end[]; 25 | extern uintptr_t _test3_start[]; 26 | extern uintptr_t _test3_end[]; 27 | //------------------------------------------------------------------------------ 28 | 29 | #define SANITYCHECKS() do { pk_debug_usercheck(0); } while (0) 30 | #define LINE() do { SANITYCHECKS(); printf("===========================================\n"); } while (0) 31 | 32 | #define SYM_SIZE(sym) (size_t)((uintptr_t)_##sym##_end - (uintptr_t)_##sym##_start) 33 | 34 | 35 | //------------------------------------------------------------------------------ 36 | 37 | int test3_domain = 0; 38 | int test2_domain = 0; 39 | 40 | //------------------------------------------------------------------------------ 41 | 42 | void setup_domains() { 43 | LINE(); 44 | printf("Testing simple API call\n"); 45 | int res = pk_simple_api_call(1,2,3,4,5,6); 46 | assert(res == (1+2+3+4+5+6)); 47 | 48 | LINE(); 49 | printf("START of test1 API\n"); 50 | test1_api(); // This test must be before any other test allocating keys 51 | printf("END of test1 API\n"); 52 | 53 | LINE(); 54 | int domain_flags = PK_KEY_INHERIT | PK_KEY_COPY; //This is necessary because bench() calls some test2 function directly without an ecall. otherwise this would lead to a key mismatch fault and we'd die. 55 | test2_domain = pk_domain_create(domain_flags); 56 | #ifndef SHARED 57 | bool ret = pk_pkey_mprotect2(test2_domain, _test2_start, SYM_SIZE(test2), PROT_EXEC | PROT_READ | PROT_WRITE, PK_DEFAULT_KEY); 58 | assert(ret == 0); 59 | #endif 60 | ecall_register_test2(test2_domain); 61 | 62 | test3_domain = pk_domain_create(domain_flags); 63 | #ifndef SHARED 64 | #ifdef PROXYKERNEL 65 | // Proxy kernel has some mmap issues, so let's just use a single mapping for code+data which allows r+w+x 66 | ret = pk_pkey_mprotect2(test3_domain, _test3_start, SYM_SIZE(test3), PROT_EXEC | PROT_READ | PROT_WRITE, PK_DEFAULT_KEY); 67 | assert(ret == 0); 68 | #else /* PROXYKERNEL */ 69 | ret = pk_pkey_mprotect2(test3_domain, _test3t_start, SYM_SIZE(test3t), PROT_EXEC | PROT_READ, PK_DEFAULT_KEY); 70 | assert(ret == 0); 71 | ret = pk_pkey_mprotect2(test3_domain, _test3d_start, SYM_SIZE(test3d), PROT_READ | PROT_WRITE, PK_DEFAULT_KEY); 72 | assert(ret == 0); 73 | #endif /* PROXYKERNEL */ 74 | #endif /* SHARED */ 75 | 76 | //currently not necessary because root domain is allowed to do any ecall? 77 | //pk_domain_allow_caller2(test3_domain, pk_current_did(), 0); 78 | //pk_domain_allow_caller2(test2_domain, pk_current_did(), 0); 79 | 80 | ecall_register_test3(test3_domain); 81 | ecall_register_test3_time(test3_domain); 82 | 83 | ecall_register_test2_nested(test2_domain); 84 | ecall_register_test3_nested(test3_domain); 85 | 86 | //test2 and test3 need to be able to call each other 87 | pk_domain_allow_caller2(test3_domain, test2_domain, 0); 88 | pk_domain_allow_caller2(test2_domain, test3_domain, 0); 89 | 90 | LINE(); 91 | 92 | #ifndef RELEASE 93 | //Print debug info after setting up domains 94 | pk_print_debug_info(); 95 | #endif 96 | } 97 | 98 | void run_tests() { 99 | //Run tests 100 | LINE(); 101 | printf("START of test2\n"); 102 | test2(); 103 | printf("END of test2\n"); 104 | 105 | LINE(); 106 | printf("START of test3\n"); 107 | ecall_test3(); //Note: calls ecall-function from test2 108 | printf("END of test3\n"); 109 | 110 | #if !defined(PROXYKERNEL) 111 | LINE(); 112 | printf("START of test4\n"); 113 | test4_pthread(); // does not work on riscv yet 114 | printf("END of test4\n"); 115 | #endif // PROXYKERNEL 116 | 117 | #if !defined(FAKE_MPK_REGISTER) 118 | LINE(); 119 | printf("START of test5\n"); 120 | test_missing_key_exception(); //does not work on x86 yet? 121 | #ifndef FAKE_MPK_REGISTER 122 | test_pkey_isolation(); 123 | #endif 124 | printf("END of test5\n"); 125 | #endif 126 | 127 | printf("START of test6\n"); 128 | test6_signals(); 129 | printf("END of test6\n"); 130 | 131 | //testing nested calls 132 | LINE(); 133 | printf("Testing nested calls\n"); 134 | ecall_test3_nested(10); 135 | LINE(); 136 | 137 | printf("run_tests done\n"); 138 | } 139 | //------------------------------------------------------------------------------ 140 | 141 | void run_preinit_tests() { 142 | // Before initialization, all calls shall work as usual 143 | int ret; 144 | int pkey = pkey_alloc(0, 0); 145 | assert(pkey >= 0); 146 | char* mem = mmap(NULL, 2 * PAGESIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, 0, 0); 147 | assert(MAP_FAILED != mem); 148 | mem[1] = mem[0]+1; 149 | ret = mprotect(mem, PAGESIZE, PROT_READ | PROT_WRITE); 150 | assert(0 == ret); 151 | ret = pkey_mprotect(mem + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, pkey); 152 | assert(0 == ret); 153 | ret = munmap(mem, 2 * PAGESIZE); 154 | assert(0 == ret); 155 | ret = pkey_free(pkey); 156 | assert(0 == ret); 157 | } 158 | //------------------------------------------------------------------------------ 159 | 160 | int main(){ 161 | 162 | #ifndef CONSTRUCTOR 163 | #ifdef TIMING 164 | bench_preinit(); 165 | #else 166 | run_preinit_tests(); 167 | #endif 168 | 169 | // Initialize PK 170 | if(pk_init() != 0){ 171 | ERROR_FAIL("main: pk_init failed"); 172 | } 173 | #endif /* CONSTRUCTOR */ 174 | 175 | 176 | pk_print_current_reg(); 177 | printf("pk_init done\n"); 178 | 179 | // TODO: only run test0 standalone, i.e. without other tests 180 | // because it allocates all protection keys without freeing them 181 | //~ LINE(); 182 | //~ printf("START of test0\n"); 183 | //~ test0(); 184 | //~ printf("END of test0\n"); 185 | //~ return 0; 186 | 187 | setup_domains(); 188 | 189 | #ifdef TIMING 190 | bench(); 191 | timing_results(); 192 | #else 193 | run_tests(); 194 | #endif 195 | printf("END\n"); 196 | 197 | #ifndef CONSTRUCTOR 198 | // Deinitialize PK 199 | if(pk_deinit() != 0){ 200 | ERROR_FAIL("main: pk_deinit failed"); 201 | } 202 | #endif /* CONSTRUCTOR */ 203 | 204 | return 0; 205 | } 206 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/x86_64/pk_arch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pk_defs.h" 3 | 4 | //------------------------------------------------------------------------------ 5 | // Arch-specific API definitions 6 | //------------------------------------------------------------------------------ 7 | 8 | #define WORDSIZE 8 9 | #define PAGESIZE 4096 10 | #define PAGEMASK (PAGESIZE-1) 11 | #define PK_NUM_KEYS 16 12 | 13 | // PK handler types 14 | // Do not change these values, as asm code would break! 15 | #define TYPE_RET 0 16 | #define TYPE_CALL 1 17 | #define TYPE_API 2 18 | #define TYPE_EXCEPTION 3 19 | 20 | // Type distinguishes dcalls, returns and API calls 21 | #define rdi_type %rdi 22 | // Holds the ID of a dcall 23 | #define rsi_id %rsi 24 | 25 | #ifdef SHARED 26 | 27 | // To access global variables directly from assembler code, 28 | // they must be marked __attribute__((visibility("hidden"))). 29 | // Otherwise, they are globally exported and require GOT. 30 | // We avoid this via CFLAGS=-fvisibility=hidden 31 | 32 | // Intra-lib data access 33 | #define PIC(x) x(%rip) 34 | // Cross-library function calls 35 | #define PLT(x) x@plt 36 | // Intra-lib function calls 37 | #define PCREL(x) x 38 | // String-representation of PIC, for C-inline assembler 39 | // We did not manage to make _pk_exception_handler_end "hidden", so 40 | // use plt indirection instead 41 | #define S_PIC(x) #x"@plt" 42 | 43 | #else // SHARED=0 (STATIC) 44 | 45 | #define PIC(x) x 46 | #define PLT(x) x 47 | #define PCREL(x) x 48 | #define S_PIC(x) #x 49 | 50 | #endif // SHARED 51 | 52 | /**********************************************************************/ 53 | // For C only 54 | #ifndef __ASSEMBLY__ 55 | /**********************************************************************/ 56 | 57 | #include 58 | 59 | #ifdef __cplusplus 60 | extern "C" { 61 | #endif 62 | 63 | typedef uint16_t pkey_t; 64 | typedef uint64_t pkru_config_t; 65 | 66 | #define GET_TLS_POINTER ((uintptr_t)_get_fsbase()) 67 | 68 | FORCE_INLINE uint64_t _get_fsbase() { 69 | uint64_t ret; 70 | __asm__ volatile ("mov %%fs:0x0, %0" : "=r" (ret)); 71 | return ret; 72 | } 73 | 74 | #define PKRU_TO_INT(x) (x) 75 | #define INT_TO_PKRU(x) (x) 76 | 77 | #ifdef FAKE_MPK_REGISTER 78 | 79 | extern pkru_config_t emulated_mpk_reg; 80 | 81 | FORCE_INLINE pkru_config_t _read_pkru_reg() { 82 | pkru_config_t copy = emulated_mpk_reg; 83 | return copy; 84 | } 85 | 86 | FORCE_INLINE void _write_pkru_reg(pkru_config_t new_config) { 87 | emulated_mpk_reg = new_config; 88 | } 89 | 90 | #else /* FAKE_MPK_REGISTER */ 91 | 92 | FORCE_INLINE pkru_config_t _read_pkru_reg() { 93 | pkru_config_t ret; 94 | // https://www.felixcloutier.com/x86/rdpkru 95 | __asm__ volatile( 96 | "xor %%ecx, %%ecx\n" 97 | "rdpkru" 98 | : "=a"(ret) 99 | : /* no inputs */ 100 | : "rdx" 101 | ); 102 | return ret; 103 | } 104 | 105 | FORCE_INLINE void _write_pkru_reg(pkru_config_t new_val) { 106 | // https://www.felixcloutier.com/x86/wrpkru 107 | __asm__ volatile( 108 | "xor %%ecx, %%ecx\n" // clear ecx 109 | "xor %%edx, %%edx\n" // clear edx 110 | "wrpkru" 111 | : /* no outputs */ 112 | : "a"(new_val) 113 | : "rcx", "rdx" 114 | ); 115 | } 116 | 117 | #endif /* FAKE_MPK_REGISTER */ 118 | 119 | FORCE_INLINE void pk_debug_usercheck_arch() { 120 | } 121 | //------------------------------------------------------------------------------ 122 | 123 | #include 124 | FORCE_INLINE void pk_print_reg_arch(pkru_config_t reg){ 125 | fprintf(stderr,"raw = 0x%lx, ", reg); 126 | fprintf(stderr,"\n"); 127 | } 128 | //------------------------------------------------------------------------------ 129 | 130 | #ifdef __cplusplus 131 | } 132 | #endif 133 | 134 | /**********************************************************************/ 135 | // For ASM only 136 | #elif defined __ASSEMBLY__ 137 | /**********************************************************************/ 138 | 139 | #ifdef FAKE_MPK_EXCEPTION 140 | 141 | .macro trigger_exception 142 | call PLT(pk_exception_handler) // Since first call invokes the dynamic linker, which manipulates r10, we use LD_BIND_NOW=1 143 | .endm 144 | 145 | #else // FAKE_MPK_EXCEPTION 146 | #error "x86 can only emulate MPK exception" 147 | #endif // FAKE_MPK_EXCEPTION 148 | 149 | .macro DIE 150 | jmp . 151 | .endm 152 | 153 | .macro SAVE_CALLEE_REGS 154 | push %rbp 155 | push %r15 156 | push %r14 157 | push %r13 158 | push %r12 159 | push %rbx 160 | add $-0x8, %rsp // to avoid psabi misalignment, we need an odd number 161 | // of pushes 162 | //Note that RSP is also callee-saved, but the exception handler handles its preservation 163 | .endm 164 | 165 | .macro RESTORE_CALLEE_REGS 166 | add $0x8, %rsp // to avoid psabi misalignment 167 | pop %rbx 168 | pop %r12 169 | pop %r13 170 | pop %r14 171 | pop %r15 172 | pop %rbp 173 | .endm 174 | 175 | /** 176 | * Macro for generating call wrappers 177 | * @param name Name of the generated wrapper function 178 | * @param id Unique integer of the wrapped function 179 | * @param type TYPE_ECALL or TYPE_API 180 | */ 181 | .macro GEN_CALL_WRAPPER name id 182 | .global ecall_\name 183 | .type ecall_\name @function 184 | ecall_\name: 185 | 186 | // Save callee regs since we cannot rely on potentially untrusted 187 | // ecall target to behave properly 188 | SAVE_CALLEE_REGS 189 | 190 | // Save first two argument registers on stack since we replace them 191 | // with type and call id 192 | push rdi_type 193 | push rsi_id 194 | 195 | mov $(TYPE_CALL), rdi_type 196 | mov $(\id), rsi_id 197 | 198 | trigger_exception 199 | _reentry_\name: 200 | 201 | // Cleanup stack 202 | add $16, %rsp 203 | 204 | RESTORE_CALLEE_REGS 205 | ret 206 | .endm 207 | 208 | /** 209 | * Macro for generating return wrappers 210 | * @param name Name of the function. The wrapper will invoke _name 211 | * @param id Unique integer of the wrapped function 212 | */ 213 | .macro GEN_CALLEE_WRAPPER name id 214 | .global _ecall_receive_\name 215 | _ecall_receive_\name: 216 | 217 | add $-0x8, %rsp // to avoid psabi misalignment 218 | call \name 219 | 220 | mov $(TYPE_RET), rdi_type 221 | mov $(\id), rsi_id 222 | 223 | add $0x8, %rsp // to avoid psabi misalignment 224 | trigger_exception 225 | DIE 226 | .endm 227 | 228 | 229 | .macro GEN_REGISTER name id 230 | .global ecall_register_\name 231 | .type ecall_register_\name @function 232 | ecall_register_\name: 233 | 234 | // _pk_domain_register_ecall2(int did, uint id, void * entry_point) 235 | // rdi = did 236 | // rsi = id 237 | // rdx = entry_point 238 | //mov %rdi, %rdi 239 | mov $(\id), %rsi 240 | lea PIC(_ecall_receive_\name), %rdx 241 | 242 | jmp pk_domain_register_ecall2 243 | DIE 244 | ret 245 | .endm 246 | 247 | .macro GEN_ALL_SIMPLE name id 248 | GEN_REGISTER \name \id 249 | GEN_CALL_WRAPPER \name \id 250 | GEN_CALLEE_WRAPPER \name \id 251 | .endm 252 | 253 | #endif // __ASSEMBLY__ 254 | -------------------------------------------------------------------------------- /syscall_hook/sysfilter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "sysfilter.h" 14 | 15 | MODULE_AUTHOR("Michael Schwarz"); 16 | MODULE_DESCRIPTION("Filter syscalls"); 17 | MODULE_LICENSE("GPL"); 18 | 19 | static inline void write_cr0_direct(unsigned long val) 20 | { 21 | asm volatile("mov %0,%%cr0": "+r" (val), "+m" (__force_order)); 22 | } 23 | 24 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) && CONFIG_X86_64 25 | #define REGS_DEFINES const struct pt_regs* regs 26 | #define REGS regs 27 | #define SYSNO regs->orig_ax 28 | #else 29 | #define REGS_DEFINES long unsigned int a, long unsigned int b, long unsigned int c, long unsigned int d, long unsigned int e, long unsigned int f 30 | #define REGS a, b, c, d, e, f 31 | #define SYSNO ??? 32 | #error "Old linux does not provide us with syscall number" 33 | #endif 34 | 35 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) 36 | #define from_user raw_copy_from_user 37 | #define to_user raw_copy_to_user 38 | #else 39 | #define from_user copy_from_user 40 | #define to_user copy_to_user 41 | #endif 42 | 43 | static bool device_busy = false; 44 | static int pid_filter = 0; 45 | static int has_pke = 0; 46 | static int pkey = 0; 47 | static uint32_t pkru_init_value = 0; 48 | static int kill_on_violation = 0; 49 | 50 | // --------------------------------------------------------------------------- 51 | static int device_open(struct inode *inode, struct file *file) { 52 | /* Check if device is busy */ 53 | if (device_busy == true) { 54 | return -EBUSY; 55 | } 56 | 57 | /* Lock module */ 58 | try_module_get(THIS_MODULE); 59 | 60 | device_busy = true; 61 | 62 | return 0; 63 | } 64 | 65 | // --------------------------------------------------------------------------- 66 | static int device_release(struct inode *inode, struct file *file) { 67 | /* Unlock module */ 68 | device_busy = false; 69 | 70 | module_put(THIS_MODULE); 71 | 72 | return 0; 73 | } 74 | 75 | // --------------------------------------------------------------------------- 76 | static sys_call_ptr_t old_sys_call_table[__NR_syscall_max]; 77 | static sys_call_ptr_t* syscall_tbl; 78 | 79 | // --------------------------------------------------------------------------- 80 | static int readkey(void) { 81 | size_t key = 0; 82 | if(has_pke) { 83 | asm volatile( 84 | "RDPKRU\n" 85 | "mov %%rax, %0\n" 86 | : "=r"(key) : "a"(0), "c"(0), "d"(0) : "memory"); 87 | } else { 88 | key = pkey; 89 | } 90 | return (key & 0xffffffff); 91 | } 92 | 93 | // --------------------------------------------------------------------------- 94 | static long hook_generic(REGS_DEFINES) { 95 | int pid = task_pid_nr(current); 96 | int sys_nr = SYSNO; 97 | 98 | if(pid_filter && pid == pid_filter) { 99 | int k = readkey(); 100 | if(k != pkru_init_value && k != 0) { 101 | printk("Blocked syscall %d (PID: %d) (k: 0x%x)\n", sys_nr, pid, k); 102 | if(kill_on_violation) kill_pid(find_vpid(pid_filter), 2, 1); 103 | return 0; 104 | } 105 | } 106 | return old_sys_call_table[sys_nr](REGS); 107 | } 108 | 109 | // --------------------------------------------------------------------------- 110 | static void hook_syscall(int nr, sys_call_ptr_t hook) { 111 | // unprotect syscall table 112 | write_cr0_direct(read_cr0() & ~0x10000); 113 | printk("[sysfilter-module] Hooking syscall %d\n", nr); 114 | syscall_tbl[nr] = hook; 115 | write_cr0_direct(read_cr0() | 0x10000); 116 | } 117 | 118 | // --------------------------------------------------------------------------- 119 | static void unhook_syscall(int nr) { 120 | // unprotect syscall table 121 | write_cr0_direct(read_cr0() & ~0x10000); 122 | printk("[sysfilter-module] Unhooking syscall %d\n", nr); 123 | syscall_tbl[nr] = old_sys_call_table[nr]; 124 | write_cr0_direct(read_cr0() | 0x10000); 125 | } 126 | 127 | // --------------------------------------------------------------------------- 128 | static long device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) { 129 | switch (ioctl_num) { 130 | case SYSFILTER_IOCTL_CMD_BLOCK: 131 | { 132 | hook_syscall(ioctl_param, hook_generic); 133 | return 0; 134 | } 135 | case SYSFILTER_IOCTL_CMD_UNBLOCK: 136 | { 137 | unhook_syscall(ioctl_param); 138 | return 0; 139 | } 140 | case SYSFILTER_IOCTL_CMD_PID: 141 | { 142 | pid_filter = ioctl_param; 143 | return 0; 144 | } 145 | case SYSFILTER_IOCTL_CMD_WRITEKEY: 146 | { 147 | pkey = ioctl_param; 148 | return 0; 149 | } 150 | case SYSFILTER_IOCTL_CMD_KILL_ON_VIOLATION: 151 | { 152 | kill_on_violation = ioctl_param; 153 | return 0; 154 | } 155 | default: 156 | return -1; 157 | } 158 | 159 | return 0; 160 | } 161 | 162 | // --------------------------------------------------------------------------- 163 | static struct file_operations f_ops = {.unlocked_ioctl = device_ioctl, 164 | .open = device_open, 165 | .release = device_release}; 166 | 167 | // --------------------------------------------------------------------------- 168 | static struct miscdevice misc_dev = { 169 | .minor = MISC_DYNAMIC_MINOR, 170 | .name = SYSFILTER_DEVICE_NAME, 171 | .fops = &f_ops, 172 | .mode = S_IRWXUGO, 173 | }; 174 | 175 | // --------------------------------------------------------------------------- 176 | int init_module(void) { 177 | int r, i; 178 | 179 | // check for PKE 180 | has_pke = !!(native_read_cr4() & (1ull << 22)); 181 | printk(KERN_INFO "[sysfilter-module] PKE: %d\n", has_pke); 182 | 183 | if(has_pke) { 184 | // get initial pkru 185 | uint32_t* pkru_init = (uint32_t*)kallsyms_lookup_name("init_pkru_value"); 186 | if(pkru_init) { 187 | pkru_init_value = *pkru_init; 188 | } 189 | } 190 | 191 | // register device 192 | r = misc_register(&misc_dev); 193 | if (r != 0) { 194 | printk(KERN_ALERT "[sysfilter-module] Failed registering device with %d\n", r); 195 | return 1; 196 | } 197 | 198 | syscall_tbl = (sys_call_ptr_t*)kallsyms_lookup_name("sys_call_table"); 199 | printk("[sysfilter-module] Syscall table @ %zx\n", (size_t)syscall_tbl); 200 | 201 | // backup old sys call table 202 | for(i = 0; i < __NR_syscall_max; i++) { 203 | old_sys_call_table[i] = syscall_tbl[i]; 204 | } 205 | 206 | printk(KERN_INFO "[sysfilter-module] Loaded.\n"); 207 | 208 | return 0; 209 | } 210 | 211 | // --------------------------------------------------------------------------- 212 | void cleanup_module(void) { 213 | int i; 214 | misc_deregister(&misc_dev); 215 | 216 | // restore old syscall table 217 | write_cr0_direct(read_cr0() & ~0x10000); 218 | for(i = 0; i < __NR_syscall_max; i++) { 219 | syscall_tbl[i] = old_sys_call_table[i]; 220 | } 221 | write_cr0_direct(read_cr0() | 0x10000); 222 | 223 | printk(KERN_INFO "[sysfilter-module] Removed.\n"); 224 | } 225 | -------------------------------------------------------------------------------- /DonkyLib/Makefile: -------------------------------------------------------------------------------- 1 | # PLATFORM is one of (riscv, x86_64) 2 | PLATFORM ?= riscv 3 | # SIM is one of ("", pk) 4 | SIM?= 5 | SHARED?= 6 | CCFLAGS?= 7 | LDFLAGS?= 8 | LDLIBS?= 9 | INSTALLDIR?=/usr/lib 10 | 11 | LDLIBS+=-lpthread 12 | 13 | CCFLAGS+=-DTLS_MISALIGNMENT_BUG 14 | ifeq ($(RELEASE),1) 15 | CCFLAGS+=-DRELEASE 16 | endif 17 | 18 | ifeq ($(TIMING),1) 19 | CCFLAGS+=-DTIMING 20 | LDLIBS+=-lm 21 | endif 22 | 23 | ifeq ($(CONSTRUCTOR),1) 24 | CCFLAGS+=-DCONSTRUCTOR 25 | endif 26 | 27 | ifeq ($(PRELOAD),1) 28 | CCFLAGS+=-DCONSTRUCTOR 29 | CCFLAGS+=-DDL_HOOKING 30 | CCFLAGS+=-DDLU_HOOKING 31 | LDLIBS+=-ldl 32 | export DO_LD_PRELOAD=$(CURDIR)/libpku.so 33 | #export DO_LD_PRELOAD=$(CURDIR)/libpk.so 34 | endif 35 | 36 | CCFLAGS+=$(CF) 37 | 38 | ifeq ($(SHARED),1) 39 | CCFLAGS+=-fPIC 40 | CCFLAGS+=-fvisibility=hidden 41 | CCFLAGS+=-DSHARED 42 | LD_LIBRARY_PATH=$(CURDIR) 43 | export LD_LIBRARY_PATH 44 | endif 45 | 46 | ifeq ($(FAKEMPK),1) 47 | CCFLAGS+=-DFAKE_MPK_REGISTER 48 | CCFLAGS+=-DFAKE_PKEY_SYSCALLS 49 | CCFLAGS+=-DFAKE_MPK_EXCEPTION 50 | endif # FAKEMPK 51 | 52 | ######################################################################## 53 | # RISC-V 54 | ######################################################################## 55 | ifeq ($(PLATFORM),riscv) 56 | 57 | ifndef RISCV 58 | $(error RISCV is not set) 59 | endif 60 | 61 | ifndef SDKBASE 62 | $(error SDKBASE is not set) 63 | endif 64 | 65 | #SDKBASE?=$(realpath ../../ariane-sdk)/ 66 | 67 | PREFIX=$(RISCV)/bin/riscv64-unknown-linux-gnu- 68 | ARCH=arch/riscv 69 | 70 | # compiler selection for platform 71 | AR=$(PREFIX)gcc-ar 72 | CC=$(PREFIX)gcc 73 | LD=$(PREFIX)ld 74 | OD=$(PREFIX)objdump 75 | OC=$(PREFIX)objcopy 76 | GDB=$(PREFIX)gdb 77 | 78 | LDFLAGS+=-L$(RISCV)/riscv64-unknown-linux-gnu/lib64/lp64d \ 79 | -L$(RISCV)/riscv64-unknown-linux-gnu/lib64 \ 80 | -L$(RISCV)/riscv64-unknown-linux-gnu/lib 81 | RUN=$(RISCV)/bin/spike 82 | #RUNARGS=--isa=rv64imacn 83 | RUNARGS= 84 | DEBUGGER=$(RISCV)/bin/spike 85 | #DEBUGARGS=--isa=rv64imacn -d 86 | DEBUGARGS=-d 87 | 88 | #CCFLAGS+=-mabi=lp64 -march=rv64imac 89 | #CCFLAGS+=-mabi=lp64d -march=rv64imafdc 90 | 91 | CCFLAGS+=-DFAKE_TLS_SWAP -DSYSCALL_SANDBOXING 92 | ifeq ($(SIM),pk) 93 | 94 | CCFLAGS+=-DPROXYKERNEL #-DFAKE_PKEY_SYSCALLS 95 | RUN_DEPS= 96 | RUN_BIN=$(SDKBASE)riscv-pk/build/pk $(MAIN) 97 | DEBUG_BIN=$(RUN_BIN) 98 | #LDFLAGS+=-static 99 | 100 | else # SIM=pk 101 | 102 | CCFLAGS+= 103 | RUN_DEPS=linux 104 | RUN_BIN=$(SDKBASE)bbl 105 | 106 | endif # SIM=pk 107 | 108 | #fvisibility=hidden might help aviding GOT stuff 109 | CCFLAGS+=-fvisibility=hidden 110 | 111 | #LDFLAGS+=-flto 112 | #CCFLAGS+=-flto 113 | 114 | endif # PLATFORM=riscv 115 | 116 | ######################################################################## 117 | # x86_64 118 | ######################################################################## 119 | ifeq ($(PLATFORM),x86_64) 120 | PREFIX= 121 | 122 | # compiler selection for platform 123 | AR=$(PREFIX)gcc-ar 124 | CC=$(PREFIX)clang 125 | LD=$(PREFIX)ld 126 | OD=$(PREFIX)objdump 127 | OC=$(PREFIX)objcopy 128 | GDB=$(PREFIX)gdb 129 | 130 | ARCH=arch/x86_64 131 | #CCFLAGS+=-Werror 132 | 133 | ifeq ($(SIM),pk) 134 | 135 | CCFLAGS+=-DFAKE_MPK_REGISTER 136 | CCFLAGS+=-DFAKE_PKEY_SYSCALLS 137 | CCFLAGS+=-DFAKE_MPK_EXCEPTION 138 | CCFLAGS+=-DFAKE_TLS_SWAP 139 | 140 | else # SIM 141 | 142 | # Since we cannot build x86 hardware 143 | CCFLAGS+=-DFAKE_MPK_EXCEPTION 144 | CCFLAGS+=-DFAKE_TLS_SWAP 145 | 146 | endif # SIM 147 | LDFLAGS+= 148 | RUN_DEPS= 149 | RUN= 150 | RUNARGS= 151 | RUN_BIN= 152 | ifeq ($(PRELOAD),1) 153 | RUN_BIN+=LD_PRELOAD=$(CURDIR)/libpku.so 154 | #RUN_BIN+=LD_PRELOAD=$(CURDIR)/libpk.so 155 | endif 156 | RUN_BIN+=LD_LIBRARY_PATH=$(CURDIR) setarch x86_64 --addr-no-randomize ./$(MAIN) 157 | DEBUGGER=./gdbscreen.sh gdb 158 | DEBUGARGS= 159 | DEBUG_BIN=./$(MAIN) 160 | endif # PLATFORM=x86_64 161 | ######################################################################## 162 | ifndef ARCH 163 | $(error Unknown platform $(PLATFORM)) 164 | endif 165 | ######################################################################## 166 | AWK=awk 167 | LDSCRIPT=$(ARCH)/linker 168 | 169 | # FLAGS FOR CC AND LD 170 | CCFLAGS+=-I. -Ipk -Ipk/$(ARCH) -Iuser -Iuser/$(ARCH) -I../syscall_hook 171 | CFLAGS+=-std=gnu11 172 | CXXFLAGS+=-std=c++11 173 | CCFLAGS+=-fPIC 174 | CCFLAGS+=-Wall 175 | CCFLAGS+=-Werror=implicit-function-declaration 176 | CCFLAGS+=-Werror=pointer-arith 177 | CCFLAGS+=-Werror=format 178 | CCFLAGS+=-Wpointer-arith 179 | CCFLAGS+=-Werror=int-conversion 180 | CCFLAGS+=-pthread 181 | #CCFLAGS+=-flto 182 | CCFLAGS+=-g 183 | #CCFLAGS+=-O3 184 | #CCFLAGS+=-O 185 | CCFLAGS+=-Os 186 | 187 | 188 | CCFLAGS+=-DADDITIONAL_DEBUG_CHECKS 189 | CCFLAGS+=-DDEBUG__CSR 190 | #CCFLAGS+=-DRELEASE 191 | #CCFLAGS+=-DTIMING 192 | 193 | #LDFLAGS:=$(CCFLAGS) $(LDFLAGS) 194 | # 195 | #CCFLAGS+=-Wextra 196 | #CCFLAGS+=-pedantic 197 | #LDFLAGS+=-no-pie 198 | #LDFLAGS+=-pie 199 | #LDFLAGS+=-Wl,-z,relro 200 | ifneq ($(SHARED),1) 201 | LDFLAGS+=-static # or alternatively sudo ln -s ${RISCV}/sysroot/lib/ld-linux-riscv64-lp64d.so.1 /lib 202 | endif 203 | #LDFLAGS+=-flto 204 | 205 | PK_OBJ=pk/pk_handler_generic.o pk/$(ARCH)/pk_handler_c.o pk/$(ARCH)/pk_handler.o 206 | PKU_OBJ=pk/pku_handler_generic.o pk/$(ARCH)/pku_handler_c.o pk/$(ARCH)/pku_handler.o pk/mprotect.o 207 | 208 | ifeq ($(SHARED),1) 209 | PKSO=libpk.so 210 | PKUSO=libpku.so 211 | MAIN=x.elf-shared 212 | else # SHARED 213 | PK=pk.a 214 | PKU=pku.a 215 | MAIN=x.elf 216 | endif # SHARED 217 | ######################################################################## 218 | all: $(MAIN) 219 | 220 | USR_OBJ=main.o \ 221 | user/$(ARCH)/tests.o \ 222 | user/test_ecalls.o \ 223 | user/test0.o \ 224 | user/test1_api.o \ 225 | user/$(ARCH)/test2.o user/$(ARCH)/test2_ecall.o \ 226 | user/test3.o \ 227 | user/test4_pthread.o \ 228 | user/test5.o \ 229 | user/test6.o \ 230 | user/bench.o 231 | USR=usr.a 232 | 233 | .PHONY: all clean clean-all run debug linux run_quiet run_deps bench-pk bench-x86 234 | 235 | $(LDSCRIPT).ld: 236 | mkdir -p $(dir $(LDSCRIPT).%) 237 | $(LD) --verbose > $@.tmp 238 | @./patch_ld.sh $@.tmp 239 | @mv $@.tmp $@ 240 | 241 | $(LDSCRIPT).lds: 242 | mkdir -p $(dir $(LDSCRIPT).%) 243 | $(LD) -shared --verbose > $@.tmp 244 | @./patch_ld.sh $@.tmp 245 | @mv $@.tmp $@ 246 | 247 | %.o: %.c 248 | $(CC) $(CFLAGS) $(CCFLAGS) -c $^ -o $@ 249 | 250 | %.o: %.cc 251 | $(CC) $(CXXFLAGS) $(CCFLAGS) -c $^ -o $@ 252 | 253 | %.o: %.S 254 | $(CC) $(CCFLAGS) -c $^ -o $@ 255 | 256 | $(PK): $(PK_OBJ) 257 | $(AR) -crs $@ $^ 258 | 259 | $(PKU): $(PKU_OBJ) 260 | $(AR) -crs $@ $^ 261 | 262 | $(PKSO): $(PK_OBJ) 263 | $(CC) -shared -o $@ $(CCFLAGS) $^ $(LDLIBS) 264 | 265 | $(PKUSO): $(PKU_OBJ) | $(PKSO) 266 | $(CC) -shared -o $@ $(CCFLAGS) $^ $(LDLIBS) -L. -lpk 267 | 268 | $(USR): $(USR_OBJ) 269 | $(AR) -crs $@ $^ 270 | # clang 7.0.0 workaround --remove-section .llvm_addrsig because of: 271 | # https://sourceware.org/bugzilla/show_bug.cgi?id=23788 272 | $(OC) --remove-section .llvm_addrsig --redefine-syms=rename_pk.lst $@ 273 | 274 | # We mis-use order-only prerequisites to use separate linker script ($|) 275 | # from the object files ($^) 276 | # We enclose all object files into --start-group/--end-group to avoid 277 | # problems with the link order 278 | 279 | ifeq ($(SHARED),1) 280 | 281 | $(MAIN): $(USR) | $(PKSO) $(PKUSO) 282 | $(CC) -o $@ $(LDFLAGS) -Wl,--start-group $^ $(PKSO) $(PKUSO) -Wl,--end-group $(LDLIBS) 283 | 284 | else 285 | 286 | $(MAIN): $(USR) $(PK) $(PKU) | $(LDSCRIPT).ld 287 | $(CC) -o $@ $(LDFLAGS) -Wl,-T $| -Wl,--start-group $^ -Wl,--end-group $(LDLIBS) 288 | 289 | endif 290 | 291 | debug: $(MAIN) 292 | $(OD) -t $(MAIN) | grep '[ _]pk_\|ecall\|_debug' | awk '{print "until pc 0 "$$1" # "$$(NF)}' | sort -k6 293 | $(DEBUGGER) $(DEBUGARGS) $(DEBUG_BIN) 294 | 295 | linux: $(MAIN) 296 | cp $^ $(SDKBASE)rootfs/$^ 297 | #riscv64-unknown-linux-gnu-strip -R .comment --strip-unneeded --strip-all $(SDKBASE)rootfs/$^ 298 | make -C $(SDKBASE) bbl bbl.bin 299 | 300 | run_deps: $(RUN_DEPS) 301 | 302 | run: $(MAIN) $(RUN_DEPS) 303 | $(RUN) $(RUNARGS) $(RUN_BIN) 304 | 305 | run_quiet: $(MAIN) $(RUN_DEPS) 306 | $(RUN) $(RUNARGS) $(RUN_BIN) 2>/dev/null 307 | 308 | ifeq ($(SHARED),1) 309 | 310 | install: $(PKSO) $(PKUSO) 311 | @echo "Installing '$^' into '$(INSTALLDIR)'" 312 | sudo cp -t '$(INSTALLDIR)' $^ 313 | 314 | else 315 | 316 | install: $(PK) $(PKU) 317 | @echo "Installing '$^' into '$(INSTALLDIR)'" 318 | sudo cp -t '$(INSTALLDIR)' $^ 319 | 320 | endif 321 | 322 | clean: 323 | rm -f --preserve-root $(PK_OBJ) $(PKU_OBJ) $(USR_OBJ) $(PK) $(PKSO) $(PKU) $(PKUSO) $(USR) $(LDSCRIPT).ld pk_stats.csv results.csv $(MAIN) 324 | 325 | clean-all: clean 326 | rm -f --preserve-root $(SDKBASE)rootfs/$(MAIN) $(MAIN) core 327 | -------------------------------------------------------------------------------- /DonkyLib/user/bench.c: -------------------------------------------------------------------------------- 1 | #ifdef TIMING 2 | 3 | #include "pk.h" 4 | #include "pk_debug.h" //for timing 5 | // 6 | #include "tests.h" 7 | //#include "test1_api.h" 8 | #include "test2_ecall.h" 9 | #include "test3_ecall.h" 10 | //#include "test4_pthread.h" 11 | //#include "test5.h" 12 | //#include "bench.h" 13 | // 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | uint64_t get_minimum(timing_index idx) { 20 | uint64_t min = UINT64_MAX; 21 | for (size_t i = 0; i < NUM_TESTRUNS; i++) { 22 | if (timing_values[idx][i] < min) { 23 | min = timing_values[idx][i]; 24 | } 25 | } 26 | return min; 27 | } 28 | 29 | uint64_t get_maximum(timing_index idx) { 30 | uint64_t max = 0; 31 | for (size_t i = 0; i < NUM_TESTRUNS; i++) { 32 | if (timing_values[idx][i] > max) { 33 | max = timing_values[idx][i]; 34 | } 35 | } 36 | return max; 37 | } 38 | 39 | int timing_cmp(const void * a, const void * b) { 40 | return *(uint64_t*)a > *(uint64_t*)b; 41 | } 42 | 43 | double get_average(timing_index idx) { 44 | double avg = 0.0f; 45 | for (size_t i = 0; i < NUM_TESTRUNS; i++) { 46 | avg += (double)timing_values[idx][i] / (double)NUM_TESTRUNS; 47 | } 48 | return avg; 49 | } 50 | 51 | #ifdef PROXYKERNEL 52 | uint64_t get_median(timing_index idx) { 53 | //qsrot doesn't work in proxykernel 54 | return (uint64_t)get_average(idx); 55 | } 56 | #else 57 | uint64_t get_median(timing_index idx) { 58 | qsort(timing_values[idx], NUM_TESTRUNS, sizeof(uint64_t), timing_cmp); 59 | return timing_values[idx][NUM_TESTRUNS/2]; 60 | } 61 | #endif 62 | 63 | double get_variance(timing_index idx, double avg) { 64 | double var = 0.0f; 65 | for (size_t i = 0; i < NUM_TESTRUNS; i++) { 66 | var += pow(timing_values[idx][i] - avg, 2) / (double)NUM_TESTRUNS; 67 | } 68 | return var; 69 | } 70 | 71 | void timing_results(){ 72 | FILE* f = fopen("results.csv", "w"); 73 | if (!f) { 74 | perror("Could not open result file"); 75 | exit(1); 76 | } 77 | fprintf(f, "test;min;max;med;avg;std\n"); 78 | printf(">>> TIMING NUM_TESTRUNS: %u\n", NUM_TESTRUNS); 79 | printf(">>> TIMING NUM_NESTING_LEVEL: %u\n", NUM_NESTING_LEVEL); 80 | #if TIMING_HANDLER_C == 0 81 | for (size_t idx = 0; idx < TIMING_T_MAX; idx++) 82 | { 83 | #ifdef TIMING_MEASURE_MINIMUM 84 | printf(">>> TIMING RESULT for %s : %zu\n", TIMING_INDEX_TO_STR(idx), timing_values[idx]); 85 | #else 86 | uint64_t min = get_minimum(idx); 87 | uint64_t max = get_maximum(idx); 88 | uint64_t med = get_median(idx); 89 | double avg = get_average(idx); 90 | double var = get_variance(idx, avg); 91 | double std = sqrt(var); 92 | printf(">>> TIMING RESULT for %35s (min|max): %6zu|%6zu, median: %6zu, avg: %8.2f+-%8.2f", TIMING_INDEX_TO_STR(idx), min, max, med, avg, std); 93 | fprintf(f, "%s;%zu;%zu;%zu;%0.2f;%0.2f\n", TIMING_INDEX_TO_STR(idx), min, max, med, avg, std); 94 | //~ for (size_t j = 0; j < NUM_TESTRUNS; j++) { 95 | //~ printf("%zu ", timing_values[idx][j]); 96 | //~ } 97 | printf("\n"); 98 | #endif 99 | } 100 | #else // TIMING_HANDLER_C 101 | if(timing_min == UINT64_MAX){ 102 | timing_min = 0; 103 | } 104 | printf(">>> TIMING RESULT for TIMING_HANDLER_C (min): %zu\n", timing_min); 105 | #endif // TIMING_HANDLER_C 106 | fclose(f); 107 | } 108 | 109 | #if __x86_64 && !(FAKE_MPK_REGISTER) 110 | #define _test_pkru() \ 111 | /* eax holds pkru value, ecx,edx must be 0. */ \ 112 | __asm__ volatile( \ 113 | "xor %%ecx, %%ecx\n" \ 114 | "xor %%edx, %%edx\n" \ 115 | "mov $0, %%rax\n" \ 116 | "wrpkru\n" \ 117 | : : : "rax","rcx","rdx" \ 118 | ) 119 | #elif __x86_64 && FAKE_MPK_REGISTER 120 | #define _test_pkru() 121 | #else 122 | #define _test_pkru() \ 123 | __asm__ volatile( \ 124 | "li t0, 0;" \ 125 | ) 126 | #endif 127 | 128 | void bench_preinit(){ 129 | printf("bench_preinit()\n"); 130 | 131 | printf("before test getpid\n"); 132 | getpid(); // warmup 133 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 134 | { 135 | uint64_t time = RDTSC(); 136 | getpid(); 137 | TIME_LOG(TIMING_GETPID, i, RDTSC() - time); 138 | } 139 | } 140 | 141 | void bench(){ 142 | printf("bench()\n"); 143 | 144 | volatile int xx; 145 | for (size_t i = 0; i < NUM_TESTRUNS; i++) { 146 | xx = i; 147 | } 148 | printf("before ecall_test3_time\n"); 149 | ecall_test3_time(); // warmup 150 | printf("after ecall_test3_time\n"); 151 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 152 | { 153 | uint64_t time0 = RDTSC(); 154 | uint64_t time1 = ecall_test3_time(); 155 | uint64_t time2 = RDTSC(); 156 | TIME_LOG(TIMING_ECALL_TEST3_TIME_COMPLETE, i, time2 - time0); 157 | TIME_LOG(TIMING_ECALL_TO_INSIDE, i, time1 - time0); 158 | TIME_LOG(TIMING_INSIDE_ECALL_TO_RETURN, i, time2 - time1); 159 | } 160 | 161 | printf("before test RDTSC\n"); 162 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 163 | { 164 | uint64_t time = RDTSC(); 165 | time = RDTSC() - time; 166 | TIME_LOG(TIMING_RDTSC, i, time); 167 | } 168 | 169 | printf("before test ecall_test_args\n"); 170 | ecall_test_args(0x10, 0x11, 0x12, 0x13, 0x14, 0x15); // warmup 171 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 172 | { 173 | uint64_t time = RDTSC(); 174 | ecall_test_args(0x10, 0x11, 0x12, 0x13, 0x14, 0x15); 175 | TIME_LOG(TIMING_ECALL_TEST_ARGS, i, RDTSC() - time); 176 | } 177 | 178 | printf("before test ecall_save_frame_overhead\n"); 179 | ecall_save_frame_prepare(); 180 | ecall_save_frame_overhead(); // warmup 181 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 182 | { 183 | uint64_t time = RDTSC(); 184 | ecall_save_frame_overhead(); 185 | TIME_LOG(TIMING_ECALL_SAVE_FRAME_OVERHEAD, i, RDTSC() - time); 186 | } 187 | 188 | //#if __x86_64 189 | printf("before test test_args\n"); 190 | //Note: Here we're calling test_args directly from root domain, 191 | // so we need to have the key for that domain loaded in the root domain! 192 | extern uint64_t test_args(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f); 193 | // 194 | //workaround for faulty hardware which fails when FETCHING protected code. 195 | int x = test_args(0x10, 0x11, 0x12, 0x13, 0x14, 0x15); // warmup 196 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 197 | { 198 | uint64_t time = RDTSC(); 199 | x = test_args(0x10, 0x11, 0x12, 0x13, 0x14, 0x15); 200 | TIME_LOG(TIMING_TEST_ARGS, i, RDTSC() - time); 201 | } 202 | //#endif 203 | 204 | printf("before test pk_simple_api_call\n"); 205 | pk_simple_api_call(1,2,3,4,5,6); // warmup 206 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 207 | { 208 | uint64_t time = RDTSC(); 209 | pk_simple_api_call(1,2,3,4,5,6); 210 | TIME_LOG(TIMING_SIMPLE_API_CALL, i, RDTSC() - time); 211 | } 212 | 213 | printf("before test ecall_test3_nested\n"); 214 | ecall_test3_nested(NUM_NESTING_LEVEL); // warmup 215 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 216 | { 217 | uint64_t time = RDTSC(); 218 | ecall_test3_nested(NUM_NESTING_LEVEL); 219 | TIME_LOG(TIMING_NESTING, i, RDTSC() - time); 220 | } 221 | 222 | // TODO: save and restore pkru before running this? 223 | printf("before test _test_pkru\n"); 224 | _test_pkru(); // warmup 225 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 226 | { 227 | uint64_t time = RDTSC(); 228 | _test_pkru(); 229 | TIME_LOG(TIMING_PKRU, i, RDTSC() - time); 230 | } 231 | 232 | #ifndef PROXYKERNEL 233 | printf("before test mprotect\n"); 234 | void* page = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); 235 | // warmup 236 | // do not directly call syscall(SYS_mprotect...) as this is blocked by our sysfilter 237 | mprotect(page, 4096, PROT_READ); 238 | mprotect(page, 4096, PROT_READ); 239 | assert(0 == mprotect(page, 4096, PROT_READ)); 240 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 241 | { 242 | uint64_t time = RDTSC(); 243 | mprotect(page, 4096, PROT_READ); 244 | TIME_LOG(TIMING_MPROTECT, i, RDTSC() - time); 245 | } 246 | #endif /* PROXYKERNEL */ 247 | 248 | printf("before test close\n"); 249 | close(0); // warmup 250 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 251 | { 252 | uint64_t time = RDTSC(); 253 | close(0); 254 | TIME_LOG(TIMING_CLOSE, i, RDTSC() - time); 255 | } 256 | 257 | printf("before test sysfilter-getpid\n"); 258 | int truepid = getpid(); 259 | printf("pid: %d\n", truepid); 260 | for (size_t i = 0; i < NUM_TESTRUNS; i++) 261 | { 262 | uint64_t time = RDTSC(); 263 | getpid(); 264 | TIME_LOG(TIMING_INTERPOSE_GETPID, i, RDTSC() - time); 265 | } 266 | } 267 | #endif // TIMING 268 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/x86_64/pk_handler.S: -------------------------------------------------------------------------------- 1 | #define __ASSEMBLY__ 2 | #include "pk_internal.h" 3 | 4 | #------------------------------------------------------------------------------ 5 | .section .pk,"a",@progbits 6 | 7 | .global pk_exception_handler # Externally visible 8 | .type pk_exception_handler, @function 9 | pk_exception_handler: 10 | 11 | .type _pk_exception_handler, @function 12 | _pk_exception_handler: # For internal rip-relative addressing 13 | 14 | #clear status flags (see A Tale of Two Worlds) 15 | cld # clear direction flag (for string operations) 16 | 17 | #ifndef FAKE_MPK_REGISTER 18 | # We're currently still on the unprotected user stack 19 | # we need to save/restore rax,rcx,rdx because they're 20 | # return values and arguments for ecalls/ocalls 21 | push %rax 22 | push %rcx 23 | push %rdx 24 | xor %ecx, %ecx # must be 0 for wrpkru 25 | xor %edx, %edx # must be 0 for wrpkru 26 | xor %eax, %eax # eax holds pkru value, which is also 0 to give 27 | wrpkru # full permissions to exception handler 28 | # Now, trusted TLS can be accessed 29 | pop %rdx 30 | pop %rcx 31 | pop %rax 32 | #endif 33 | # TODO: implement fs-swap in userspace 34 | # https://github.com/occlum/enable_rdfsbase 35 | 36 | # r10/r11 are caller-saved, so we can freely use them 37 | 38 | # save user stack pointer and switch to exception stack 39 | # both are located on trusted TLS 40 | 41 | mov PIC(_pk_ttls_offset), %r10 42 | # maybe this should only need to run in init? 43 | test %r10, %r10 44 | jz _pk_assert 45 | 46 | mov %rsp, %r11 # keep a copy of user stack in %r11 47 | mov %rsp, %fs:(%r10) # store user stack on %fs:(_pk_ttls_offset+0) 48 | add $8, %r10 49 | mov %fs:(%r10), %rsp # load exception stack from %fs:(_pk_ttls_offset+8) 50 | 51 | #################################################################### 52 | # From now on, we operate on the exception stack 53 | # Since our exception stack is always 16-byte aligned (psabi), 54 | # we need to push/pop an even number of times before doing C-calls 55 | # 56 | # The original user stack is available under %r11 57 | #################################################################### 58 | 59 | # Test type 60 | cmp $(TYPE_CALL), rdi_type 61 | je _pk_handle_ecall 62 | cmp $(TYPE_RET), rdi_type 63 | je _pk_handle_eret 64 | cmp $(TYPE_API), rdi_type 65 | je _pk_handle_apicall 66 | # TODO: handle normal exceptions with rdi_type=TYPE_EXCEPTION 67 | jmp _pk_assert 68 | 69 | ##################################### 70 | # dispatch API calls 71 | ##################################### 72 | _pk_handle_apicall: 73 | 74 | cmp $(API_TABLE_SIZE), rsi_id 75 | jge _pk_assert 76 | 77 | # Load correct API function pointer into %r10 78 | shl $3, rsi_id # Multiply id with WORDSIZE 79 | lea PIC(_pk_api_table), %r10 # Load API table 80 | add rsi_id, %r10 # Find correct entry 81 | 82 | # Restore original API arguments from user stack 83 | mov 16(%r11), %rdi # restore arg1 84 | mov 8(%r11), %rsi # restore arg2 85 | # arg3 ... arg6 are unmodified 86 | 87 | # Do actual API call 88 | call *(%r10) 89 | 90 | # Preserve return value of API call 91 | push %rax # store return register 1 92 | push %rdx # store return register 2 93 | 94 | call PCREL(_prepare_pkru_for_swap) 95 | 96 | jmp _pk_exception_handler_end_with_pushed_rax_rdx 97 | #pop %rdx 98 | #pop %rax 99 | #jmp _pk_exception_handler_end 100 | 101 | ##################################### 102 | # Dispatch ECALL 103 | ##################################### 104 | _pk_handle_ecall: 105 | 106 | # TODO: save and restore callee registers properly, since we cannot rely on the target 107 | # domain to properly preserve them. 108 | 109 | # Store caller-saved registers for c-function call 110 | # arg1 is on user stack 111 | # arg2 is on user stack 112 | push %rcx # store arg3 113 | push %rdx # store arg4 114 | push %r8 # store arg5 115 | push %r9 # store arg6 116 | push %r11 # store user stack of caller 117 | add $-0x8, %rsp # to avoid psabi misalignment 118 | 119 | call PCREL(_pk_exception_handler_arch_c) 120 | call PCREL(_prepare_pkru_for_swap) 121 | 122 | add $0x8, %rsp # to avoid psabi misalignment 123 | pop %r11 # restore user stack pointer of caller 124 | pop %r9 # restore arg6 125 | pop %r8 # restore arg5 126 | pop %rdx # restore arg4 127 | pop %rcx # restore arg3 128 | mov 8(%r11), %rsi # restore arg2 129 | mov 16(%r11), %rdi # restore arg1 130 | 131 | jmp _pk_exception_handler_end 132 | 133 | ##################################### 134 | # Dispatch ERET (return from ECALL) 135 | ##################################### 136 | _pk_handle_eret: 137 | 138 | # Store caller-saved return registers for c-function call 139 | push %rax # store return register 1 140 | push %rdx # store return register 2 141 | 142 | call PCREL(_pk_exception_handler_arch_c) 143 | call PCREL(_prepare_pkru_for_swap) 144 | 145 | jmp _pk_exception_handler_end_with_pushed_rax_rdx 146 | #pop %rdx 147 | #pop %rax 148 | #jmp _pk_exception_handler_end 149 | 150 | .global _pk_exception_handler_end 151 | _pk_exception_handler_end: 152 | push %rax 153 | push %rdx 154 | _pk_exception_handler_end_with_pushed_rax_rdx: 155 | 156 | mov %rsp, %rdx # keep our exception stack 157 | mov PIC(_pk_ttls_offset), %rax # restore user stack pointer 158 | mov %fs:(%rax), %rsp # restore user stack 159 | 160 | #################################################################### 161 | # We are back on the user stack 162 | # The exception stack is still accessible under %rdx 163 | #################################################################### 164 | 165 | # TODO: ensure that user stack has enough space for 4 slots! 166 | 167 | # Before doing the PKRU switch, push original %rax and %rdx 168 | # from exception stack to user stack 169 | # Since we still have access to user stack after updating PKRU, 170 | # we can restore them afterwards 171 | 172 | push 8(%rdx) # original %rax 173 | push 0(%rdx) # original %rdx 174 | 175 | #ifndef FAKE_MPK_REGISTER 176 | push %rcx 177 | 178 | add $24, %rax # Load current_pkru value into rax 179 | mov %fs:(%rax), %rax # from the third slot of pk_trusted_tls 180 | xor %ecx, %ecx # must be 0 for wrpkru 181 | xor %edx, %edx # must be 0 for wrpkru 182 | wrpkru 183 | 184 | #################################################################### 185 | # now we don't have access to exception stack anymore 186 | #################################################################### 187 | 188 | pop %rcx 189 | #endif 190 | pop %rdx 191 | pop %rax 192 | 193 | return_from_exception 194 | 195 | _pk_assert: 196 | # Prepare an assert frame with all registers on the _pk_assert_stack 197 | mov %rsp, PIC(_pk_assert_stack_top-8) # Save rsp before switching stack 198 | lea PIC(_pk_assert_stack_top-8), %rsp # Switch to _pk_assert_stack 199 | push %rbp 200 | push %rax 201 | push %rbx 202 | push %rcx 203 | push %rdx 204 | push %rsi 205 | push %rdi 206 | push %r8 207 | push %r9 208 | push %r10 209 | push %r11 210 | push %r12 211 | push %r13 212 | push %r14 213 | push %r15 214 | mov %rsp, %rdi 215 | #add $-8, %rdi 216 | jmp _pk_assert_c 217 | DIE 218 | 219 | .global pk_do_init 220 | .type pk_do_init, @function 221 | pk_do_init: 222 | # Call _pk_init 223 | add $-0x8, %rsp # to avoid psabi misalignment 224 | call PCREL(_pk_init) 225 | add $0x8, %rsp # to avoid psabi misalignment 226 | 227 | test %rax, %rax 228 | jnz pk_do_init_end 229 | 230 | # Load current_pkru config 231 | call PCREL(_prepare_pkru_for_swap) 232 | 233 | #ifndef FAKE_MPK_REGISTER 234 | # We are in a weird situation now: 235 | # * _pk_init operated on the thread stack rather than the exception stack 236 | # * _pk_init directly returned to us instead of the pk_exception_handler 237 | # So, we must take care to configure pkru properly ourselves 238 | # We still have full pkru access to all allocated keys 239 | # This is no problem, as an attacker that tampers with this code 240 | # could also have tampered with the protection handler before it is 241 | # properly initialized 242 | 243 | mov PIC(_pk_ttls_offset), %rax 244 | add $24, %rax # Load current_pkru value into rax 245 | mov %fs:(%rax), %rax # from the third slot of pk_trusted_tls 246 | xor %ecx, %ecx # must be 0 for wrpkru 247 | xor %edx, %edx # must be 0 for wrpkru 248 | wrpkru 249 | 250 | #################################################################### 251 | # now we don't have access to protection handler anymore 252 | #################################################################### 253 | xor %rax, %rax # return 0 254 | #endif 255 | pk_do_init_end: 256 | return_from_exception 257 | 258 | .bss 259 | .align WORDSIZE 260 | # bottom of stack 261 | .type _pk_assert_stack, @object 262 | .size _pk_assert_stack, 1024*WORDSIZE 263 | _pk_assert_stack: 264 | .skip 1024*WORDSIZE 265 | 266 | # end of stack (exclusive) 267 | .type _pk_assert_stack_top, @object 268 | .size _pk_assert_stack_top, 8 269 | _pk_assert_stack_top: 270 | 271 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/riscv/pk_handler.S: -------------------------------------------------------------------------------- 1 | #define __ASSEMBLY__ 2 | #include "pk_internal.h" 3 | 4 | #include "pku_api_wrapper.S" 5 | 6 | # ------------------------------------------------------------------------------ 7 | 8 | #define FRAMESIZE 136 9 | #define OFFSET_ra 128 10 | #define OFFSET_ra 128 11 | #define OFFSET_t0 120 12 | #define OFFSET_t1 112 13 | #define OFFSET_t2 104 14 | #define OFFSET_s0 96 15 | #define OFFSET_a0 88 16 | #define OFFSET_a1 80 17 | #define OFFSET_a2 72 18 | #define OFFSET_a3 64 19 | #define OFFSET_a4 56 20 | #define OFFSET_a5 48 21 | #define OFFSET_a6 40 22 | #define OFFSET_a7 32 23 | #define OFFSET_t3 24 24 | #define OFFSET_t4 16 25 | #define OFFSET_t5 8 26 | #define OFFSET_t6 0 27 | #define OFFSET_reg_a2_type OFFSET_a2 28 | 29 | #define SAVE(reg) sd reg, OFFSET_##reg (sp) 30 | #define RESTORE(reg) ld reg, OFFSET_##reg (sp) 31 | #define ALLOCATE_FRAME() addi sp, sp, -FRAMESIZE 32 | #define DELETE_FRAME() addi sp, sp, FRAMESIZE 33 | 34 | .macro CALL_C_HANDLER 35 | //csrrw zero, CSR_UEPC, ra 36 | call _pk_exception_handler_arch_c # returns the type in a0 37 | .endm 38 | # ------------------------------------------------------------------------------ 39 | .section .text 40 | 41 | //.section .pk_utvec_table,"ax",@progbits 42 | .section .pk_utvec_table,"a",@progbits 43 | //.section .pk,"a",@progbits 44 | .global pk_utvec_table 45 | .align 4 # must be aligned on a 4-byte boundary according to RISC-V priv spec 46 | pk_utvec_table: 47 | j fail // 0 48 | j fail // 1 49 | j fail // 2 50 | j fail // 3 51 | j fail // 4 52 | j fail // 5 53 | j fail // 6 54 | j fail // 7 55 | j _pk_handle_syscall // 8 56 | j fail // 9 57 | j fail // 10 58 | j fail // 11 59 | j fail // 12 60 | j fail // 13 61 | j _pk_exception_handler // 14 62 | j fail // 15 63 | .section .text 64 | 65 | # ------------------------------------------------------------------------------ 66 | 67 | .section .pk,"a",@progbits 68 | .global fail 69 | fail: 70 | DIE 71 | .section .text 72 | 73 | # ------------------------------------------------------------------------------ 74 | 75 | .section .pk,"a",@progbits 76 | .global _pk_exception_syscall 77 | .align 4 78 | _pk_exception_syscall: 79 | .skip 4 80 | 81 | .global _pk_exception_handler 82 | .align 4 83 | _pk_exception_handler: 84 | // load secure exception handler stack from uscratch: 85 | // we have to do this before saving any registers because otherwise we'd 86 | // write to an arbitrary memory location with higher priviledges 87 | // since the pkeys aren't checked in the exception handler mode 88 | csrrw sp, CSR_USCRATCH, sp 89 | 90 | //allocate frame and save the essential registers 91 | ALLOCATE_FRAME() 92 | SAVE(ra) 93 | // 94 | 95 | 96 | /* 97 | if utval != _pk_exception_handler 98 | set type to something invalid 99 | */ 100 | //Save registers which we're using for calculations. 101 | //Especially important for non-deliberate calls (=exceptions) 102 | SAVE(t5) 103 | SAVE(t6) 104 | SAVE(reg_a2_type) 105 | csrr t6, CSR_UTVAL 106 | la t5, _pk_exception_handler 107 | sub t6, t6, t5 108 | beqz t6, _pk_deliberate_call 109 | //not a deliberate call: set type 110 | li reg_a2_type, 0xFFFFFFFF //set type to something invalid (for the dispatching/comparison below) 111 | j _pk_normal // skip setting uepc, because it was a normal missing-key-exception and not a deliberate call 112 | _pk_deliberate_call: 113 | csrrw zero, CSR_UEPC, ra 114 | _pk_normal: 115 | //Restore registers used for earlier calculations. 116 | //NOTE: reg_a2_type is restored later in _pk_other_end 117 | //(because we still need its now-different value within the C handler) 118 | //TODO: we could simplify this by calling a different C handler since we do the dispatching in assembly anyways. 119 | RESTORE(t6) 120 | RESTORE(t5) 121 | 122 | // once we know if it's a a real exception (which means that reg_* are wrong) 123 | // we can do things more efficiently without saving all the regs 124 | 125 | //dispatch based on reg_a2_type 126 | // 0 = eret, 1 = ecall, 2 = api 127 | beqz reg_a2_type, _pk_handle_eret 128 | addi reg_a2_type, reg_a2_type, -1 129 | beqz reg_a2_type, _pk_handle_ecall 130 | addi reg_a2_type, reg_a2_type, -1 131 | beqz reg_a2_type, _pk_handle_api 132 | j _pk_handle_other 133 | 134 | _pk_handle_eret: 135 | RESTORE(reg_a2_type) 136 | //save necessary registers: return values 137 | //which are in t0..t2 because of GEN_CALLEE_WRAPPER 138 | SAVE(t0) 139 | SAVE(t1) 140 | //SAVE(t2) 141 | // 142 | CALL_C_HANDLER 143 | // 144 | j _pk_eret_end 145 | _pk_handle_ecall: 146 | RESTORE(reg_a2_type) 147 | 148 | //Save all arguments to the ecall function 149 | //because they will be overwritten by our C handler before we get to the call 150 | //Note a0..a2 arguments are actually in t0..t2 151 | SAVE(t0) 152 | SAVE(t1) 153 | SAVE(t2) 154 | 155 | SAVE(a3) 156 | SAVE(a4) 157 | SAVE(a5) 158 | SAVE(a6) 159 | SAVE(a7) 160 | 161 | //Note: No need to save callee-saved regs since they're handled by GEN_CALL_WRAPPER 162 | CALL_C_HANDLER 163 | j _pk_ecall_end 164 | _pk_handle_api: 165 | //RESTORE(reg_a2_type) // we don't need the type anymore 166 | //Note we could also use reg_a2_type instead of t5, but t5 is caller-saved anyway 167 | // 168 | //check if API id is valid (if id < api table size) 169 | li t5, API_TABLE_SIZE 170 | bge reg_a1_id, t5, fail // or _pk_handle_other for semi-graceful error handling? 171 | //look up API address in api table and store address in t5 172 | slli reg_a1_id, reg_a1_id, 3 // multiply id with wordsize so that we can use the result as an offset to the api table 173 | la t5, _pk_api_table // t5 = address of api table 174 | add t5, t5, reg_a1_id // t5 = address of _pk_api_table[reg_a1_id] 175 | ld t5, 0(t5) // t5 = address of API function 176 | // 177 | // restore api-function arguments from tmp-regs 178 | // Note: they were stored in tmp-regs in GEN_CALL_WRAPPER_API 179 | TMP_REGS_TO_ECALL 180 | //call API function 181 | jalr ra, t5, 0 // store return address in ra and jump to API function 182 | 183 | // at this point a0,a1 should contain the return value 184 | j clear_stack_and_end 185 | _pk_handle_other: 186 | li reg_a2_type, TYPE_EXCEPTION //NOTE: we could remove this if we swap out 0xFFFFFF above with TYPE_EXCEPTION 187 | //save all registers 188 | SAVE(t0) 189 | SAVE(t1) 190 | SAVE(t2) 191 | SAVE(s0) 192 | SAVE(a0) 193 | SAVE(a1) 194 | //NOTE: a2 (=reg_a2_type) is not being saved, because we did so earlier and by now it contains a different value. 195 | SAVE(a3) 196 | SAVE(a4) 197 | SAVE(a5) 198 | SAVE(a6) 199 | SAVE(a7) 200 | SAVE(t3) 201 | SAVE(t4) 202 | SAVE(t5) 203 | SAVE(t6) 204 | // 205 | CALL_C_HANDLER 206 | beqz a0, _pk_other_end // If _pk_exception_handler_arch_c returned non-zero, we want to call user_exception_handler(badaddr) 207 | csrr a0, utval // Load badaddr into a0 208 | SAVE(a0) 209 | j _pk_other_end 210 | 211 | _pk_eret_end: 212 | // 213 | // Returning from ECALL 214 | // discard all registers except return values 215 | RESTORE(t0) 216 | RESTORE(t1) 217 | //RESTORE(t2) 218 | // clear one stack frame from the exception handler, because this was the frame from the RET 219 | // but now we need to restore RA from the previous frame (where we had a CALL) 220 | DELETE_FRAME() 221 | // callee-saved regs are handled by GEN_CALL_WRAPPER, caller-saved by the caller 222 | // no other registers (apart form RA) have to be restored 223 | RESTORE(ra) 224 | // 225 | j clear_stack_and_end 226 | 227 | 228 | _pk_ecall_end: 229 | // just restore the arguments (or just all the a* regs) 230 | // also some t-regs because they may contain arguments 231 | // Note: we restore the arguments, because they were probably overwritten in 232 | // the C handler. but they're needed now that we actually call the ECALL function 233 | 234 | //Note a0..a2 arguments are actually in t0..t2 235 | RESTORE(t0) 236 | RESTORE(t1) 237 | RESTORE(t2) 238 | 239 | RESTORE(a3) 240 | RESTORE(a4) 241 | RESTORE(a5) 242 | RESTORE(a6) 243 | RESTORE(a7) 244 | // NOTE: we jump straight to the end, without deleting the stack frame, 245 | // because we need it later when we return from the ecall! 246 | j _pk_exception_handler_end 247 | 248 | _pk_other_end: 249 | // return from normal (missing-key-)exception and restore all the registers 250 | RESTORE(ra) 251 | RESTORE(t0) 252 | RESTORE(t1) 253 | RESTORE(t2) 254 | RESTORE(s0) 255 | RESTORE(a0) 256 | RESTORE(a1) 257 | RESTORE(a2) 258 | RESTORE(a3) 259 | RESTORE(a4) 260 | RESTORE(a5) 261 | RESTORE(a6) 262 | RESTORE(a7) 263 | RESTORE(t3) 264 | RESTORE(t4) 265 | RESTORE(t5) 266 | RESTORE(t6) 267 | 268 | clear_stack_and_end: 269 | DELETE_FRAME() 270 | 271 | .global _pk_exception_handler_end 272 | _pk_exception_handler_end: 273 | csrrw sp, CSR_USCRATCH, sp 274 | uret 275 | # ------------------------------------------------------------------------------ 276 | 277 | _pk_handle_syscall: 278 | csrrw sp, CSR_USCRATCH, sp 279 | sd t6, 8(sp) 280 | csrr t6, CSR_UEPC // Resume at instruction following ecall (UEPC+4) 281 | addi t6, t6, 4 282 | csrw CSR_UEPC, t6 283 | #ifdef SYSCALL_SANDBOXING 284 | // Check if current domain is root 285 | // If not, DIE 286 | csrr t6, CSR_MPK 287 | slli t6, t6, 1 // remove monitor bit 288 | srli t6, t6, 1+44 // remove four 11-bit slots 289 | // we're left with software-defined bits = DID 290 | beqz t6, syscall_allowed // root domain has DID=0, and is granted access 291 | #DIE # To enforce sysfilter, uncomment 292 | syscall_allowed: 293 | #endif 294 | ld t6, 8(sp) 295 | ecall 296 | csrrw sp, CSR_USCRATCH, sp 297 | uret 298 | # ------------------------------------------------------------------------------ 299 | .section .text 300 | # ------------------------------------------------------------------------------ 301 | // WARNING CODE BELOW is not protected! (outside of the pk section) 302 | -------------------------------------------------------------------------------- /DonkyLib/pk/pk_debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /**********************************************************************/ 4 | // For C only 5 | #ifndef __ASSEMBLY__ 6 | /**********************************************************************/ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | #define COLOR_RED "\x1b[31m" 20 | #define COLOR_GREEN "\x1b[32m" 21 | #define COLOR_YELLOW "\x1b[33m" 22 | #define COLOR_BLUE "\x1b[34m" 23 | #define COLOR_MAGENTA "\x1b[35m" 24 | #define COLOR_CYAN "\x1b[36m" 25 | #define COLOR_RESET "\x1b[0m" 26 | #define COLOR_INFO COLOR_CYAN 27 | 28 | //Note: Using stderr because unbuffered. 29 | //Otherwise this is needed: setbuf(stdout, NULL); 30 | 31 | //#define DONTPRINTANYTHING 32 | 33 | #ifdef DONTPRINTANYTHING 34 | #define ERROR_FAIL2(MESSAGE, ...) do { exit(EXIT_FAILURE); } while (0) 35 | #define ERROR_FAIL(MESSAGE, ...) do { exit(EXIT_FAILURE); } while (0) 36 | #define ERROR(MESSAGE, ...) do { ; } while (0) 37 | #define WARNING(MESSAGE, ...) do { ; } while (0) 38 | #define DEBUG_MPK(MESSAGE, ...) do { ; } while (0) 39 | #else 40 | #define ERROR_FAIL2(MESSAGE, ...) do { fprintf(stderr, COLOR_RED "0x%lx %d: %s:%d: " MESSAGE COLOR_RESET "\n", pthread_self(), getpid(), __FILE__, __LINE__, ##__VA_ARGS__); if(errno){perror(NULL);} exit(EXIT_FAILURE); } while (0) 41 | #define ERROR_FAIL(MESSAGE, ...) do { fprintf(stderr, COLOR_RED "0x%lx %d: %s: " MESSAGE COLOR_RESET "\n", pthread_self(), getpid(), __func__, ##__VA_ARGS__); if(errno){perror(NULL);} exit(EXIT_FAILURE); } while (0) 42 | #define ERROR(MESSAGE, ...) do { fprintf(stderr, COLOR_RED "0x%lx %d: %s: " MESSAGE COLOR_RESET "\n", pthread_self(), getpid(), __func__, ##__VA_ARGS__); } while (0) 43 | #define WARNING(MESSAGE, ...) do { fprintf(stderr, COLOR_YELLOW "0x%lx %d: %s: " MESSAGE COLOR_RESET "\n", pthread_self(), getpid(), __func__, ##__VA_ARGS__); } while (0) 44 | #define DEBUG_MPK(MESSAGE, ...) do { fprintf(stderr, COLOR_CYAN "0x%lx %d: %s: " MESSAGE COLOR_RESET "\n", pthread_self(), getpid(), __func__, ##__VA_ARGS__); } while (0) 45 | #endif 46 | //%s%d ... __FILE__, __LINE__ 47 | //------------------------------------------------------------------------------ 48 | //------------------------------------------------------------------------------ 49 | #ifdef RELEASE 50 | #ifdef DEBUG_MPK 51 | #undef DEBUG_MPK 52 | #define DEBUG_MPK(MESSAGE, ...) 53 | #endif 54 | 55 | #define assert_ifdebug(EXPRESSION) 56 | 57 | #ifdef ADDITIONAL_DEBUG_CHECKS 58 | #undef ADDITIONAL_DEBUG_CHECKS 59 | #endif /* ADDITIONAL_DEBUG_CHECKS */ 60 | 61 | #ifdef DEBUG__CSR 62 | #undef DEBUG__CSR 63 | #endif /* DEBUG__CSR */ 64 | 65 | #else /* RELEASE */ 66 | 67 | //#ifdef TIMING 68 | // #error "timing non-release is not advised" 69 | //#endif /* TIMING */ 70 | 71 | #define assert_ifdebug(EXPRESSION) assert(EXPRESSION) 72 | #endif /* RELEASE */ 73 | 74 | //------------------------------------------------------------------------------ 75 | static inline void PRINT_STACK(char* name, int size, const uint64_t * stack){ 76 | for (int i = size-1; i >= 0; i--) { 77 | uint64_t word = *(stack+i); 78 | printf("%s[%4d] = %8zx = %zu\n", name, i, word, word); 79 | } 80 | puts(""); 81 | } 82 | 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | 89 | static inline void print_maps() { 90 | #ifndef RELEASE 91 | char line[2048]; 92 | FILE * fp; 93 | DEBUG_MPK("print_maps()"); 94 | 95 | #ifndef PROXYKERNEL 96 | // print maps, including protection keys 97 | DEBUG_MPK("print_maps() smaps"); 98 | fp = fopen("/proc/self/smaps", "r"); 99 | if(fp == NULL){ 100 | ERROR_FAIL("Failed to fopen /proc/self/smaps"); 101 | } 102 | while (fgets(line, 2048, fp) != NULL) { 103 | if (strstr(line, "-") != NULL 104 | || (strstr(line, "ProtectionKey") != NULL && strstr(line, "ProtectionKey: 0") == NULL) 105 | //|| strstr(line, "Size") == line 106 | ) { 107 | fprintf(stderr, "%s", line); 108 | } 109 | } 110 | fclose(fp); 111 | #endif // !PROXYKERNEL 112 | 113 | #endif // !RELEASE 114 | } 115 | 116 | //------------------------------------------------------------------------------ 117 | #define assert_warn(expression) do { \ 118 | if(!(expression)) WARNING("assertion failed: %s", #expression); \ 119 | } while (0) 120 | 121 | //------------------------------------------------------------------------------ 122 | 123 | // for printing debug messages whenever CSRs are used 124 | #ifdef DEBUG__CSR 125 | #define DEBUG_CSR DEBUG_MPK 126 | #define IFDEBUG_CSR(CODE) CODE 127 | #else 128 | #define DEBUG_CSR(MESSAGE, ...) 129 | #define IFDEBUG_CSR(CODE) 130 | #endif 131 | 132 | //------------------------------------------------------------------------------ 133 | #if __riscv 134 | #ifdef TIMING_RDINSTRET 135 | __attribute__((always_inline)) static inline uint64_t RDTSC() { 136 | uint64_t res; 137 | __asm__ volatile ("fence.i"); 138 | __asm__ volatile ("rdinstret %0": "=r" (res)); 139 | __asm__ volatile ("fence.i"); 140 | return res; 141 | } 142 | #else 143 | __attribute__((always_inline)) static inline uint64_t RDTSC() { 144 | uint64_t res; 145 | //__asm__ volatile ("fence.i"); 146 | __asm__ volatile ("rdcycle %0": "=r" (res)); 147 | //__asm__ volatile ("fence.i"); 148 | return res; 149 | } 150 | #endif 151 | #else // x86 152 | __attribute__((always_inline)) static inline uint64_t RDTSC() { 153 | uint64_t a, d; 154 | //__asm__ volatile ("mfence"); 155 | __asm__ volatile ("lfence"); 156 | __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d)); 157 | a = (d<<32) | a; 158 | __asm__ volatile ("lfence"); 159 | //__asm__ volatile ("mfence"); 160 | return a; 161 | } 162 | #endif // __riscv/x86 163 | 164 | //------------------------------------------------------------------------------ 165 | 166 | #define C_STATIC_ASSERT(test) typedef char assertion_on_mystruct[( !!(test) )*2-1 ] 167 | 168 | //------------------------------------------------------------------------------ 169 | #ifdef TIMING 170 | //#ifndef RELEASE 171 | // #error "timing with debug checks is not advised" 172 | //#endif /* RELEASE */ 173 | 174 | //#ifdef ADDITIONAL_DEBUG_CHECKS 175 | // #error "timing with debug checks is not advised" 176 | //#endif /* ADDITIONAL_DEBUG_CHECKS */ 177 | 178 | //#ifdef DEBUG__CSR 179 | // #error "timing with debug checks is not advised" 180 | //#endif /* DEBUG__CSR */ 181 | 182 | //old: 1 table for all benchmarks 183 | //extern size_t timing_values_index; 184 | //typedef struct { 185 | // const char * name; 186 | // uint64_t time; 187 | //} _timing; 188 | //#define NUM_TIMING_VALUES 64 //should be power of two 189 | //#define TIME_LOG(NAME,VAL) do { timing_values[timing_values_index].name = NAME; timing_values[timing_values_index].time = VAL; timing_values_index = (timing_values_index + 1) % NUM_TIMING_VALUES; } while (0) 190 | //#define TIME_START(NAME) uint64_t _time_##NAME = RDTSC(); 191 | //#define TIME_STOP(NAME) do { _time_##NAME = RDTSC() - _time_##NAME; TIME_LOG(#NAME,_time_##NAME); /*fprintf(stderr, COLOR_MAGENTA "\nTIME(" #NAME ") = %lu" COLOR_RESET "\n\n", _time_##NAME);*/ } while (0) 192 | 193 | extern uint64_t timing_min; 194 | extern uint64_t timing_tmp; 195 | //Assertion so that we dont accidentally time things that are timing things themselves 196 | #define TIME_START(x) if ((x) == 1) { assert(timing_tmp == 0); timing_tmp = RDTSC(); } 197 | #define TIME_STOP(x) if ((x) == 1) { timing_tmp = RDTSC() - timing_tmp; if(timing_tmp < timing_min ) { timing_min = timing_tmp; } assert(timing_tmp != 0); timing_tmp = 0; } 198 | 199 | #ifndef TIMING_HANDLER_C 200 | #define TIMING_HANDLER_C 0 201 | #else 202 | #define TIMING_HANDLER_C_TYPE TYPE_RET 203 | #endif 204 | 205 | typedef enum { 206 | TIMING_SIMPLE_API_CALL = 0, 207 | TIMING_ECALL_TEST_ARGS, 208 | TIMING_ECALL_SAVE_FRAME_OVERHEAD, 209 | TIMING_TEST_ARGS, 210 | TIMING_NESTING, 211 | TIMING_ECALL_TEST3_TIME_COMPLETE, 212 | TIMING_ECALL_TO_INSIDE, 213 | TIMING_INSIDE_ECALL_TO_RETURN, 214 | TIMING_MPROTECT, 215 | TIMING_GETPID, 216 | TIMING_INTERPOSE_GETPID, 217 | TIMING_CLOSE, 218 | TIMING_PKRU, 219 | TIMING_RDTSC, 220 | TIMING_T_MAX //must be last line with highest number 221 | } timing_index; 222 | 223 | __attribute__((always_inline)) static inline char * TIMING_INDEX_TO_STR(timing_index idx) { 224 | switch (idx) 225 | { 226 | #define CASE(x) case x: return ((char*)#x) + 7; break; /* +7 to cut away "TIMING_" */ 227 | CASE(TIMING_SIMPLE_API_CALL) 228 | CASE(TIMING_ECALL_TEST_ARGS) 229 | CASE(TIMING_TEST_ARGS) 230 | CASE(TIMING_ECALL_SAVE_FRAME_OVERHEAD) 231 | CASE(TIMING_NESTING) 232 | CASE(TIMING_ECALL_TEST3_TIME_COMPLETE) 233 | CASE(TIMING_ECALL_TO_INSIDE) 234 | CASE(TIMING_INSIDE_ECALL_TO_RETURN) 235 | CASE(TIMING_MPROTECT) 236 | CASE(TIMING_GETPID) 237 | CASE(TIMING_INTERPOSE_GETPID) 238 | CASE(TIMING_CLOSE) 239 | CASE(TIMING_PKRU) 240 | CASE(TIMING_RDTSC) 241 | #undef CASE 242 | default: break; 243 | } 244 | assert(0); 245 | return NULL; 246 | } 247 | 248 | #define NUM_TESTRUNS (1000) 249 | #define NUM_NESTING_LEVEL 10 250 | 251 | #ifdef TIMING_MEASURE_MINIMUM 252 | extern uint64_t timing_values[TIMING_T_MAX]; 253 | __attribute__((always_inline)) static inline void TIME_LOG(timing_index idx, uint64_t value) { 254 | #ifdef TIMING 255 | assert(timing_tmp == 0); // in case the other timing thing runs 256 | if (timing_values[idx] == 0 || value < timing_values[idx]){ 257 | timing_values[idx] = value; 258 | } 259 | #endif 260 | } 261 | #else // TIMING_MEASURE_MINIMUM 262 | extern uint64_t timing_values[TIMING_T_MAX][NUM_TESTRUNS]; 263 | __attribute__((always_inline)) static inline void TIME_LOG(timing_index idx, size_t testrun, uint64_t value) { 264 | #ifdef TIMING 265 | assert(timing_tmp == 0); // in case the other timing thing runs 266 | assert(testrun < NUM_TESTRUNS); 267 | timing_values[idx][testrun] = value; 268 | #endif 269 | } 270 | #endif // TIMING_MEASURE_MINIMUM 271 | #endif // TIMING 272 | 273 | #ifdef __cplusplus 274 | } 275 | #endif 276 | 277 | #endif // __ASSEMBLY__ 278 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/x86_64/pk_handler_c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "pk_internal.h" 6 | 7 | #ifdef FAKE_MPK_REGISTER 8 | 9 | // should be thread-local, but since this is for emulation only, we don't care 10 | pkru_config_t PK_API emulated_mpk_reg = {0,}; 11 | 12 | #endif 13 | 14 | /** 15 | * Per-thread trusted storage 16 | * 17 | * It has the following properties 18 | * - placed inside TLS at a fixed offset 19 | * - the offset is computed during initialization, and stored in tls_trusted_offset 20 | * - protected dynamically in the same way as PK_DATA for static variables 21 | * Thus, it needs to be aligned on a page boundary, and be multiples of a page size 22 | * - Access only via TLS macro 23 | */ 24 | 25 | 26 | //------------------------------------------------------------------------------ 27 | // Public API functions 28 | //------------------------------------------------------------------------------ 29 | int PK_CODE _pk_init_arch() { 30 | DEBUG_MPK("_pk_init_arch"); 31 | 32 | //DEBUG_MPK("Original pkru-reg = 0x%lx", _read_pkru_reg()); 33 | // needed for sysfilter 34 | _write_pkru_reg(0x0); 35 | DEBUG_MPK("Handler pkru-reg = 0x%lx", _read_pkru_reg()); 36 | DEBUG_MPK("User pkru-reg (will be set when leaving pk handler) = 0x%lx", _read_pkru()); 37 | 38 | assert(pk_trusted_tls.current_did == DID_FOR_ROOT_DOMAIN); 39 | 40 | return 0; 41 | } 42 | //------------------------------------------------------------------------------ 43 | 44 | 45 | //------------------------------------------------------------------------------ 46 | // Internal functions 47 | //------------------------------------------------------------------------------ 48 | 49 | pkru_config_t PK_CODE _read_pkru() { 50 | return pk_data.domains[CURRENT_DID].default_config; 51 | } 52 | //------------------------------------------------------------------------------ 53 | 54 | void PK_CODE _prepare_pkru_for_swap() { 55 | pk_trusted_tls.current_pkru = pk_data.domains[CURRENT_DID].default_config; 56 | } 57 | //------------------------------------------------------------------------------ 58 | 59 | void PK_CODE _write_pkru(pkru_config_t new_config) { 60 | pk_data.domains[CURRENT_DID].default_config = new_config; 61 | } 62 | //------------------------------------------------------------------------------ 63 | 64 | void PK_CODE _pk_setup_exception_stack_arch(void* exception_stack) { 65 | // -2 because (in x86_64) the stack must be 16-byte aligned for psabi 66 | pk_trusted_tls.exception_stack_base = exception_stack; 67 | pk_trusted_tls.exception_stack = pk_trusted_tls.exception_stack_base + EXCEPTION_STACK_WORDS - 2; 68 | DEBUG_MPK("_pk_setup_exception_stack(%p)", exception_stack); 69 | 70 | if (pk_data.initialized) { 71 | assert(_pk_ttls_offset == (uint64_t)&pk_trusted_tls.backup_user_stack - (uint64_t)_get_fsbase()); 72 | } 73 | 74 | DEBUG_MPK("backup_user_stack = %p", pk_trusted_tls.backup_user_stack); 75 | DEBUG_MPK("exception_stack_top = %p", pk_trusted_tls.exception_stack); 76 | DEBUG_MPK("exception_stack_base = %p", pk_trusted_tls.exception_stack_base); 77 | } 78 | //------------------------------------------------------------------------------ 79 | 80 | void PK_CODE _pk_setup_exception_handler_arch() { 81 | } 82 | //------------------------------------------------------------------------------ 83 | 84 | void PK_CODE _pk_setup_domain_arch(int did, pkey_t pkey) { 85 | 86 | assert(pkey >= 0 && pkey < PK_NUM_KEYS); 87 | pk_data.domains[did].default_config = ((pkru_config_t)-1) << 2; // Deny all access except for KEY_FOR_UNPROTECTED 88 | pkru_config_t mask = (pkru_config_t)(PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) << (pkey * 2); 89 | pk_data.domains[did].default_config &= ~mask; // Allow only specified pkey 90 | DEBUG_MPK("Domain %d: pkru config is 0x%lx", did, pk_data.domains[did].default_config); 91 | } 92 | //------------------------------------------------------------------------------ 93 | 94 | bool PK_CODE _pk_is_key_loaded_arch(pkey_t pkey) { 95 | 96 | assert(pkey >= 0 && pkey < PK_NUM_KEYS); 97 | const pkru_config_t mask = (pkru_config_t)(PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) << (pkey*2); 98 | DEBUG_MPK("_pk_is_key_loaded_arch: mask = %zx", mask); 99 | 100 | for (size_t tid = 0; tid < NUM_THREADS; tid++) { 101 | if (NULL == pk_data.threads[tid]) { 102 | continue; 103 | } 104 | assert(pk_data.threads[tid]->init); 105 | pkru_config_t pkru = pk_data.threads[tid]->current_pkru; 106 | if ((pkru & mask) != mask) { 107 | WARNING("_pk_is_key_loaded_arch: thread[%zu] has key %d loaded", tid, pkey); 108 | WARNING("_pk_is_key_loaded_arch: pkru = %lx", pkru); 109 | WARNING("_pk_is_key_loaded_arch: mask = %lx", mask); 110 | errno = EPERM; 111 | return -1; 112 | } 113 | } 114 | return 0; 115 | } 116 | //------------------------------------------------------------------------------ 117 | 118 | int PK_CODE _pk_domain_load_key_arch(int did, pkey_t pkey, int slot, int perm) { 119 | if (slot != PK_SLOT_ANY && slot != PK_SLOT_NONE) { 120 | ERROR("Invalid slots"); 121 | errno = EINVAL; 122 | return -1; 123 | } 124 | 125 | if (pkey < 0 || pkey >= PK_NUM_KEYS) { 126 | ERROR("Invalid pkey %d. Must be between 0 and 15", pkey); 127 | errno = EINVAL; 128 | return -1; 129 | } 130 | 131 | if (PK_SLOT_NONE == slot) { 132 | // By disabling access, key is unloaded in x86 133 | perm = PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE; 134 | } 135 | 136 | if (perm & ~(PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE)) { 137 | ERROR("Invalid permissions. Only supports PKEY_DISABLE_ACCESS and PKEY_DISABLE_WRITE"); 138 | errno = EINVAL; 139 | return -1; 140 | } 141 | 142 | // PKRU layout: 143 | // AD: access (read+write) disabled 144 | // WD: write disabled 145 | // 31 1 0 bit position 146 | // |W|A|W|A|...|W|A|W|A|W|A|W|A| 147 | // |D|D|D|D|...|D|D|D|D|D|D|D|D| 148 | // |f|f|e|e|...|3|3|2|2|1|1|0|0| 149 | 150 | pkru_config_t pkru = pk_data.domains[did].default_config; 151 | DEBUG_MPK("Old config: 0x%lx", pkru); 152 | pkru_config_t nkey = (pkru_config_t)perm << (pkey*2); 153 | const pkru_config_t mask = (pkru_config_t)(PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) << (pkey*2); 154 | DEBUG_MPK("Old register: 0x%lx", pkru); 155 | 156 | // apply new key to corresponding slots 157 | DEBUG_MPK("Mask 0x%lx", mask); 158 | DEBUG_MPK("Nkey 0x%lx", nkey); 159 | pkru &= ~mask; 160 | pkru |= nkey; 161 | 162 | DEBUG_MPK("New register: 0x%lx", pkru); 163 | 164 | //update config 165 | pk_data.domains[did].default_config = pkru; 166 | return 0; 167 | } 168 | //------------------------------------------------------------------------------ 169 | 170 | // Ensure that _expected_return stack is not 16-byte but 8-byte aligned 171 | // This ensures that user stack gets 16-byte aligned for psabi 172 | // See _pk_exception_handler_arch_c 173 | C_STATIC_ASSERT((sizeof(_expected_return) % 16) == 8); 174 | C_STATIC_ASSERT((sizeof(_return_did) % 16) == 8); 175 | 176 | 177 | uint64_t PK_CODE _pk_exception_handler_arch_c(uint64_t type, uint64_t id){ 178 | #ifdef TIMING 179 | TIME_START(TIMING_HANDLER_C); 180 | #endif 181 | _pk_acquire_lock(); 182 | 183 | void * reentry = 0; 184 | // user stack is still misaligned by 8 bytes. _pk_exception_handler_c 185 | // pushes _expected_return struct on user stack s.t. it becomes 186 | // 16-byte aligned 187 | DEBUG_MPK("_pk_exception_handler_arch_c: stack=%p", pk_trusted_tls.backup_user_stack); 188 | 189 | if (type == TYPE_CALL) { 190 | assert_ifdebug(((uintptr_t)pk_trusted_tls.backup_user_stack % 16) == 8); // check stack misaligned 191 | reentry = (void*)*(pk_trusted_tls.backup_user_stack); // get original reentry point 192 | }else{ 193 | assert_ifdebug(((uintptr_t)pk_trusted_tls.backup_user_stack % 16) == 0); // check stack misaligned 194 | } 195 | 196 | uint64_t ret = _pk_exception_handler_unlocked(0, id, type, pk_trusted_tls.backup_user_stack, reentry); 197 | 198 | _pk_release_lock(); 199 | #ifdef TIMING 200 | TIME_STOP(TIMING_HANDLER_C); 201 | #endif 202 | return ret; 203 | } 204 | //------------------------------------------------------------------------------ 205 | 206 | void PK_CODE _pk_domain_switch_arch(int type, int target_did, pkru_config_t config, void* entry_point, uint64_t* target_stack) { 207 | DEBUG_MPK("_pk_domain_switch_arch(%d, %d, 0x%lx, %p, %p)", type, target_did, config, entry_point, target_stack); 208 | 209 | // config is switched in Assembler code via asm wrpkru(_read_pkru()) 210 | 211 | // Switch to target stack 212 | pk_trusted_tls.backup_user_stack = target_stack; 213 | 214 | // Switch to target did 215 | pk_trusted_tls.current_did = target_did; 216 | 217 | // For TYPE_RET, the original reentry point is already pushed on the 218 | // original caller's stack (target stack). For TYPE_CALL, we need to 219 | // push the entry point on the target stack manually. 220 | // This allows to use the 'ret' instruction 221 | if (type == TYPE_CALL) { 222 | assert_ifdebug(((uintptr_t)pk_trusted_tls.backup_user_stack % 16) == 8); 223 | 224 | pk_trusted_tls.backup_user_stack--; 225 | *(pk_trusted_tls.backup_user_stack) = (uint64_t)entry_point; 226 | 227 | // sp must be 16-b misaligned s.t. 'ret' aligns it for psabi 228 | assert_ifdebug(((uintptr_t)pk_trusted_tls.backup_user_stack % 16) == 0); 229 | } 230 | 231 | assert_ifdebug(_pk_current_did() == target_did); 232 | } 233 | //------------------------------------------------------------------------------ 234 | 235 | void* PK_CODE __attribute__((naked)) _pthread_init_function_asm(void * arg) { 236 | // We're entering here with the new user stack but in trusted mode 237 | // switch to trusted exception stack and call into C wrapper 238 | __asm__ volatile( 239 | "mov %0, %%rdi\n" // save start_routine as first argument 240 | "mov %%rsp, %%rsi\n" // save current_user_stack as second argument 241 | "mov %1, %%rsp\n" // load exception stack 242 | "mov %2, %%rbx\n" // load *arg for start_routine in callee-saved register (rbx) 243 | "call _pthread_init_function_c\n" 244 | "mov %%rbx, %%rdi\n" // load *arg as first argument for start_routine 245 | "jmp " S_PIC(_pk_exception_handler_end) "\n" 246 | : // no output operands 247 | : "m"(pk_data.pthread_arg.start_routine), 248 | "m"(pk_data.pthread_arg.exception_stack_top), 249 | "m"(pk_data.pthread_arg.arg) // Ensure we're using callee-saved register since GCC resolves %2 before the call. 250 | ); 251 | } 252 | //------------------------------------------------------------------------------ 253 | 254 | enum { 255 | rsp=0, 256 | rbp, 257 | rax, 258 | rbx, 259 | rcx, 260 | rdx, 261 | rsi, 262 | rdi, 263 | r8, 264 | r9, 265 | r10, 266 | r11, 267 | r12, 268 | r13, 269 | r14, 270 | r15, 271 | rend 272 | }; 273 | 274 | #define PRINT_REG(r, stack) ERROR("%4s: 0x%016lx", #r, stack[rend-1-r]) 275 | 276 | void PK_CODE __attribute__((noreturn)) _pk_assert_c(uintptr_t * assert_stack) { 277 | ERROR("_pk_assert_c DYING"); 278 | PRINT_REG(rsp, assert_stack); 279 | PRINT_REG(rbp, assert_stack); 280 | PRINT_REG(rax, assert_stack); 281 | PRINT_REG(rbx, assert_stack); 282 | PRINT_REG(rcx, assert_stack); 283 | PRINT_REG(rdx, assert_stack); 284 | PRINT_REG(rsi, assert_stack); 285 | PRINT_REG(rdi, assert_stack); 286 | PRINT_REG(r8 , assert_stack); 287 | PRINT_REG(r9 , assert_stack); 288 | PRINT_REG(r10, assert_stack); 289 | PRINT_REG(r11, assert_stack); 290 | PRINT_REG(r12, assert_stack); 291 | PRINT_REG(r13, assert_stack); 292 | PRINT_REG(r14, assert_stack); 293 | PRINT_REG(r15, assert_stack); 294 | assert(false); 295 | } 296 | //------------------------------------------------------------------------------ 297 | 298 | -------------------------------------------------------------------------------- /DonkyLib/user/test1_api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include "pk.h" 8 | #include "pk_debug.h" 9 | 10 | #ifndef SHARED 11 | extern unsigned char pk_initialized; 12 | #endif 13 | 14 | void test1_pkey_alloc() { 15 | #if __riscv && ! defined PROXYKERNEL 16 | //skip these tests on riscv-linux for now 17 | //because it's slow and freeing of keys has an issue 18 | return; 19 | #endif 20 | 21 | #ifndef SHARED 22 | assert(pk_initialized); 23 | #endif 24 | 25 | vkey_t keys[PK_NUM_KEYS]; 26 | vkey_t start_offset = -1; 27 | vkey_t ret; 28 | 29 | //find id of first possible key (for this test) 30 | start_offset = pk_pkey_alloc(0, 0); 31 | DEBUG_MPK("start_offset = %d", start_offset); 32 | assert(VKEY_INVALID != start_offset); 33 | ret = pk_pkey_free(start_offset); 34 | DEBUG_MPK("ret = %d", ret); 35 | assert(0 == ret); 36 | //start_offset should be roughly 3. pkeys start with 1. pk_init allocates two more keys, one for the root domain and one for the exception handler 37 | assert(start_offset > 0 && start_offset < 5); 38 | //start_offset should be 3 if the proxykernel is being userd, otherwsie maybe 4(?) because kernel might allocate 1 key. 39 | //assert(start_offset == 3); 40 | 41 | // Try to allocate all keys. 42 | // This must be the first test. 43 | for (size_t i = start_offset; i < PK_NUM_KEYS; i++) { 44 | keys[i] = pk_pkey_alloc(0, 0); 45 | DEBUG_MPK("Allocated %d\n", keys[i]); 46 | assert(VKEY_INVALID != keys[i]); 47 | } 48 | // The next call shall run out of pkeys 49 | ret = pk_pkey_alloc(0, 0); 50 | assert(VKEY_INVALID == ret && errno == ENOSPC); 51 | // This one should also fail, because all keys are allocated (and not shareable) 52 | ret = pk_pkey_alloc(PK_KEY_SHARED, 0); 53 | assert(VKEY_INVALID == ret && errno == ENOSPC); 54 | 55 | // Free all keys again (in reverse order because kernel implementation) 56 | for (size_t i = PK_NUM_KEYS - 1; i >= start_offset; i--) { 57 | assert(0 == pk_pkey_free(keys[i])); 58 | keys[i] = -1; 59 | } 60 | 61 | // Test sharing of keys 62 | #define NUM_SHARED_KEYS PK_NUM_KEYS 63 | 64 | #if (NUM_SHARED_KEYS + PK_NUM_KEYS) > NUM_KEYS_PER_DOMAIN 65 | #error "Too few keys per domain available to test" 66 | #endif 67 | 68 | vkey_t shared[NUM_SHARED_KEYS]; 69 | // Allocate all but one key 70 | for (size_t i = start_offset; i < PK_NUM_KEYS-1; i++) { 71 | keys[i] = pk_pkey_alloc(0, 0); 72 | DEBUG_MPK("Allocated %d\n", keys[i]); 73 | assert(VKEY_INVALID != keys[i]); 74 | } 75 | // Allocate shared key, which is the last real key 76 | keys[PK_NUM_KEYS-1] = pk_pkey_alloc(PK_KEY_SHARED, 0); 77 | assert(VKEY_INVALID != keys[PK_NUM_KEYS-1]); 78 | // Allocation of an unshared key must fail 79 | ret = pk_pkey_alloc(0, 0); 80 | assert(VKEY_INVALID == ret && errno == ENOSPC); 81 | // Shared keys can still be allocated 82 | for (size_t i = 0; i < NUM_SHARED_KEYS; i++) { 83 | shared[i] = pk_pkey_alloc(PK_KEY_SHARED, 0); 84 | DEBUG_MPK("Allocated shared key %d\n", shared[i]); 85 | assert(VKEY_INVALID != shared[i]); 86 | } 87 | 88 | // Free all keys again (in reverse order because kernel implementation) 89 | //for (size_t i = 0; i < NUM_SHARED_KEYS; i++) { 90 | for (size_t i = NUM_SHARED_KEYS - 1; i >= 0 && i != (size_t)-1; i--) { 91 | DEBUG_MPK("Freeing shared key %d\n", shared[i]); 92 | if(shared[i] == -1) continue; 93 | assert(0 == pk_pkey_free(shared[i])); 94 | shared[i] = -1; 95 | } 96 | 97 | // Free all keys again (in reverse order because kernel implementation) 98 | //for (size_t i = start_offset; i < PK_NUM_KEYS; i++) { 99 | for (size_t i = PK_NUM_KEYS - 1; i >= start_offset; i--) { 100 | DEBUG_MPK("Freeing key %d\n", keys[i]); 101 | if(keys[i] == -1) continue; 102 | assert(0 == pk_pkey_free(keys[i])); 103 | keys[i] = -1; 104 | //TODO there might be a double-free (which doesnt matter) 105 | } 106 | 107 | // Keys smaller than the start_offset should not be free-able (unless this test runs in the root domain) 108 | //for (size_t i = 0; i < start_offset; i++) { 109 | // assert(-1 == pk_pkey_free(i)); 110 | //} 111 | 112 | #undef NUM_SHARED_KEYS 113 | } 114 | 115 | void test1_api() { 116 | int did1, did2, did3; 117 | int pkey1, pkey2, pkey3, pkey4; 118 | int ret; 119 | 120 | test1_pkey_alloc(); 121 | 122 | //-------------------------------------------------------------------- 123 | // test pk_domain_create 124 | //-------------------------------------------------------------------- 125 | 126 | // invalid flags 127 | ret = pk_domain_create(-1); 128 | assert(ret == -1 && errno == EINVAL); 129 | ret = pk_domain_create(PK_KEY_OWNER); 130 | assert(ret == -1 && errno == EINVAL); 131 | ret = pk_domain_create(PK_KEY_COPY); 132 | assert(ret == -1 && errno == EINVAL); 133 | 134 | did1 = pk_domain_create(0); 135 | assert(did1 > 0); 136 | 137 | did2 = pk_domain_create(0); 138 | assert(did2 > 0 && did1 != did2); 139 | 140 | did3 = pk_domain_create(0); 141 | assert(did3 > 0 && did2 != did3); 142 | 143 | //-------------------------------------------------------------------- 144 | // test pk_pkey_alloc 145 | //-------------------------------------------------------------------- 146 | 147 | // invalid flags 148 | ret = pkey_alloc(-1, 0); 149 | assert(ret == -1 && errno == EINVAL); 150 | 151 | // invalid access_rights 152 | ret = pkey_alloc(0, 0x8); 153 | assert(ret == -1 && errno == EINVAL); 154 | 155 | // allocate four pkeys 156 | pkey1 = pkey_alloc(0, PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE); 157 | assert(pkey1 > 0); 158 | // an allocated key belongs to the current domain and can be loaded 159 | // infinitely often 160 | ret = pk_domain_load_key(pkey1, PK_SLOT_ANY, 0); 161 | assert(ret == 0); 162 | ret = pk_domain_load_key(pkey1, PK_SLOT_ANY, 0); 163 | assert(ret == 0); 164 | ret = pk_domain_load_key(pkey1, PK_SLOT_ANY, 0); 165 | assert(ret == 0); 166 | ret = pk_domain_load_key(pkey1, PK_SLOT_ANY, 0); 167 | assert(ret == 0); 168 | 169 | pkey2 = pkey_alloc(0, 0); 170 | assert(pkey2 > 0 && pkey1 != pkey2); 171 | ret = pk_domain_load_key(pkey2, PK_SLOT_ANY, 0); 172 | assert(ret == 0); 173 | 174 | pkey3 = pkey_alloc(0, 0); 175 | assert(pkey3 > 0 && pkey2 != pkey3); 176 | ret = pk_domain_load_key(pkey3, PK_SLOT_ANY, 0); 177 | assert(ret == 0); 178 | 179 | pkey4 = pkey_alloc(0, 0); 180 | assert(pkey4 > 0 && pkey3 != pkey4); 181 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 0); 182 | assert(ret == 0); 183 | 184 | //-------------------------------------------------------------------- 185 | // test pk_domain_load_key 186 | //-------------------------------------------------------------------- 187 | // test invalid pkey 188 | ret = pk_domain_load_key(0x7FFFFFFF, PK_SLOT_ANY, 0); 189 | assert(ret == -1 && errno == EACCES); 190 | 191 | // test permission flags 192 | // key can be loaded multiple times 193 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 0); 194 | assert(ret == 0); 195 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 0); 196 | assert(ret == 0); 197 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 0); 198 | assert(ret == 0); 199 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 0); 200 | assert(ret == 0); 201 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 0); 202 | assert(ret == 0); 203 | // invalid flags 204 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 1); 205 | assert(ret == -1 && errno == EINVAL); 206 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, -1); 207 | assert(ret == -1 && errno == EINVAL); 208 | 209 | //-------------------------------------------------------------------- 210 | // test pk_domain_assign_pkey(int did, int pkey, int flags, int access_rights) 211 | //-------------------------------------------------------------------- 212 | 213 | // invalid did 214 | ret = pk_domain_assign_pkey(100, pkey1, 0, 0); 215 | assert(ret == -1 && errno == EINVAL); 216 | 217 | // invalid pkey 218 | ret = pk_domain_assign_pkey(did1, -1, 0, 0); 219 | assert(ret == -1 && errno == EACCES); 220 | 221 | // invalid flags 222 | ret = pk_domain_assign_pkey(did1, pkey1, -1, 0); 223 | assert(ret == -1 && errno == EINVAL); 224 | 225 | // invalid access rights 226 | ret = pk_domain_assign_pkey(did1, pkey1, 0, -1); 227 | assert(ret == -1 && errno == EINVAL); 228 | 229 | // assign key to self. Should succeed an arbitrary number of times 230 | ret = pk_domain_assign_pkey(PK_DOMAIN_CURRENT, pkey1, PK_KEY_OWNER | PK_KEY_COPY, 0); 231 | assert(ret == 0); 232 | ret = pk_domain_assign_pkey(PK_DOMAIN_CURRENT, pkey1, PK_KEY_OWNER | PK_KEY_COPY, 0); 233 | assert(ret == 0); 234 | // PK_KEY_COPY is redundant when assigning to key to self. 235 | ret = pk_domain_assign_pkey(PK_DOMAIN_CURRENT, pkey1, PK_KEY_OWNER, 0); 236 | assert(ret == 0); 237 | ret = pk_domain_assign_pkey(PK_DOMAIN_CURRENT, pkey1, PK_KEY_OWNER, 0); 238 | assert(ret == 0); 239 | // we still have pkey 240 | ret = pk_domain_load_key(pkey1, PK_SLOT_ANY, 0); 241 | assert(ret == 0); 242 | 243 | // assign key to self without ownership transfer. Makes the key immutable 244 | ret = pk_domain_assign_pkey(PK_DOMAIN_CURRENT, pkey1, 0, 0); 245 | assert(ret == 0); 246 | // no ownership 247 | ret = pk_domain_assign_pkey(PK_DOMAIN_CURRENT, pkey1, PK_KEY_OWNER, 0); 248 | assert(ret == -1 && errno == EACCES); 249 | // we still have pkey, although without owner permission 250 | ret = pk_domain_load_key(pkey1, PK_SLOT_ANY, 0); 251 | assert(ret == 0); 252 | 253 | // copy key to other domains but keep ownership 254 | ret = pk_domain_assign_pkey(did2, pkey2, PK_KEY_COPY, 0); 255 | assert(ret == 0); 256 | ret = pk_domain_assign_pkey(did3, pkey3, PK_KEY_COPY, 0); 257 | assert(ret == 0); 258 | // we still have pkey 259 | ret = pk_domain_load_key(pkey2, PK_SLOT_ANY, 0); 260 | assert(ret == 0); 261 | // we still have pkey 262 | ret = pk_domain_load_key(pkey3, PK_SLOT_ANY, 0); 263 | assert(ret == 0); 264 | 265 | // migrate key to others 266 | ret = pk_domain_assign_pkey(did2, pkey4, PK_KEY_OWNER, 0); 267 | assert(ret == 0); 268 | // no ownership 269 | ret = pk_domain_assign_pkey(did3, pkey4, PK_KEY_OWNER, 0); 270 | assert(ret == -1 && errno == EACCES); 271 | // we lost access to pkey4 272 | ret = pk_domain_load_key(pkey4, PK_SLOT_ANY, 0); 273 | assert(ret == -1 && errno == EACCES); 274 | 275 | //-------------------------------------------------------------------- 276 | // test pk_pkey_free 277 | //-------------------------------------------------------------------- 278 | 279 | // invalid pkey 280 | ret = pkey_free(-1); 281 | assert(ret == -1 && errno == EACCES); 282 | 283 | // missing ownership 284 | ret = pkey_free(pkey1); 285 | assert(ret == -1 && errno == EACCES); 286 | ret = pkey_free(pkey4); 287 | assert(ret == -1 && errno == EACCES); 288 | 289 | #if __riscv && ! defined PROXYKERNEL 290 | //skip key-freeing, because kernel implementation can only free most recently allocated key for now. 291 | return; 292 | #endif 293 | 294 | // key is still in use, but unloaded within free 295 | ret = pkey_free(pkey2); 296 | assert(ret == 0); 297 | 298 | // free keys 299 | //ret = pk_domain_load_key(pkey2, PK_SLOT_NONE, 0); 300 | //assert(ret == 0); 301 | //ret = pkey_free(pkey2); 302 | //assert(ret == 0); 303 | ret = pk_domain_load_key(pkey3, PK_SLOT_NONE, 0); 304 | assert(ret == 0); 305 | ret = pkey_free(pkey3); 306 | assert(ret == 0); 307 | // we lost access to pkeys 308 | ret = pk_domain_load_key(pkey2, PK_SLOT_ANY, 0); 309 | assert(ret == -1 && errno == EACCES); 310 | ret = pk_domain_load_key(pkey3, PK_SLOT_ANY, 0); 311 | assert(ret == -1 && errno == EACCES); 312 | 313 | // double free 314 | ret = pkey_free(pkey3); 315 | assert(ret == -1 && errno == EACCES); 316 | 317 | // parent can act in place of child 318 | ret = pk_domain_register_ecall2(did1, PK_ECALL_ANY, test1_api); 319 | assert(ret >= 0); 320 | ret = pk_domain_register_ecall2(did1, PK_ECALL_ANY, test1_api); 321 | assert(ret >= 0); 322 | // until we release it 323 | ret = pk_domain_release_child(did1); 324 | assert(0 == ret); 325 | ret = pk_domain_release_child(did1); 326 | assert(-1 == ret && errno == EINVAL); 327 | ret = pk_domain_register_ecall2(did1, PK_ECALL_ANY, test1_api); 328 | assert(-1 == ret && errno == EACCES); 329 | } 330 | -------------------------------------------------------------------------------- /DonkyLib/user/test5.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "pk.h" 10 | #include "pk_debug.h" 11 | #include "test5.h" 12 | 13 | // Since assert might need to access stack, we need to give it full 14 | // pkru permission if test failed. This avoids recursive SEGFAULT 15 | //#define ASSERT(test) assert(test) 16 | #if __riscv 17 | #define ASSERT(test) do { if (!(test)) { ERROR_FAIL2("Assertion `" #test "' failed."); } } while (0) 18 | #else 19 | #define ASSERT(test) \ 20 | if (!(test)) { \ 21 | asm volatile( \ 22 | "xor %eax, %eax\n" \ 23 | "xor %ecx, %ecx\n" \ 24 | "xor %edx, %edx\n" \ 25 | "wrpkru\n" \ 26 | ); \ 27 | assert(test); \ 28 | } 29 | #endif 30 | 31 | void test_missing_key_exception(){ 32 | printf("testing missing-key-exception\n"); 33 | int test_size = 4096; 34 | int test_key = pk_pkey_alloc(0, 0); 35 | printf("test_key = %d\n", test_key); 36 | ASSERT(test_key > 0); 37 | void * test_memory = mmap(NULL, test_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, 0, 0); 38 | printf("test_memory = %p\n", test_memory); 39 | ASSERT(test_memory != MAP_FAILED); 40 | //printf("accessing %p", test_memory); 41 | //volatile int x1 = *(int*)test_memory; 42 | int ret = pk_pkey_mprotect(test_memory, test_size, PROT_READ | PROT_WRITE, test_key); 43 | ASSERT(ret == 0); 44 | ret = pk_domain_load_key(test_key, PK_SLOT_ANY, 0); 45 | ASSERT(ret == 0); 46 | printf("accessing %p\n", test_memory); 47 | volatile int x = *(int*)test_memory; 48 | (void)x; 49 | printf("testing missing-key-exception done\n"); 50 | } 51 | 52 | jmp_buf exception_buffer; 53 | bool access_test_running = false; 54 | 55 | int current_test = 0; 56 | size_t exc_count = 0; 57 | uintptr_t* childmem = NULL; 58 | uint32_t pkru = 0; 59 | 60 | #define ACCESS_MUST_FAIL(access) do { \ 61 | ASSERT(!access_test_running); \ 62 | access_test_running = true; \ 63 | SAVE_PKRU(); \ 64 | /* Store reentry point for exception handler */ \ 65 | int exc = setjmp(exception_buffer); \ 66 | if (0 == exc) { \ 67 | /* Do critical access */ \ 68 | do { access; } while(0); \ 69 | /* The access did not fail */ \ 70 | ASSERT(!"ACCESS_MUST_FAIL("#access")"); \ 71 | } else { \ 72 | /* We came from the exception handler */ \ 73 | ASSERT(1 == exc); \ 74 | exc_count++; \ 75 | } \ 76 | ASSERT(access_test_running); \ 77 | access_test_running = false; \ 78 | } while(0) 79 | 80 | #define ACCESS_MUST_NOT_FAIL(access) do { \ 81 | ASSERT(!access_test_running); \ 82 | access_test_running = true; \ 83 | SAVE_PKRU(); \ 84 | /* Store reentry point for exception handler */ \ 85 | int exc = setjmp(exception_buffer); \ 86 | if (0 == exc) { \ 87 | /* Do critical access */ \ 88 | do { access; } while(0); \ 89 | /* The access did not fail */ \ 90 | } else { \ 91 | /* We came from the exception handler */ \ 92 | ASSERT(!"ACCESS_MUST_NOT_FAIL("#access")"); \ 93 | } \ 94 | ASSERT(access_test_running); \ 95 | access_test_running = false; \ 96 | } while(0) 97 | 98 | #if __x86_64 99 | 100 | // x86: Linux modifies pkru to kernel's copy before calling segfault handler 101 | // We want to restore our own version of pkru 102 | #define SAVE_PKRU() do { pkru = _read_pkru_reg(); } while(0) 103 | #define RESTORE_PKRU() do { _write_pkru_reg(pkru); } while(0) 104 | 105 | #else 106 | 107 | // riscv does not allow writing pkru from user space, but it should 108 | // also not be needed. 109 | #define SAVE_PKRU() 110 | #define RESTORE_PKRU() 111 | 112 | #endif 113 | 114 | uintptr_t* pkey_isolation_child_alloc() { 115 | uintptr_t* child = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 116 | return child; 117 | } 118 | 119 | volatile uintptr_t* child_stack; 120 | uintptr_t* pkey_isolation_child_stack() { 121 | volatile uintptr_t stack[PAGESIZE/sizeof(uintptr_t)]; 122 | for (size_t i = 0; i < PAGESIZE/sizeof(uintptr_t); i++) { 123 | ACCESS_MUST_NOT_FAIL(stack[i] = i); 124 | } 125 | WARNING("pkey_isolation_child_stack returning %p", stack); 126 | child_stack = stack; 127 | return 0; 128 | } 129 | 130 | __thread uintptr_t mytls[PAGESIZE] = {0,}; 131 | 132 | void pkey_isolation_child_success(volatile uintptr_t* mem, int shared_pkey) { 133 | int ret; 134 | ret = pk_domain_load_key(shared_pkey, PK_SLOT_ANY, 0); 135 | ASSERT(0 == ret); 136 | ASSERT(mem); 137 | 138 | DEBUG_MPK("Hi, I am child, accessing mem %p", mem); 139 | for (size_t i = 0; i < 256/sizeof(uintptr_t); i++) { 140 | ACCESS_MUST_NOT_FAIL(mem[i] = i); 141 | } 142 | } 143 | 144 | void pkey_isolation_child_fail(volatile uintptr_t* mem, int shared_pkey) { 145 | int ret; 146 | ret = pk_domain_load_key(shared_pkey, PK_SLOT_ANY, 0); 147 | ASSERT(0 == ret); 148 | ASSERT(mem); 149 | 150 | DEBUG_MPK("Hi, I am child-fail, accessing mem %p", mem); 151 | for (size_t i = 0; i < 256/sizeof(uintptr_t); i++) { 152 | ACCESS_MUST_FAIL(mem[i] = i); 153 | } 154 | } 155 | 156 | void test5_exception_handler(void *bad_addr) { 157 | DEBUG_MPK("test5_exception_handler(%p)", bad_addr); 158 | 159 | if (access_test_running) { 160 | DEBUG_MPK("access_test_running: resuming with longjmp"); 161 | longjmp(exception_buffer, 1); 162 | // should not reach here 163 | ASSERT(false); 164 | } else { 165 | ERROR("test5_exception_handler: no test is running!"); 166 | print_maps(); 167 | ASSERT(false); 168 | } 169 | } 170 | 171 | void test5_segfault_handler(int sig, siginfo_t *si, void *unused) { 172 | psiginfo(si, "test5_segfault_handler"); 173 | ASSERT(SIGSEGV == sig); 174 | 175 | //DEBUG_MPK("test5_segfault_handler at %p. Reason: %d", si->si_addr, si->si_code); 176 | 177 | RESTORE_PKRU(); 178 | 179 | // Don't forget to unblock the signal 180 | sigset_t sigset; 181 | int ret; 182 | ret = sigemptyset(&sigset); ASSERT(0 == ret); 183 | ret = sigaddset(&sigset, SIGSEGV); ASSERT(0 == ret); 184 | ret = sigprocmask(SIG_UNBLOCK, &sigset, NULL); ASSERT(0 == ret); 185 | 186 | test5_exception_handler(si->si_addr); 187 | ASSERT(false); 188 | } 189 | 190 | #if __x86_64 191 | 192 | static void __attribute__((naked)) segfault_handler_asm() { 193 | // Linux segfault handler restores pkru to kernel's copy 194 | // Temporarily enable full PKRU permission to give 195 | // test5_segfault_handler stack access 196 | asm volatile( 197 | "xor %eax, %eax\n" // clear eax (full permission) 198 | "xor %ecx, %ecx\n" // clear ecx (needed for wrpkru) 199 | "xor %edx, %edx\n" // clear edx (needed for wrpkru) 200 | "wrpkru\n" 201 | "jmp test5_segfault_handler\n" 202 | ); 203 | } 204 | 205 | #else 206 | 207 | // riscv does not need asm handler 208 | #define segfault_handler_asm test5_segfault_handler 209 | 210 | #endif 211 | 212 | extern __thread uintptr_t pk_trusted_tls; 213 | 214 | void test_pkey_isolation() { 215 | int ret; 216 | 217 | DEBUG_MPK("Preparing for isolation tests"); 218 | 219 | // register segfault handler (needed for x86) 220 | struct sigaction sa; 221 | memset(&sa, 0, sizeof(sa)); 222 | sa.sa_flags = SA_SIGINFO; 223 | sa.sa_sigaction = segfault_handler_asm; 224 | ret = sigaction(SIGSEGV, &sa, NULL); 225 | ASSERT(0 == ret); 226 | 227 | ret = pk_register_exception_handler(test5_exception_handler); 228 | ASSERT(0 == ret); 229 | 230 | int did = pk_domain_create(0); 231 | ASSERT(did > 0); 232 | DEBUG_MPK("Child domain: %d", did); 233 | 234 | volatile uintptr_t* hostmem = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 235 | ASSERT(hostmem != MAP_FAILED); 236 | DEBUG_MPK("host memory: %p", hostmem); 237 | 238 | int shared_pkey = pkey_alloc(0, 0); 239 | ASSERT(shared_pkey > 0); 240 | DEBUG_MPK("Shared pkey: %d", shared_pkey); 241 | 242 | volatile uintptr_t* sharedmem = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 243 | ASSERT(sharedmem != MAP_FAILED); 244 | DEBUG_MPK("shared memory: %p", sharedmem); 245 | 246 | int ro_pkey = pkey_alloc(0, PKEY_DISABLE_WRITE); 247 | ASSERT(ro_pkey > 0); 248 | DEBUG_MPK("Read-only pkey: %d", ro_pkey); 249 | 250 | volatile uintptr_t currentval; // volatile prevents compiler from optimizing away the memory access 251 | 252 | // Read-only access test 253 | ret = pkey_mprotect((void*)sharedmem, 4096, PROT_READ | PROT_WRITE, ro_pkey); 254 | ASSERT(0 == ret); 255 | ret = pk_domain_load_key(ro_pkey, PK_SLOT_ANY, 0); 256 | ASSERT(0 == ret); 257 | ACCESS_MUST_NOT_FAIL(currentval = sharedmem[0]); // read access 258 | ACCESS_MUST_FAIL(sharedmem[0] = currentval); // write access 259 | 260 | // Read-write access test 261 | ret = pkey_mprotect((void*)sharedmem, 4096, PROT_READ | PROT_WRITE, shared_pkey); 262 | ret = pk_domain_load_key(shared_pkey, PK_SLOT_ANY, 0); 263 | ASSERT(0 == ret); 264 | ACCESS_MUST_NOT_FAIL(currentval = sharedmem[1]); // read access 265 | ACCESS_MUST_NOT_FAIL(sharedmem[1] = currentval); // write access 266 | 267 | // Access test across domains 268 | ret = pk_domain_assign_pkey(did, shared_pkey, PK_KEY_COPY, 0); 269 | ASSERT(0 == ret); 270 | 271 | ret = pk_domain_load_key(shared_pkey, PK_SLOT_ANY, 0); 272 | ASSERT(0 == ret); 273 | 274 | ret = ecall_register_pkey_isolation_child_alloc(did); 275 | ASSERT(ret >= 0); 276 | ret = ecall_register_pkey_isolation_child_stack(did); 277 | ASSERT(ret >= 0); 278 | ret = ecall_register_pkey_isolation_child_success(did); 279 | ASSERT(ret >= 0); 280 | ret = ecall_register_pkey_isolation_child_fail(did); 281 | ASSERT(ret >= 0); 282 | childmem = ecall_pkey_isolation_child_alloc(); 283 | ASSERT(MAP_FAILED != childmem); 284 | DEBUG_MPK("child memory: %p", childmem); 285 | 286 | // Let child access its own child memory 287 | ecall_pkey_isolation_child_success(childmem, shared_pkey); 288 | 289 | // Let child access shared memory 290 | ecall_pkey_isolation_child_success(sharedmem, shared_pkey); 291 | 292 | // Let child access host memory. Must fail 293 | ecall_pkey_isolation_child_fail(hostmem, shared_pkey); 294 | 295 | // Let child access host stack. Must fail 296 | uintptr_t stack_var[PAGESIZE/sizeof(uintptr_t)]; // some stack variable, should be protected 297 | ecall_pkey_isolation_child_fail(stack_var, shared_pkey); 298 | 299 | // Let child access child stack 300 | ecall_pkey_isolation_child_stack(); //writes to child_stack 301 | 302 | // Let host access child stack. Must fail 303 | for (size_t i = 0; i < PAGESIZE/sizeof(uintptr_t); i++) { 304 | uintptr_t val; 305 | /* We're accessing child-private memory */ 306 | ACCESS_MUST_FAIL(val = child_stack[i]); 307 | } 308 | 309 | // Let child access TLS. Must succeed 310 | ecall_pkey_isolation_child_success(mytls, shared_pkey); 311 | DEBUG_MPK("mytls %p", mytls); 312 | DEBUG_MPK("errno %p", &errno); 313 | 314 | uintptr_t* ttls = &pk_trusted_tls; 315 | #ifdef TLS_MISALIGNMENT_BUG 316 | ttls = (uintptr_t*)((char*)ttls + PAGESIZE); 317 | #endif 318 | DEBUG_MPK("ttls: %p", ttls); 319 | 320 | //pk_print_debug_info(); 321 | //print_maps(); 322 | 323 | // Let host access trusted TLS. Must fail 324 | for (size_t i = 0; i < PAGESIZE/sizeof(uintptr_t); i++) { 325 | volatile uintptr_t val; 326 | /* We're accessing pk-private memory */ 327 | ACCESS_MUST_FAIL(val = ttls[i]); 328 | } 329 | 330 | // Let host access trusted TLS. Must fail 331 | ecall_pkey_isolation_child_fail(ttls, shared_pkey); 332 | 333 | //test memory accesses with current pkey (non-child) 334 | for (size_t i = 0; i < 256/sizeof(uintptr_t); i++) { 335 | uintptr_t val; 336 | /* We're accessing our own memory */ 337 | ACCESS_MUST_NOT_FAIL(hostmem[i] = i); 338 | /* We're accessing shared memory */ 339 | ACCESS_MUST_NOT_FAIL(val = sharedmem[i]); 340 | ASSERT(val == i); 341 | /* We're trying to illegally access child memory */ 342 | ACCESS_MUST_FAIL(childmem[i] = i); 343 | } 344 | 345 | ret = munmap((void*)sharedmem, 4096); 346 | ASSERT(0 == ret); 347 | 348 | ret = pkey_free(ro_pkey); 349 | // TODO: cleanup shared_pkey and subdomain 350 | 351 | // Deregister signal handler 352 | memset(&sa, 0, sizeof(sa)); 353 | sa.sa_handler = SIG_DFL; 354 | ret = sigaction(SIGSEGV, &sa, NULL); 355 | ASSERT(0 == ret); 356 | } 357 | -------------------------------------------------------------------------------- /DonkyLib/pk/arch/riscv/pk_arch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pk_defs.h" 3 | 4 | //------------------------------------------------------------------------------ 5 | // Arch-specific API definitions 6 | //------------------------------------------------------------------------------ 7 | 8 | #define WORDSIZE 8 //sizeof(uint64_t) 9 | #define PAGESIZE 4096 10 | #define PAGEMASK (PAGESIZE-1) 11 | #define PK_NUM_KEYS 1023 // NOTE: 1023 is an invalid key. the kernel should never give this key away. 12 | 13 | // PK handler types 14 | // Do not change these values, as asm code would break! 15 | #define TYPE_RET 0 16 | #define TYPE_CALL 1 17 | #define TYPE_API 2 18 | #define TYPE_EXCEPTION 3 19 | 20 | // Distinguishes ECALLs and returns 21 | #define reg_a2_type a2 22 | // Holds the CALL ID 23 | #define reg_a1_id a1 24 | // Holds ms data (TODO: needed?) 25 | #define reg_a0_data a0 26 | 27 | // defines for PK API 28 | 29 | // CSR defines for interrupt handling 30 | #define CSR_USTATUS 0x000 31 | #define CSR_UIE 0x004 32 | #define CSR_UTVEC 0x005 33 | #define CSR_USCRATCH 0x040 34 | #define CSR_UEPC 0x041 35 | #define CSR_UCAUSE 0x042 36 | #define CSR_UTVAL 0x043 37 | #define CSR_UIP 0x044 38 | 39 | // CSR defines for arch ISA 40 | #define CSR_MISA 0x301 41 | #define CSR_UMISA 0x047 42 | 43 | // defines for MPK 44 | #define CSR_MPK 0x046 45 | #define CAUSE_MPKEY_MISMATCH_FAULT 0xe 46 | 47 | #ifdef SHARED 48 | // shared lib 49 | // To access global variables from assembler code, 50 | // they must be marked __attribute__((visibility("hidden"))). 51 | // Otherwise, they are globally exported. 52 | // We avoid this via CFLAGS=-fvisibility=hidden 53 | 54 | // Relocation types 55 | // http://www.mindfruit.co.uk/2012/06/relocations-relocations.html 56 | 57 | // TODO https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#pc-relative-symbol-addresses 58 | 59 | // Data accesses are done rip-relative 60 | #define PIC(x) x(pc) /* TODO */ 61 | // For cross-library calls 62 | #define PLT(x) x@plt 63 | //#define PCREL(x) x@pcrel 64 | // Intra-lib calls can be done directly (pc-relative), since they are 65 | // not exported (i.e. "hidden") 66 | #define PCREL(x) x 67 | // We did not manage to make _pk_exception_handler_end "hidden", so 68 | // use plt indirection instead 69 | #define S_PIC(x) #x"@plt" 70 | #else // SHARED 71 | #define PIC(x) x 72 | #define PLT(x) x 73 | #define PCREL(x) x 74 | #define S_PIC(x) #x 75 | #endif // SHARED 76 | 77 | /**********************************************************************/ 78 | // For C only 79 | #ifndef __ASSEMBLY__ 80 | /**********************************************************************/ 81 | 82 | #include 83 | 84 | #ifdef __cplusplus 85 | extern "C" { 86 | #endif 87 | 88 | typedef uint16_t pkey_t; 89 | 90 | typedef struct __attribute__((__packed__)) { 91 | uint slot_0_mpkey : 10; 92 | uint slot_0_wd : 1; 93 | uint slot_1_mpkey : 10; 94 | uint slot_1_wd : 1; 95 | uint slot_2_mpkey : 10; 96 | uint slot_2_wd : 1; 97 | uint slot_3_mpkey : 10; 98 | uint slot_3_wd : 1; 99 | uint sw_did : 8; 100 | uint sw_unused : 11; 101 | uint mode : 1; 102 | } pkru_config_t; 103 | 104 | 105 | #define GET_TLS_POINTER ((uintptr_t)_get_tp()) 106 | 107 | FORCE_INLINE uint64_t _get_tp() { 108 | register uint64_t ret asm("tp"); 109 | return ret; 110 | } 111 | 112 | #define CSRR(csr_id) ({uint64_t ret; asm volatile ("csrr %0, %1" : "=r"(ret) : "i"(csr_id)); ret;}) // GCC statement expression 113 | //~ __attribute__((always_inline)) static inline uint64_t CSRR(const uint64_t csr_id){ 114 | //~ uint64_t ret; 115 | //~ asm volatile ("csrr %0, %1" : "=r"(ret) : "i"(csr_id)); 116 | //~ return ret; 117 | //~ } 118 | 119 | FORCE_INLINE void CSRW(const uint64_t csr_id, const uint64_t val){ 120 | IFDEBUG_CSR({ 121 | char * s; 122 | switch (csr_id) 123 | { 124 | case CSR_USTATUS: s = "USTATUS"; break; 125 | case CSR_UIE: s = "UIE"; break; 126 | case CSR_UTVEC: s = "UTVEC"; break; 127 | case CSR_USCRATCH: s = "USCRATCH"; break; 128 | case CSR_UEPC: s = "UEPC"; break; 129 | case CSR_UCAUSE: s = "UCAUSE"; break; 130 | case CSR_UTVAL: s = "UTVAL"; break; 131 | case CSR_UIP: s = "UIP"; break; 132 | case CSR_MISA: s = "MISA"; break; 133 | case CSR_UMISA: s = "UMISA"; break; 134 | case CSR_MPK: s = "MPK"; break; 135 | default: s = "???"; break; 136 | } 137 | DEBUG_CSR("Setting CSR %s to 0x%zx", s, val); 138 | }); 139 | 140 | asm volatile ("csrw %0, %1" : : "i"(csr_id), "r"(val)); 141 | #ifdef ADDITIONAL_DEBUG_CHECKS 142 | uint64_t read = CSRR(csr_id); 143 | if(read != val){ 144 | ERROR_FAIL("Could not set CSR 0x%lx to 0x%lx. (its value is 0x%lx)", csr_id, val, read); 145 | } 146 | #endif 147 | } 148 | 149 | #define CSRW(x,y) CSRW((x), (uint64_t)(y)) 150 | 151 | typedef union{ 152 | pkru_config_t pkru; 153 | uint64_t pkru_as_int; 154 | } union_pkru_config_t; 155 | 156 | FORCE_INLINE uint64_t PKRU_TO_INT(pkru_config_t pkru) { 157 | union_pkru_config_t u; 158 | u.pkru = pkru; 159 | return u.pkru_as_int; 160 | } 161 | 162 | FORCE_INLINE pkru_config_t INT_TO_PKRU(uint64_t reg) { 163 | union_pkru_config_t u; 164 | u.pkru_as_int = reg; 165 | return u.pkru; 166 | } 167 | 168 | #ifdef FAKE_MPK_REGISTER 169 | extern pkru_config_t emulated_mpk_reg; 170 | FORCE_INLINE pkru_config_t _read_pkru_reg() { 171 | pkru_config_t copy = emulated_mpk_reg; 172 | return copy; 173 | } 174 | FORCE_INLINE void _write_pkru_reg(pkru_config_t new_config) { 175 | emulated_mpk_reg = new_config; 176 | } 177 | #else /* FAKE_MPK_REGISTER */ 178 | FORCE_INLINE pkru_config_t _read_pkru_reg() { 179 | uint64_t copy = CSRR(CSR_MPK); 180 | return INT_TO_PKRU(copy); 181 | } 182 | 183 | FORCE_INLINE void _write_pkru_reg(pkru_config_t new_config) { 184 | uint64_t reg = PKRU_TO_INT(new_config); 185 | CSRW(CSR_MPK, reg); 186 | // In theory we need an instruction-fence here, 187 | // unless we can guarantee that we execute more instructions before uret 188 | // such that the pipeline never contains invalid instructions? 189 | #ifdef ADDITIONAL_DEBUG_CHECKS 190 | if(CSRR(CSR_MPK) != reg){ 191 | ERROR_FAIL("Failed to set CSR_MPK to 0x%lx. Its value is 0x%lx", reg, CSRR(CSR_MPK)); 192 | } 193 | #endif 194 | assert_ifdebug(CSRR(CSR_MPK) == reg); 195 | } 196 | #endif /* FAKE_MPK_REGISTER */ 197 | 198 | 199 | // read/write pkru directly goes to the mpk register 200 | #define _read_pkru() _read_pkru_reg() 201 | #define _write_pkru(new_config) _write_pkru_reg(new_config) 202 | 203 | #define CURRENT_DID ({ assert_ifdebug(pk_trusted_tls.init); _read_pkru().sw_did;}) 204 | 205 | FORCE_INLINE void pk_print_reg_arch(pkru_config_t reg){ 206 | fprintf(stderr,"raw = 0x%zx, ", PKRU_TO_INT(reg)); 207 | fprintf(stderr,"(mode=%d), did = %4u, ", reg.mode, reg.sw_did); 208 | fprintf(stderr,"keys = [%4u](wd=%1u) [%4u](wd=%1u) [%4u](wd=%1u) [%4u](wd=%1u)", 209 | reg.slot_3_mpkey, reg.slot_3_wd, 210 | reg.slot_2_mpkey, reg.slot_2_wd, 211 | reg.slot_1_mpkey, reg.slot_1_wd, 212 | reg.slot_0_mpkey, reg.slot_0_wd 213 | ); 214 | printf("\n"); 215 | } 216 | 217 | FORCE_INLINE void pk_debug_usercheck_arch() { 218 | // If called from main, expected mode is 0 219 | pkru_config_t reg = _read_pkru(); 220 | assert_warn(reg.mode == 0); 221 | } 222 | //------------------------------------------------------------------------------ 223 | 224 | void PK_CODE _pk_exception_syscall(void); 225 | 226 | #ifdef __cplusplus 227 | } 228 | #endif 229 | 230 | /**********************************************************************/ 231 | // For ASM only 232 | #elif defined __ASSEMBLY__ 233 | /**********************************************************************/ 234 | 235 | .macro loadword reg label 236 | la \reg, \label 237 | ld \reg, 0(\reg) 238 | .endm 239 | 240 | .macro DIE 241 | ld t0, 0(zero) 242 | //j . 243 | .endm 244 | 245 | .macro SLOW_PUSH reg 246 | addi sp, sp, -8 247 | sd \reg, 0(sp) 248 | .endm 249 | 250 | .macro SLOW_POP reg 251 | ld \reg, 0(sp) 252 | addi sp, sp, 8 253 | .endm 254 | 255 | /* 256 | .macro simulate_exception 257 | // For now we have to simulate the exception, so we set ucause, uepc, mpk.mode 258 | // set ucause 259 | csrw CSR_UCAUSE, CAUSE_MPKEY_MISMATCH_FAULT 260 | 261 | // store return address (label 1) at uepc to simulate an exception? 262 | SLOW_PUSH ra 263 | la ra, 1f 264 | csrrw zero, CSR_UEPC, ra 265 | SLOW_POP ra 266 | .align 2 // uepc needs to be 2-byte aligned 267 | 1: 268 | .endm 269 | 270 | .macro trigger_exception_jump_simple 271 | j _pk_exception_handler 272 | .align 2 273 | .endm 274 | 275 | .macro trigger_exception_call 276 | SLOW_PUSH ra 277 | call _pk_exception_handler 278 | .align 2 // uepc needs to be 2-byte aligned 279 | SLOW_POP ra 280 | .endm 281 | */ 282 | 283 | .macro trigger_exception_jump 284 | SLOW_PUSH ra 285 | la ra, 1f //store return address in ra (will be in uepc later) 286 | j _pk_exception_handler 287 | .align 2 // uepc needs to be 2-byte aligned 288 | 1: SLOW_POP ra 289 | .endm 290 | 291 | .macro trigger_exception_load 292 | SLOW_PUSH ra 293 | la ra, 1f 294 | //Trigger exception by loading a word from the handler 295 | //Note: using unused caller-saved register. 296 | loadword t5 _pk_exception_handler 297 | //loadword t5 pk_initialized 298 | .align 2 // uepc needs to be 2-byte aligned 299 | 1: SLOW_POP ra 300 | .endm 301 | 302 | .macro trigger_exception 303 | //trigger_exception_jump 304 | trigger_exception_load 305 | .endm 306 | 307 | .macro goto_exception_handler id type 308 | // For now we have to simulate the exception, so we set ucause, uepc, mpk.mode 309 | // set ucause 310 | // csrw CSR_UCAUSE, CAUSE_MPKEY_MISMATCH_FAULT 311 | // store label 1 at uepc to simulate an exception 312 | // note temporarily mis-using reg_a1_id because we overwrite it later anyway 313 | //la reg_a1_id, 1f 314 | //la reg_a1_id, _pk_exception_handler 315 | //csrrw zero, CSR_UEPC, reg_a1_id 316 | 317 | // exception data to regs 318 | li reg_a1_id, \id 319 | li reg_a2_type, \type 320 | 321 | //go to exception handler 322 | trigger_exception 323 | .endm 324 | 325 | .macro CLEAR_CALLER_SAVED_TEMP_REGS 326 | mv t0, x0 327 | mv t1, x0 328 | mv t2, x0 329 | mv t3, x0 330 | mv t4, x0 331 | mv t5, x0 332 | mv t6, x0 333 | .endm 334 | 335 | .macro CLEAR_CALLEE_SAVED_REGS 336 | mv s0, x0 337 | mv s1, x0 338 | mv s2, x0 339 | mv s3, x0 340 | mv s4, x0 341 | mv s5, x0 342 | mv s6, x0 343 | mv s7, x0 344 | mv s8, x0 345 | mv s9, x0 346 | mv s10, x0 347 | mv s11, x0 348 | .endm 349 | 350 | .macro SAVE_CALLEE_REGS 351 | addi sp, sp, -12*8 352 | sd s0, 11*8(sp) 353 | sd s1, 10*8(sp) 354 | sd s2, 9*8(sp) 355 | sd s3, 8*8(sp) 356 | sd s4, 7*8(sp) 357 | sd s5, 6*8(sp) 358 | sd s6, 5*8(sp) 359 | sd s7, 4*8(sp) 360 | sd s8, 3*8(sp) 361 | sd s9, 2*8(sp) 362 | sd s10, 1*8(sp) 363 | sd s11, 0*8(sp) 364 | .endm 365 | 366 | .macro RESTORE_CALLEE_REGS 367 | ld s0, 11*8(sp) 368 | ld s1, 10*8(sp) 369 | ld s2, 9*8(sp) 370 | ld s3, 8*8(sp) 371 | ld s4, 7*8(sp) 372 | ld s5, 6*8(sp) 373 | ld s6, 5*8(sp) 374 | ld s7, 4*8(sp) 375 | ld s8, 3*8(sp) 376 | ld s9, 2*8(sp) 377 | ld s10, 1*8(sp) 378 | ld s11, 0*8(sp) 379 | addi sp, sp, 12*8 380 | .endm 381 | 382 | 383 | //NOTE: Using t* registers, because they're caller saved. 384 | // So we do not have to backup&restore them. 385 | // Also we avoid PLT issues this way. 386 | .macro ECALL_REGS_TO_TMP 387 | mv t0, reg_a0_data 388 | mv t1, reg_a1_id 389 | mv t2, reg_a2_type 390 | .endm 391 | 392 | .macro TMP_REGS_TO_ECALL 393 | mv reg_a0_data, t0 394 | mv reg_a1_id, t1 395 | mv reg_a2_type, t2 396 | .endm 397 | 398 | 399 | .macro GEN_CALL_WRAPPER_API name id 400 | .global \name 401 | \name: 402 | ECALL_REGS_TO_TMP 403 | goto_exception_handler \id TYPE_API 404 | _reentry_\name: 405 | // NOTE: exception preserves RA reg, so we safely return 406 | ret 407 | .endm 408 | 409 | /** 410 | * Macro for generating call wrappers that fall back to the libc function 411 | * if not initialized yet (i.e. pk_initialized is false) 412 | * 413 | * @param name points to the original libc function. The generated wrapper 414 | * function gets a pk_ prefix. 415 | * @param id Unique integer of the wrapped function 416 | * @param type TYPE_ECALL or TYPE_API 417 | */ 418 | .macro GEN_CALL_WRAPPER_API_FALLBACK name id 419 | .global pk_\name 420 | pk_\name: 421 | # If pk is not initialized yet, call the native function 422 | lb t0, PIC(pk_initialized) 423 | beqz t0, \name 424 | GEN_CALL_WRAPPER_API pk2_\name \id 425 | .endm 426 | 427 | .macro GEN_CALL_WRAPPER name id 428 | .global ecall_\name 429 | ecall_\name: 430 | //NOTE we save s* regs because they're callee-saved, but we can't trust our callee to do that 431 | //NOTE reg_ecall_* (a0..a2) are not required to be preserved across calls 432 | SAVE_CALLEE_REGS 433 | 434 | 435 | 436 | //NOTE: if argument registers are also callee-saved (in other architectures) 437 | // we could also save them here instead of doing that in the exception handler code 438 | // just make sure that we don't do it twice! 439 | 440 | // Save ecall argument regs to temp registers because we use them in goto_exception_handler 441 | ECALL_REGS_TO_TMP 442 | 443 | goto_exception_handler \id TYPE_CALL 444 | _reentry_\name: //label just for debugging 445 | TMP_REGS_TO_ECALL //TODO do we need to restore all or just a0,a1? 446 | 447 | RESTORE_CALLEE_REGS 448 | // NOTE: exception preserves RA reg, so we safely return 449 | ret 450 | .endm 451 | 452 | 453 | .macro GEN_CALLEE_WRAPPER name id 454 | .global _ecall_receive_\name 455 | _ecall_receive_\name: 456 | 457 | //Restore (previously saved) function arguments from tmp-regs 458 | TMP_REGS_TO_ECALL 459 | 460 | 461 | call \name 462 | 463 | //move return values (if any), to t0..t2. 464 | //(so that we can restore them later at _reentry_\name) 465 | //TODO technically we probably only need to do a0,a1 466 | ECALL_REGS_TO_TMP 467 | 468 | 469 | 470 | // Callee-saved regs are already restored by \name due to ABI 471 | 472 | goto_exception_handler \id TYPE_RET 473 | DIE 474 | .endm 475 | 476 | 477 | .macro GEN_REGISTER name id 478 | .global ecall_register_\name 479 | ecall_register_\name: 480 | // note: a0 = did = function argument 481 | // a1 will be the id of the ecall 482 | // 483 | //loadword a1, ECALL_TEST2_ID 484 | li a1, \id 485 | la a2, _ecall_receive_\name 486 | // 487 | addi sp,sp,-8 488 | sd ra,0(sp) 489 | call pk_domain_register_ecall2 490 | ld ra,0(sp) 491 | addi sp,sp,8 492 | ret 493 | .endm 494 | 495 | //generate all 3 wrappers for functions with simple arguments (args fit into regs) 496 | .macro GEN_ALL_SIMPLE name id 497 | GEN_REGISTER \name \id 498 | GEN_CALL_WRAPPER \name \id 499 | GEN_CALLEE_WRAPPER \name \id 500 | .endm 501 | 502 | 503 | #endif // __ASSEMBLY__ 504 | -------------------------------------------------------------------------------- /DonkyLib/pk/pku_handler_generic.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 1 2 | 3 | #include 4 | #include 5 | 6 | #include "pk.h" 7 | #include "pk_internal.h" 8 | 9 | //------------------------------------------------------------------------------ 10 | // Internal globals 11 | //------------------------------------------------------------------------------ 12 | 13 | pthread_mutex_t pku_mutex; 14 | 15 | unsigned char pk_initialized = 0; 16 | 17 | 18 | int _pku_dl_hooking(void); 19 | 20 | extern int pk_do_init(); 21 | 22 | #ifdef DLU_HOOKING 23 | // Intercept libc/pthread functions and forward them to pk 24 | // For this to work, libpku.so needs to be preloaded 25 | 26 | #include 27 | 28 | int (*real_sigaction)(int signum, const struct sigaction *act, 29 | struct sigaction *oldact) = NULL; 30 | sighandler_t (*real_signal)(int signum, sighandler_t handler) = NULL; 31 | void *(*real_mmap)(void *, size_t, int, int, int, off_t) = NULL; 32 | int (*real_munmap)(void *addr, size_t length) = NULL; 33 | int (*real_mprotect)(void *, size_t, int) = NULL; 34 | int (*real_pkey_alloc)(unsigned int flags, unsigned int access_rights) = NULL; 35 | int (*real_pkey_free)(int pkey) = NULL; 36 | int (*real_pkey_mprotect)(void *addr, size_t len, int prot, int pkey) = NULL; 37 | int (*real_pthread_create)(pthread_t *thread, const pthread_attr_t *attr, 38 | void *(*start_routine) (void *), void *arg) = NULL; 39 | 40 | #define SIGACTION real_sigaction 41 | #define SIGNAL real_signal 42 | #define MMAP real_mmap 43 | #define MUNMAP real_munmap 44 | #define MPROTECT real_mprotect 45 | #define PKEY_ALLOC pkey_alloc 46 | #define PKEY_FREE pkey_free 47 | #define PKEY_MPROTECT pkey_mprotect 48 | 49 | #else // DLU_HOOKING 50 | 51 | #define SIGACTION sigaction 52 | #define SIGNAL signal 53 | #define MMAP mmap 54 | #define MUNMAP munmap 55 | #define MPROTECT mprotect 56 | #define PKEY_ALLOC pkey_alloc 57 | #define PKEY_FREE pkey_free 58 | #define PKEY_MPROTECT pkey_mprotect 59 | #define PTHREAD_CREATE pthread_create 60 | 61 | #endif // DLU_HOOKING 62 | 63 | // Initialize the mutex in first constructor. This always needs to be 64 | // done in constructor, no matter if CONSTRUCTOR is defined or not 65 | __attribute__((constructor(0))) 66 | void _pku_ctor_mutex(){ 67 | DEBUG_MPK("_pku_ctor_mutex"); 68 | assert(0 == pthread_mutex_init(&pku_mutex, NULL)); 69 | } 70 | 71 | #ifdef CONSTRUCTOR 72 | // Automatically initialize and deinitialize pk 73 | 74 | __attribute__((constructor(1))) 75 | void _pku_ctor(){ 76 | DEBUG_MPK("_pku_ctor"); 77 | if(pk_init() != 0){ 78 | ERROR_FAIL("PKU constructor: pk_init failed"); 79 | } 80 | } 81 | //------------------------------------------------------------------------------ 82 | 83 | __attribute__((destructor(1))) 84 | void _pku_dtor(){ 85 | DEBUG_MPK("_pku_dtor"); 86 | if(pk_deinit() != 0){ 87 | ERROR_FAIL("PKU destructor: pk_deinit failed"); 88 | } 89 | } 90 | //------------------------------------------------------------------------------ 91 | #endif /* CONSTRUCTOR */ 92 | 93 | void PK_API pk_print_current_reg() { 94 | pk_print_reg_arch(_read_pkru_reg()); 95 | } 96 | //------------------------------------------------------------------------------ 97 | 98 | void PK_API pk_debug_usercheck(int expected_did) { 99 | assert(pk_current_did() == expected_did); 100 | pk_debug_usercheck_arch(); 101 | } 102 | //------------------------------------------------------------------------------ 103 | 104 | #define SIGMAX 256 105 | struct sigaction registered_signals[SIGMAX] = {{{0,},}}; 106 | 107 | // This wrapper is invoked by _pk_sa_sigaction_asm, which sets up 108 | // the stack 109 | void* _pk_sa_sigaction_c(int sig, siginfo_t *info, void *ucontext) { 110 | DEBUG_MPK("_pk_sa_sigaction intercepting signal %d", sig); 111 | // By doing *any* PK-API call, the trusted handler will restore the 112 | // correct pkru settings for us 113 | //~ pk_simple_api_call(0,0,0,0,0,0); 114 | //psiginfo(info, "_pk_sa_sigaction_c"); 115 | // pkru should be restored. From now on, we are allowed to call the 116 | // signal handler registered by the user 117 | assert(sig >= 0 && sig < SIGMAX); 118 | assert(registered_signals[sig].sa_handler); 119 | // We do not call handler directly in C, since we need to restore the 120 | // original user stack 121 | if (registered_signals[sig].sa_flags & SA_SIGINFO) { 122 | //DEBUG_MPK("sigaction: %p", registered_signals[sig].sa_sigaction); 123 | return (void*)registered_signals[sig].sa_sigaction; 124 | } else { 125 | //DEBUG_MPK("handler: %p", registered_signals[sig].sa_handler); 126 | return (void*)registered_signals[sig].sa_handler; 127 | } 128 | } 129 | //------------------------------------------------------------------------------ 130 | 131 | int PK_API pk_sigaction(int signum, const struct sigaction *act, 132 | struct sigaction *oldact) { 133 | DEBUG_MPK("pk_sigaction(%d, %p, %p)", signum, act, oldact); 134 | #ifdef FAKE_MPK_REGISTER 135 | DEBUG_MPK("fallback to libc"); 136 | return SIGACTION(signum,act,oldact); 137 | #endif 138 | if (!act) { 139 | errno = EINVAL; 140 | return -1; 141 | } 142 | if (signum < 0 || signum >= SIGMAX) { 143 | WARNING("pk_sigaction: signum out of range"); 144 | errno = EINVAL; 145 | return -1; 146 | } 147 | 148 | struct sigaction newact = *act; 149 | if (act->sa_flags & SA_SIGINFO) { 150 | // Hook action with our own handler 151 | newact.sa_sigaction = _pk_sa_sigaction_asm; 152 | DEBUG_MPK("pk_sigaction registering sigaction: %p",newact.sa_sigaction); 153 | } else { 154 | // We leave SIG_DFL and SIG_IGN unchanged 155 | if (act->sa_handler != SIG_DFL && act->sa_handler != SIG_IGN) { 156 | // Hook action with our own handler 157 | newact.sa_handler = _pk_sa_sigaction_asm; 158 | } 159 | DEBUG_MPK("pk_sigaction registering handler: %p",newact.sa_handler); 160 | } 161 | 162 | assert(0 == pthread_mutex_lock(&pku_mutex)); 163 | int ret = SIGACTION(signum, &newact, NULL); 164 | if (0 == ret) { 165 | if (oldact) { 166 | *oldact = registered_signals[signum]; 167 | } 168 | registered_signals[signum] = *act; 169 | } 170 | assert(0 == pthread_mutex_unlock(&pku_mutex)); 171 | 172 | return ret; 173 | } 174 | //------------------------------------------------------------------------------ 175 | 176 | sighandler_t PK_API pk_signal(int signum, sighandler_t handler) { 177 | struct sigaction oldact; 178 | struct sigaction act; 179 | memset(&act, 0, sizeof(act)); 180 | act.sa_handler = handler; 181 | 182 | if (0 != pk_sigaction(signum, &act, &oldact)) { 183 | return SIG_ERR; 184 | } 185 | return oldact.sa_handler; 186 | } 187 | //------------------------------------------------------------------------------ 188 | 189 | 190 | #ifdef DLU_HOOKING 191 | #include 192 | 193 | int PK_API sigaction(int signum, const struct sigaction *act, 194 | struct sigaction *oldact) { 195 | DEBUG_MPK("Intercept sigaction"); 196 | return pk_sigaction(signum, act, oldact); 197 | } 198 | //------------------------------------------------------------------------------ 199 | 200 | sighandler_t PK_API signal(int signum, sighandler_t handler) { 201 | DEBUG_MPK("Intercept signal"); 202 | return pk_signal(signum, handler); 203 | } 204 | //------------------------------------------------------------------------------ 205 | 206 | void PK_API *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) { 207 | DEBUG_MPK("Intercept mmap"); 208 | return pk_mmap(addr, length, prot, flags, fd, offset); 209 | } 210 | //------------------------------------------------------------------------------ 211 | 212 | int PK_API munmap(void *addr, size_t length) { 213 | DEBUG_MPK("Intercept munmap"); 214 | return pk_munmap(addr, length); 215 | } 216 | //------------------------------------------------------------------------------ 217 | 218 | int PK_API mprotect(void *addr, size_t len, int prot) { 219 | DEBUG_MPK("Intercept mprotect"); 220 | return pk_mprotect(addr, len, prot); 221 | } 222 | //------------------------------------------------------------------------------ 223 | 224 | int PK_API pthread_create(pthread_t *thread, const pthread_attr_t *attr, 225 | void *(*start_routine) (void *), void *arg) { 226 | DEBUG_MPK("Intercept pthread_create"); 227 | return pk_pthread_create(thread, attr, start_routine, arg); 228 | } 229 | //------------------------------------------------------------------------------ 230 | 231 | int _pku_dl_hooking(void) { 232 | 233 | if (!real_sigaction) { 234 | DEBUG_MPK("Hooking sigaction"); 235 | real_sigaction = dlsym(RTLD_NEXT, "sigaction"); 236 | } 237 | if (!real_signal) { 238 | DEBUG_MPK("Hooking signal"); 239 | real_signal = dlsym(RTLD_NEXT, "signal"); 240 | } 241 | if (!real_mmap) { 242 | DEBUG_MPK("Hooking mmap\n"); 243 | real_mmap = dlsym(RTLD_NEXT, "mmap"); 244 | } 245 | if (!real_munmap) { 246 | DEBUG_MPK("Hooking munmap\n"); 247 | real_munmap = dlsym(RTLD_NEXT, "munmap"); 248 | } 249 | if (!real_mprotect) { 250 | DEBUG_MPK("Hooking mprotect\n"); 251 | real_mprotect = dlsym(RTLD_NEXT, "mprotect"); 252 | } 253 | if (!real_pthread_create) { 254 | DEBUG_MPK("Hooking pthread_create\n"); 255 | real_pthread_create = dlsym(RTLD_NEXT, "pthread_create"); 256 | } 257 | if (!real_sigaction || 258 | !real_signal || 259 | !real_mmap || 260 | !real_munmap || 261 | !real_mprotect || 262 | !real_pthread_create 263 | ) { 264 | errno = EACCES; 265 | return -1; 266 | } 267 | return 0; 268 | } 269 | 270 | #endif // DLU_HOOKING 271 | 272 | int PK_API pk_init(void) { 273 | int ret = 0; 274 | int pk_do_init_finished = 0; 275 | assert(0 == pthread_mutex_lock(&pku_mutex)); 276 | 277 | if (pk_initialized) { 278 | WARNING("pk already initialized"); 279 | ret = 0; 280 | goto cleanup; 281 | } 282 | 283 | ret = pk_do_init(); 284 | if (-1 == ret) { 285 | // errno set by pk_do_init 286 | goto error; 287 | } 288 | pk_do_init_finished = 1; 289 | 290 | #ifdef DLU_HOOKING 291 | if (-1 == _pku_dl_hooking()) { 292 | DEBUG_MPK("pk_init: failed to hook libc"); 293 | // errno set by _pku_dl_hooking 294 | ret = -1; 295 | goto error; 296 | } 297 | #endif // DLU_HOOKING 298 | 299 | pk_initialized = 1; 300 | ret = 0; 301 | goto cleanup; 302 | 303 | error: 304 | if (pk_do_init_finished) { 305 | if(pk_deinit() != 0){ 306 | ERROR("pk_deinit failed"); 307 | } 308 | } 309 | cleanup: 310 | assert(0 == pthread_mutex_unlock(&pku_mutex)); 311 | return ret; 312 | } 313 | //------------------------------------------------------------------------------ 314 | 315 | #ifdef SHARED 316 | 317 | #define _GNU_SOURCE 318 | #include 319 | #include 320 | 321 | /** 322 | * Copied from RISCV-PK: 323 | * 324 | * The protection flags are in the p_flags section of the program header. 325 | * But rather annoyingly, they are the reverse of what mmap expects. 326 | */ 327 | static int pk_get_prot(uint32_t p_flags) 328 | { 329 | int prot_x = (p_flags & PF_X) ? PROT_EXEC : PROT_NONE; 330 | int prot_w = (p_flags & PF_W) ? PROT_WRITE : PROT_NONE; 331 | int prot_r = (p_flags & PF_R) ? PROT_READ : PROT_NONE; 332 | 333 | return (prot_x | prot_w | prot_r); 334 | } 335 | //------------------------------------------------------------------------------ 336 | 337 | typedef struct { 338 | int did; 339 | vkey_t vkey; 340 | const void* self; 341 | const char* module; 342 | int count; 343 | } module_t; 344 | //------------------------------------------------------------------------------ 345 | 346 | static int PK_CODE pk_module_reprotect_phdr(module_t* module, struct dl_phdr_info *info, Elf64_Phdr* phdr) { 347 | uintptr_t start = info->dlpi_addr + phdr->p_vaddr; 348 | uintptr_t end = start + phdr->p_memsz; 349 | start &= ~PAGEMASK; // round down 350 | end = (end + PAGESIZE-1) & ~PAGEMASK; // round up 351 | int prot = pk_get_prot(phdr->p_flags); 352 | int ret = pk_pkey_mprotect2(module->did, (void*)start, end - start, prot, module->vkey); 353 | if (-1 == ret) { 354 | perror("pk_pkey_mprotect failed"); 355 | return -1; 356 | } 357 | return 0; 358 | } 359 | //------------------------------------------------------------------------------ 360 | 361 | static int pk_module_protect_phdr(struct dl_phdr_info *info, size_t size, void *data) 362 | { 363 | int j; 364 | module_t* module = (module_t*)data; 365 | if (!module) { 366 | return -1; 367 | } 368 | DEBUG_MPK("pku_selfprotect_phdr(%d, %d, %p, %s)", module->did, module->vkey, module->self, module->module); 369 | DEBUG_MPK("Module %s (%d segments)", info->dlpi_name, info->dlpi_phnum); 370 | 371 | if (module->self) { 372 | DEBUG_MPK("Searching for module which covers self %p", module->self); 373 | // Search for module that contains our code 374 | for (j = 0; j < info->dlpi_phnum; j++) { 375 | Elf64_Phdr phdr = info->dlpi_phdr[j]; 376 | if (phdr.p_type == PT_LOAD) { 377 | Elf64_Phdr phdr = info->dlpi_phdr[j]; 378 | uintptr_t start = info->dlpi_addr + phdr.p_vaddr; 379 | uintptr_t end = start + phdr.p_memsz; 380 | if ((uintptr_t)module->self >= start && (uintptr_t)module->self < end) { 381 | DEBUG_MPK("Found self module"); 382 | break; 383 | } 384 | } 385 | } 386 | if (j >= info->dlpi_phnum) { 387 | // We're in the wrong module 388 | DEBUG_MPK("Skipping"); 389 | return 0; 390 | } 391 | } 392 | 393 | if (module->module) { 394 | DEBUG_MPK("Searching for module matching substring %s", module->module); 395 | if (strstr(info->dlpi_name, module->module)) { 396 | DEBUG_MPK("Found module %s", info->dlpi_name); 397 | } else { 398 | // We're in the wrong module 399 | DEBUG_MPK("Skipping"); 400 | return 0; 401 | } 402 | } 403 | 404 | // Re-protect all PT_LOAD (+ GNU_RELRO) segments with did/vkey 405 | for (j = 0; j < info->dlpi_phnum; j++) { 406 | Elf64_Phdr phdr = info->dlpi_phdr[j]; 407 | if (phdr.p_type == PT_LOAD) { 408 | DEBUG_MPK("Reprotecting PT_LOAD %2d: address=%10p (0x%010lx) [flags 0x%x]", j, (void *) (info->dlpi_addr + phdr.p_vaddr), phdr.p_memsz, phdr.p_flags); 409 | if (-1 == pk_module_reprotect_phdr(module, info, &phdr)) { 410 | // errno set by reprotect_phdr 411 | return -1; 412 | } 413 | } else if (phdr.p_type == PT_GNU_RELRO) { 414 | DEBUG_MPK("Reprotecting GNU_RELRO %2d [%d]: address=%10p (0x%010lx) [flags 0x%x]", j, phdr.p_type, (void *) (info->dlpi_addr + phdr.p_vaddr), phdr.p_memsz, phdr.p_flags); 415 | if (-1 == pk_module_reprotect_phdr(module, info, &phdr)) { 416 | // errno set by reprotect_phdr 417 | return -1; 418 | } 419 | } else if (phdr.p_type == PT_TLS) { 420 | DEBUG_MPK("Ignoring PT_TLS %2d [%d]: address=%10p (0x%010lx) [flags 0x%x]", j, phdr.p_type, (void *) (info->dlpi_addr + phdr.p_vaddr), phdr.p_memsz, phdr.p_flags); 421 | } else { 422 | DEBUG_MPK("Ignoring header %2d [%d]: address=%10p (0x%010lx) [flags 0x%x]", j, phdr.p_type, (void *) (info->dlpi_addr + phdr.p_vaddr), phdr.p_memsz, phdr.p_flags); 423 | } 424 | } 425 | module->count++; 426 | return 0; 427 | } 428 | //------------------------------------------------------------------------------ 429 | 430 | int PK_API pk_module_protect(int did, vkey_t vkey, const void* self, const char* module) { 431 | module_t mod = { 432 | .did = did, // domain to which memory shall be assigned. Can be PK_DOMAIN_CURRENT 433 | .vkey = vkey, // protection key to be assigned to memory. Needs to be owned by did. Can be PK_DEFAULT_KEY 434 | .self = self, // if not NULL, only protect module containing this pointer 435 | .module = module, // if not NULL, only protect module matching this string 436 | .count = 0, // incremented by pk_module_protect_phdr for each protected module 437 | }; 438 | int ret = dl_iterate_phdr(pk_module_protect_phdr, &mod); 439 | if (ret < 0) { 440 | // errno is set by pk_module_protect_phdr 441 | return ret; 442 | } 443 | return mod.count; 444 | } 445 | //------------------------------------------------------------------------------ 446 | 447 | #endif // SHARED 448 | 449 | #ifdef TIMING 450 | //_timing timing_values[NUM_TIMING_VALUES] = {{0,},}; 451 | //size_t timing_values_index = 0; 452 | uint64_t PK_API timing_min = UINT64_MAX; 453 | uint64_t PK_API timing_tmp = 0; 454 | 455 | //complex timing table: 456 | #ifdef TIMING_MEASURE_MINIMUM 457 | uint64_t PK_API timing_values[TIMING_T_MAX]; 458 | #else 459 | uint64_t PK_API timing_values[TIMING_T_MAX][NUM_TESTRUNS]; 460 | #endif 461 | uint64_t PK_API time_in_progress = 0; 462 | 463 | //------------------------------------------------------------------------------ 464 | #endif 465 | --------------------------------------------------------------------------------