hypervisor kernel integrity enforcement via EPT/NPT write interception for Xen/HVM guests
14

Configure Feed

Select the types of activity you want to include in your feed.

initial commit

author vm.fail date (Jan 15, 2026, 5:08 PM UTC) commit 0d0ae950
+2791
+10
.gitignore
··· 1 + target/ 2 + *.o 3 + *.ko 4 + *.mod 5 + *.mod.c 6 + *.order 7 + *.symvers 8 + *.cmd 9 + .*.cmd 10 + .tmp_versions/
+280
build-xen-turnstile.sh
··· 1 + #!/bin/bash 2 + set -e 3 + 4 + XEN_VERSION="4.18.0" 5 + XEN_URL="https://downloads.xenproject.org/release/xen/${XEN_VERSION}/xen-${XEN_VERSION}.tar.gz" 6 + BUILD_DIR="/tmp/xen-turnstile-build" 7 + TURNSTILE_DIR="$(cd "$(dirname "$0")" && pwd)" 8 + 9 + echo "=== Turnstile Xen Build Script ===" 10 + echo "Xen version: ${XEN_VERSION}" 11 + echo "Turnstile source: ${TURNSTILE_DIR}" 12 + echo "Build directory: ${BUILD_DIR}" 13 + echo "" 14 + 15 + install_deps_arch() { 16 + echo "Installing build dependencies for Arch Linux..." 17 + sudo pacman -Sy --needed --noconfirm \ 18 + base-devel git wget python python-setuptools \ 19 + iasl acpica yajl pixman bridge-utils \ 20 + iproute2 libaio glib2 libpng sdl2 ncurses \ 21 + libnl openssl zlib lzo libseccomp systemd \ 22 + e2fsprogs nasm flex bison ninja meson 23 + 24 + if ! command -v bcc &> /dev/null; then 25 + echo "" 26 + echo "dev86 not found. Installing from AUR..." 27 + if command -v yay &> /dev/null; then 28 + yay -S --needed --noconfirm dev86 29 + elif command -v paru &> /dev/null; then 30 + paru -S --needed --noconfirm dev86 31 + else 32 + echo "No AUR helper found. Installing dev86 manually..." 33 + cd /tmp 34 + rm -rf dev86 35 + git clone https://aur.archlinux.org/dev86.git 36 + cd dev86 37 + makepkg -si --noconfirm 38 + cd "${TURNSTILE_DIR}" 39 + fi 40 + fi 41 + } 42 + 43 + install_deps_debian() { 44 + echo "Installing build dependencies for Debian/Ubuntu..." 45 + sudo apt-get update 46 + sudo apt-get install -y \ 47 + build-essential git wget python3 python3-dev \ 48 + uuid-dev libncurses-dev libglib2.0-dev libyajl-dev \ 49 + libaio-dev libpixman-1-dev libssl-dev libsdl2-dev \ 50 + liblzo2-dev libseccomp-dev bison flex iasl \ 51 + bridge-utils iproute2 e2fsprogs ninja-build meson 52 + } 53 + 54 + install_deps() { 55 + if [ -f /etc/arch-release ]; then 56 + install_deps_arch 57 + elif [ -f /etc/debian_version ]; then 58 + install_deps_debian 59 + else 60 + echo "Unknown distribution. Please install Xen build dependencies manually." 61 + echo "Required: build tools, python, uuid, ncurses, glib2, yajl, aio, pixman, ssl, sdl2, lzo, seccomp, iasl, ninja" 62 + exit 1 63 + fi 64 + } 65 + 66 + download_xen() { 67 + echo "" 68 + echo "=== Downloading Xen ${XEN_VERSION} ===" 69 + mkdir -p "${BUILD_DIR}" 70 + cd "${BUILD_DIR}" 71 + 72 + if [ ! -f "xen-${XEN_VERSION}.tar.gz" ]; then 73 + wget "${XEN_URL}" 74 + fi 75 + 76 + if [ ! -d "xen-${XEN_VERSION}" ]; then 77 + tar xzf "xen-${XEN_VERSION}.tar.gz" 78 + fi 79 + } 80 + 81 + copy_turnstile_sources() { 82 + echo "" 83 + echo "=== Copying turnstile sources ===" 84 + cd "${BUILD_DIR}/xen-${XEN_VERSION}" 85 + 86 + cp "${TURNSTILE_DIR}/xen-patch/turnstile.c" xen/arch/x86/ 87 + cp "${TURNSTILE_DIR}/xen-patch/turnstile.h" xen/arch/x86/include/asm/ 88 + 89 + echo "Copied turnstile.c to xen/arch/x86/" 90 + echo "Copied turnstile.h to xen/arch/x86/include/asm/" 91 + } 92 + 93 + apply_patches() { 94 + echo "" 95 + echo "=== Applying turnstile patches ===" 96 + cd "${BUILD_DIR}/xen-${XEN_VERSION}" 97 + 98 + DOMAIN_H="xen/arch/x86/include/asm/domain.h" 99 + DOMAIN_C="xen/arch/x86/domain.c" 100 + VMX_C="xen/arch/x86/hvm/vmx/vmx.c" 101 + SVM_C="xen/arch/x86/hvm/svm/svm.c" 102 + MAKEFILE="xen/arch/x86/Makefile" 103 + 104 + echo "Adding turnstile pointer to arch_domain..." 105 + if ! grep -q "struct turnstile_domain_state" "${DOMAIN_H}"; then 106 + sed -i '/^struct arch_domain$/i struct turnstile_domain_state;' "${DOMAIN_H}" 107 + sed -i '/^struct arch_domain$/,/^{$/ { /^{$/a\ struct turnstile_domain_state *turnstile; 108 + }' "${DOMAIN_H}" 109 + fi 110 + 111 + echo "Adding turnstile include and cleanup to domain.c..." 112 + if ! grep -q "asm/turnstile.h" "${DOMAIN_C}"; then 113 + sed -i '/#include <asm\/pv\/mm.h>/a #include <asm/turnstile.h>' "${DOMAIN_C}" 114 + fi 115 + if ! grep -q "turnstile_domain_destroy" "${DOMAIN_C}"; then 116 + sed -i '/^void arch_domain_destroy(struct domain \*d)/,/^{/ { /^{$/a\ turnstile_domain_destroy(d); 117 + }' "${DOMAIN_C}" 118 + fi 119 + 120 + echo "Adding turnstile EPT hook to vmx.c..." 121 + if ! grep -q "asm/turnstile.h" "${VMX_C}"; then 122 + sed -i '/#include <asm\/monitor.h>/a #include <asm/turnstile.h>' "${VMX_C}" 123 + fi 124 + if ! grep -q "turnstile_check_violation" "${VMX_C}"; then 125 + sed -i '/case EXIT_REASON_EPT_VIOLATION:/,/ept_handle_violation/ { 126 + /paddr_t gpa;/a\ uint8_t insn_bytes[16]; 127 + }' "${VMX_C}" 128 + sed -i 's/ ept_handle_violation(exit_qualification, gpa);/ if ( (exit_qualification \& (1u << 1)) \&\& currd->arch.turnstile )\n {\n unsigned long rip, cr3;\n int block;\n\n __vmread(GUEST_RIP, \&rip);\n __vmread(GUEST_CR3, \&cr3);\n\n memset(insn_bytes, 0, sizeof(insn_bytes));\n hvm_copy_from_guest_phys(insn_bytes, rip, sizeof(insn_bytes));\n\n block = turnstile_check_violation(currd, gpa, rip, cr3,\n exit_qualification,\n insn_bytes);\n if ( block )\n {\n hvm_inject_page_fault(PFEC_write_access | PFEC_page_present, gpa);\n break;\n }\n }\n\n ept_handle_violation(exit_qualification, gpa);/' "${VMX_C}" 129 + fi 130 + 131 + echo "Adding turnstile NPT hook to svm.c..." 132 + if ! grep -q "asm/turnstile.h" "${SVM_C}"; then 133 + sed -i '/#include <asm\/apic.h>/a #include <asm/turnstile.h>' "${SVM_C}" 134 + fi 135 + if ! grep -q "turnstile_check_violation" "${SVM_C}"; then 136 + sed -i '/case VMEXIT_NPF:/,/v->arch.hvm.svm.cached_insn_len = 0;/ { 137 + /if ( cpu_has_svm_decode )/i\ if ( (vmcb->ei.npf.ec \& PFEC_write_access) \&\& v->domain->arch.turnstile )\n {\n uint8_t insn_bytes[16];\n unsigned long rip = vmcb->rip;\n unsigned long cr3 = vmcb_get_cr3(vmcb);\n int block;\n\n memset(insn_bytes, 0, sizeof(insn_bytes));\n hvm_copy_from_guest_phys(insn_bytes, rip, sizeof(insn_bytes));\n\n block = turnstile_check_violation(v->domain, vmcb->ei.npf.gpa, rip, cr3,\n vmcb->ei.npf.ec,\n insn_bytes);\n if ( block )\n {\n hvm_inject_page_fault(PFEC_write_access | PFEC_page_present,\n vmcb->ei.npf.gpa);\n break;\n }\n }\n 138 + }' "${SVM_C}" 139 + fi 140 + 141 + echo "Adding turnstile.o to Makefile..." 142 + if ! grep -q "turnstile.o" "${MAKEFILE}"; then 143 + sed -i '/^obj-y += usercopy.o/i obj-y += turnstile.o' "${MAKEFILE}" 144 + fi 145 + 146 + echo "Patches applied." 147 + } 148 + 149 + configure_xen() { 150 + echo "" 151 + echo "=== Configuring Xen ===" 152 + cd "${BUILD_DIR}/xen-${XEN_VERSION}" 153 + 154 + ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var \ 155 + --enable-systemd --disable-stubdom --disable-docs 156 + } 157 + 158 + build_xen() { 159 + echo "" 160 + echo "=== Building Xen hypervisor ===" 161 + cd "${BUILD_DIR}/xen-${XEN_VERSION}" 162 + 163 + make -j$(nproc) xen 164 + } 165 + 166 + build_tools() { 167 + echo "" 168 + echo "=== Building Xen tools ===" 169 + cd "${BUILD_DIR}/xen-${XEN_VERSION}" 170 + 171 + make -j$(nproc) tools 172 + } 173 + 174 + install_xen() { 175 + echo "" 176 + echo "=== Installing Xen hypervisor ===" 177 + cd "${BUILD_DIR}/xen-${XEN_VERSION}" 178 + 179 + sudo make install-xen 180 + 181 + echo "" 182 + echo "=== Updating boot configuration ===" 183 + if [ -f /etc/arch-release ]; then 184 + sudo grub-mkconfig -o /boot/grub/grub.cfg 185 + elif [ -f /etc/debian_version ]; then 186 + sudo update-grub 187 + fi 188 + } 189 + 190 + install_tools() { 191 + echo "" 192 + echo "=== Installing Xen tools ===" 193 + cd "${BUILD_DIR}/xen-${XEN_VERSION}" 194 + 195 + sudo make install-tools 196 + } 197 + 198 + print_usage() { 199 + echo "Usage: $0 [command]" 200 + echo "" 201 + echo "Commands:" 202 + echo " deps - Install build dependencies only" 203 + echo " download - Download Xen source only" 204 + echo " patch - Copy sources and apply patches only" 205 + echo " configure - Configure Xen build" 206 + echo " build - Build Xen hypervisor (assumes patched)" 207 + echo " build-tools - Build Xen tools" 208 + echo " install - Install built Xen hypervisor" 209 + echo " install-tools - Install Xen tools" 210 + echo " hypervisor - Build and install hypervisor only (no tools)" 211 + echo " all - Do everything (default)" 212 + echo "" 213 + } 214 + 215 + case "${1:-all}" in 216 + deps) 217 + install_deps 218 + ;; 219 + download) 220 + download_xen 221 + ;; 222 + patch) 223 + download_xen 224 + copy_turnstile_sources 225 + apply_patches 226 + ;; 227 + configure) 228 + configure_xen 229 + ;; 230 + build) 231 + build_xen 232 + ;; 233 + build-tools) 234 + build_tools 235 + ;; 236 + install) 237 + install_xen 238 + ;; 239 + install-tools) 240 + install_tools 241 + ;; 242 + hypervisor) 243 + install_deps 244 + download_xen 245 + copy_turnstile_sources 246 + apply_patches 247 + configure_xen 248 + build_xen 249 + install_xen 250 + echo "" 251 + echo "=== Turnstile-patched Xen ${XEN_VERSION} hypervisor installed ===" 252 + echo "" 253 + echo "Next steps:" 254 + echo "1. Reboot and select Xen from the bootloader" 255 + echo "2. Build turnstile-ctl: cd ${TURNSTILE_DIR}/turnstile-ctl && cargo build --release" 256 + echo "3. Start a guest domain and run: turnstile-ctl protect <domid> /path/to/vmlinux" 257 + ;; 258 + all) 259 + install_deps 260 + download_xen 261 + copy_turnstile_sources 262 + apply_patches 263 + configure_xen 264 + build_xen 265 + build_tools 266 + install_xen 267 + install_tools 268 + echo "" 269 + echo "=== Turnstile-patched Xen ${XEN_VERSION} installed ===" 270 + echo "" 271 + echo "Next steps:" 272 + echo "1. Reboot and select Xen from the bootloader" 273 + echo "2. Build turnstile-ctl: cd ${TURNSTILE_DIR}/turnstile-ctl && cargo build --release" 274 + echo "3. Start a guest domain and run: turnstile-ctl protect <domid> /path/to/vmlinux" 275 + ;; 276 + *) 277 + print_usage 278 + exit 1 279 + ;; 280 + esac
+103
readme.md
··· 1 + # turnstile 2 + 3 + hypervisor kernel integrity enforcement via ept/npt write interception for xen hvm guests 4 + 5 + this leverages second level address translation fault handling to enforce write protection on guest kernel text and rodata segments. the hypervisor intercepts ept violations and npt faults targeting protected guest physical address ranges and applying configurable policy responses while maintaining implicit write grant detection for legitimate kernel self-modification patterns 6 + 7 + it has overall 3 components: 8 + 9 + - a xen hypervisor patch implementing the violation detection and policy engine 10 + 11 + - a dom0 userspace controller for guest introspection and protection orchestration 12 + 13 + - an optional guest kernel module providing explicit hypercall-based write grant negotiation for livepatch compatibility 14 + 15 + ### violation interception 16 + 17 + write faults to protected gpas trigger vmexit with exit reason ept_violation or vmexit_npf. the turnstile hook in the vmexit handler extracts the faulting gpa, guest rip and cr3 and instruction bytes and then invoking the policy engine before delegating to the standard p2m fault handler 18 + 19 + ### implicit detection 20 + 21 + turnstile recognises legitimate kernel text modifications w/o explicit hypercall coordination: 22 + 23 + - 5 byte nop to call instruction patching at function prologues (0x0f 0x1f 0x44 0x00 0x00 -> 0xe8 rel32) 24 + - jump_label infrastructure modifications (nop -> jmp patching via __jump_table metadata) 25 + 26 + detection works by correlating faulting addresses against uploaded symbol metadata 27 + 28 + function entry points and jump_table entries are extracted from the vmlinux elf and adjusted for kaslr slide to enable implicit grant decisions w/o guest modification 29 + 30 + ### explicit grants 31 + 32 + for livepatch and other intentional kernel modifications the guest module issues turnstile_op_request_write hypercalls specifying gpa range and timeout 33 + 34 + the hypervisor grants temporary write permission and logged and bounded by configurable expiry 35 + 36 + ### kaslr handling 37 + 38 + dom0 controller detects kaslr slide by scanning guest physical memory for the startup_64 instruction sequence where by pattern matching at 2mb aligned addresses from 0x1000000 to 0x40000000 locates the kernel text physical base so that the runtime slides as delta from the vmlinux link time addresses 39 + 40 + 41 + ## build 42 + 43 + ### hv 44 + 45 + ``` 46 + ./build-xen-turnstile.sh hypervisor 47 + ``` 48 + 49 + note: requires standard xen build deps + dev86 from aur on arch (which is what i wrote this on, specifically i3) 50 + 51 + ### dom0 52 + 53 + ``` 54 + cd turnstile-ctl 55 + cargo build --release 56 + ``` 57 + 58 + optional disassembly support fromiced-x86: 59 + 60 + ``` 61 + cargo build --release --features disasm 62 + ``` 63 + 64 + ### guest 65 + 66 + ``` 67 + cd turnstile-guest 68 + make 69 + insmod turnstile_guest.ko 70 + ``` 71 + 72 + ## usage 73 + 74 + ### protect 75 + 76 + ``` 77 + turnstile-ctl protect <domid> /path/to/vmlinux 78 + ``` 79 + 80 + extracts kernel layout and detects kaslr along w/ uploads metadata and configuring protected ranges/sets audit policy 81 + 82 + ### violations 83 + 84 + ``` 85 + turnstile-ctl monitor <domid> /path/to/vmlinux --interval-ms 50 86 + ``` 87 + 88 + polls violation ring buffer and resolves addresses to symbols to display instruction bytes 89 + 90 + ### policy 91 + 92 + ``` 93 + turnstile-ctl policy <domid> enforce 94 + turnstile-ctl policy <domid> audit 95 + turnstile-ctl policy <domid> disabled 96 + ``` 97 + 98 + ### statistics 99 + 100 + ``` 101 + turnstile-ctl stats <domid> 102 + ``` 103 +
+305
turnstile-ctl/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "anstream" 7 + version = "0.6.21" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 10 + dependencies = [ 11 + "anstyle", 12 + "anstyle-parse", 13 + "anstyle-query", 14 + "anstyle-wincon", 15 + "colorchoice", 16 + "is_terminal_polyfill", 17 + "utf8parse", 18 + ] 19 + 20 + [[package]] 21 + name = "anstyle" 22 + version = "1.0.13" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 25 + 26 + [[package]] 27 + name = "anstyle-parse" 28 + version = "0.2.7" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 31 + dependencies = [ 32 + "utf8parse", 33 + ] 34 + 35 + [[package]] 36 + name = "anstyle-query" 37 + version = "1.1.5" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 40 + dependencies = [ 41 + "windows-sys", 42 + ] 43 + 44 + [[package]] 45 + name = "anstyle-wincon" 46 + version = "3.0.11" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 49 + dependencies = [ 50 + "anstyle", 51 + "once_cell_polyfill", 52 + "windows-sys", 53 + ] 54 + 55 + [[package]] 56 + name = "bitflags" 57 + version = "2.10.0" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 60 + 61 + [[package]] 62 + name = "cfg-if" 63 + version = "1.0.4" 64 + source = "registry+https://github.com/rust-lang/crates.io-index" 65 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 66 + 67 + [[package]] 68 + name = "cfg_aliases" 69 + version = "0.2.1" 70 + source = "registry+https://github.com/rust-lang/crates.io-index" 71 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 72 + 73 + [[package]] 74 + name = "clap" 75 + version = "4.5.54" 76 + source = "registry+https://github.com/rust-lang/crates.io-index" 77 + checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" 78 + dependencies = [ 79 + "clap_builder", 80 + "clap_derive", 81 + ] 82 + 83 + [[package]] 84 + name = "clap_builder" 85 + version = "4.5.54" 86 + source = "registry+https://github.com/rust-lang/crates.io-index" 87 + checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" 88 + dependencies = [ 89 + "anstream", 90 + "anstyle", 91 + "clap_lex", 92 + "strsim", 93 + ] 94 + 95 + [[package]] 96 + name = "clap_derive" 97 + version = "4.5.49" 98 + source = "registry+https://github.com/rust-lang/crates.io-index" 99 + checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 100 + dependencies = [ 101 + "heck", 102 + "proc-macro2", 103 + "quote", 104 + "syn", 105 + ] 106 + 107 + [[package]] 108 + name = "clap_lex" 109 + version = "0.7.7" 110 + source = "registry+https://github.com/rust-lang/crates.io-index" 111 + checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" 112 + 113 + [[package]] 114 + name = "colorchoice" 115 + version = "1.0.4" 116 + source = "registry+https://github.com/rust-lang/crates.io-index" 117 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 118 + 119 + [[package]] 120 + name = "goblin" 121 + version = "0.9.3" 122 + source = "registry+https://github.com/rust-lang/crates.io-index" 123 + checksum = "daa0a64d21a7eb230583b4c5f4e23b7e4e57974f96620f42a7e75e08ae66d745" 124 + dependencies = [ 125 + "log", 126 + "plain", 127 + "scroll", 128 + ] 129 + 130 + [[package]] 131 + name = "heck" 132 + version = "0.5.0" 133 + source = "registry+https://github.com/rust-lang/crates.io-index" 134 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 135 + 136 + [[package]] 137 + name = "iced-x86" 138 + version = "1.21.0" 139 + source = "registry+https://github.com/rust-lang/crates.io-index" 140 + checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" 141 + dependencies = [ 142 + "lazy_static", 143 + ] 144 + 145 + [[package]] 146 + name = "is_terminal_polyfill" 147 + version = "1.70.2" 148 + source = "registry+https://github.com/rust-lang/crates.io-index" 149 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 150 + 151 + [[package]] 152 + name = "lazy_static" 153 + version = "1.5.0" 154 + source = "registry+https://github.com/rust-lang/crates.io-index" 155 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 156 + 157 + [[package]] 158 + name = "libc" 159 + version = "0.2.180" 160 + source = "registry+https://github.com/rust-lang/crates.io-index" 161 + checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" 162 + 163 + [[package]] 164 + name = "log" 165 + version = "0.4.29" 166 + source = "registry+https://github.com/rust-lang/crates.io-index" 167 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 168 + 169 + [[package]] 170 + name = "nix" 171 + version = "0.29.0" 172 + source = "registry+https://github.com/rust-lang/crates.io-index" 173 + checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 174 + dependencies = [ 175 + "bitflags", 176 + "cfg-if", 177 + "cfg_aliases", 178 + "libc", 179 + ] 180 + 181 + [[package]] 182 + name = "once_cell_polyfill" 183 + version = "1.70.2" 184 + source = "registry+https://github.com/rust-lang/crates.io-index" 185 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 186 + 187 + [[package]] 188 + name = "plain" 189 + version = "0.2.3" 190 + source = "registry+https://github.com/rust-lang/crates.io-index" 191 + checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 192 + 193 + [[package]] 194 + name = "proc-macro2" 195 + version = "1.0.105" 196 + source = "registry+https://github.com/rust-lang/crates.io-index" 197 + checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" 198 + dependencies = [ 199 + "unicode-ident", 200 + ] 201 + 202 + [[package]] 203 + name = "quote" 204 + version = "1.0.43" 205 + source = "registry+https://github.com/rust-lang/crates.io-index" 206 + checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" 207 + dependencies = [ 208 + "proc-macro2", 209 + ] 210 + 211 + [[package]] 212 + name = "scroll" 213 + version = "0.12.0" 214 + source = "registry+https://github.com/rust-lang/crates.io-index" 215 + checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 216 + dependencies = [ 217 + "scroll_derive", 218 + ] 219 + 220 + [[package]] 221 + name = "scroll_derive" 222 + version = "0.12.1" 223 + source = "registry+https://github.com/rust-lang/crates.io-index" 224 + checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" 225 + dependencies = [ 226 + "proc-macro2", 227 + "quote", 228 + "syn", 229 + ] 230 + 231 + [[package]] 232 + name = "strsim" 233 + version = "0.11.1" 234 + source = "registry+https://github.com/rust-lang/crates.io-index" 235 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 236 + 237 + [[package]] 238 + name = "syn" 239 + version = "2.0.114" 240 + source = "registry+https://github.com/rust-lang/crates.io-index" 241 + checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 242 + dependencies = [ 243 + "proc-macro2", 244 + "quote", 245 + "unicode-ident", 246 + ] 247 + 248 + [[package]] 249 + name = "thiserror" 250 + version = "2.0.17" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 253 + dependencies = [ 254 + "thiserror-impl", 255 + ] 256 + 257 + [[package]] 258 + name = "thiserror-impl" 259 + version = "2.0.17" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 262 + dependencies = [ 263 + "proc-macro2", 264 + "quote", 265 + "syn", 266 + ] 267 + 268 + [[package]] 269 + name = "turnstile-ctl" 270 + version = "0.1.0" 271 + dependencies = [ 272 + "clap", 273 + "goblin", 274 + "iced-x86", 275 + "libc", 276 + "nix", 277 + "thiserror", 278 + ] 279 + 280 + [[package]] 281 + name = "unicode-ident" 282 + version = "1.0.22" 283 + source = "registry+https://github.com/rust-lang/crates.io-index" 284 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 285 + 286 + [[package]] 287 + name = "utf8parse" 288 + version = "0.2.2" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 291 + 292 + [[package]] 293 + name = "windows-link" 294 + version = "0.2.1" 295 + source = "registry+https://github.com/rust-lang/crates.io-index" 296 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 297 + 298 + [[package]] 299 + name = "windows-sys" 300 + version = "0.61.2" 301 + source = "registry+https://github.com/rust-lang/crates.io-index" 302 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 303 + dependencies = [ 304 + "windows-link", 305 + ]
+19
turnstile-ctl/Cargo.toml
··· 1 + [package] 2 + name = "turnstile-ctl" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + goblin = "0.9" 8 + clap = { version = "4", features = ["derive"] } 9 + nix = { version = "0.29", features = ["mman", "ioctl"] } 10 + libc = "0.2" 11 + iced-x86 = { version = "1.21", optional = true } 12 + thiserror = "2" 13 + 14 + [features] 15 + default = [] 16 + disasm = ["iced-x86"] 17 + 18 + [profile.release] 19 + lto = true
+214
turnstile-ctl/src/elf.rs
··· 1 + use crate::error::{Result, TurnstileError}; 2 + use goblin::elf::{Elf, SectionHeader}; 3 + use std::fs; 4 + use std::path::Path; 5 + 6 + const KASLR_PATTERN_SIZE: usize = 32; 7 + 8 + #[derive(Debug, Clone)] 9 + pub struct KernelSection { 10 + pub name: &'static str, 11 + pub vaddr: u64, 12 + pub size: u64, 13 + pub flags: u64, 14 + } 15 + 16 + #[derive(Debug, Clone)] 17 + pub struct FunctionEntry { 18 + pub vaddr: u64, 19 + pub name: String, 20 + } 21 + 22 + #[derive(Debug, Clone)] 23 + pub struct JumpEntry { 24 + pub code_addr: u64, 25 + pub target_addr: u64, 26 + pub key_addr: u64, 27 + } 28 + 29 + #[derive(Debug)] 30 + pub struct KernelLayout { 31 + pub text: KernelSection, 32 + pub rodata: KernelSection, 33 + pub stext: u64, 34 + pub etext: u64, 35 + pub srodata: u64, 36 + pub erodata: u64, 37 + pub function_entries: Vec<FunctionEntry>, 38 + pub jump_entries: Vec<JumpEntry>, 39 + pub kaslr_pattern: [u8; KASLR_PATTERN_SIZE], 40 + pub kaslr_pattern_vaddr: u64, 41 + } 42 + 43 + fn find_section<'a>(elf: &'a Elf, name: &str) -> Option<&'a SectionHeader> { 44 + elf.section_headers.iter().find(|sh| { 45 + elf.shdr_strtab 46 + .get_at(sh.sh_name) 47 + .map(|n| n == name) 48 + .unwrap_or(false) 49 + }) 50 + } 51 + 52 + fn find_symbol(elf: &Elf, names: &[&str]) -> Option<u64> { 53 + for sym in &elf.syms { 54 + if let Some(name) = elf.strtab.get_at(sym.st_name) { 55 + if names.contains(&name) { 56 + return Some(sym.st_value); 57 + } 58 + } 59 + } 60 + None 61 + } 62 + 63 + fn find_symbol_exact(elf: &Elf, target: &str) -> Option<u64> { 64 + for sym in &elf.syms { 65 + if let Some(name) = elf.strtab.get_at(sym.st_name) { 66 + if name == target { 67 + return Some(sym.st_value); 68 + } 69 + } 70 + } 71 + None 72 + } 73 + 74 + fn vaddr_to_file_offset(elf: &Elf, vaddr: u64) -> Option<usize> { 75 + for phdr in &elf.program_headers { 76 + if phdr.p_type == goblin::elf::program_header::PT_LOAD 77 + && vaddr >= phdr.p_vaddr 78 + && vaddr < phdr.p_vaddr + phdr.p_memsz 79 + { 80 + let offset = vaddr - phdr.p_vaddr + phdr.p_offset; 81 + return Some(offset as usize); 82 + } 83 + } 84 + None 85 + } 86 + 87 + pub fn parse_vmlinux<P: AsRef<Path>>(path: P) -> Result<KernelLayout> { 88 + let data = fs::read(path)?; 89 + let elf = Elf::parse(&data).map_err(|e| TurnstileError::Elf(e.to_string()))?; 90 + 91 + if elf.header.e_machine != goblin::elf::header::EM_X86_64 { 92 + return Err(TurnstileError::InvalidVmlinux("not x86_64")); 93 + } 94 + 95 + if elf.header.e_type != goblin::elf::header::ET_EXEC { 96 + return Err(TurnstileError::InvalidVmlinux("not ET_EXEC")); 97 + } 98 + 99 + let text_sh = find_section(&elf, ".text").ok_or(TurnstileError::SectionNotFound(".text"))?; 100 + let rodata_sh = 101 + find_section(&elf, ".rodata").ok_or(TurnstileError::SectionNotFound(".rodata"))?; 102 + 103 + let text = KernelSection { 104 + name: ".text", 105 + vaddr: text_sh.sh_addr, 106 + size: text_sh.sh_size, 107 + flags: text_sh.sh_flags, 108 + }; 109 + 110 + let rodata = KernelSection { 111 + name: ".rodata", 112 + vaddr: rodata_sh.sh_addr, 113 + size: rodata_sh.sh_size, 114 + flags: rodata_sh.sh_flags, 115 + }; 116 + 117 + let stext = find_symbol(&elf, &["_stext", "startup_64"]) 118 + .ok_or(TurnstileError::SymbolNotFound("_stext"))?; 119 + let etext = 120 + find_symbol(&elf, &["_etext"]).ok_or(TurnstileError::SymbolNotFound("_etext"))?; 121 + let srodata = find_symbol(&elf, &["__start_rodata", "__srodata"]) 122 + .ok_or(TurnstileError::SymbolNotFound("__start_rodata"))?; 123 + let erodata = find_symbol(&elf, &["__end_rodata", "__erodata"]) 124 + .ok_or(TurnstileError::SymbolNotFound("__end_rodata"))?; 125 + 126 + let mut function_entries = Vec::new(); 127 + for sym in &elf.syms { 128 + if sym.st_type() == goblin::elf::sym::STT_FUNC && sym.st_value != 0 && sym.st_size > 0 { 129 + let name = elf 130 + .strtab 131 + .get_at(sym.st_name) 132 + .unwrap_or("") 133 + .to_string(); 134 + if !name.is_empty() { 135 + function_entries.push(FunctionEntry { 136 + vaddr: sym.st_value, 137 + name, 138 + }); 139 + } 140 + } 141 + } 142 + function_entries.sort_by_key(|f| f.vaddr); 143 + 144 + let mut jump_entries = Vec::new(); 145 + if let Some(jump_sh) = find_section(&elf, "__jump_table") { 146 + let offset = jump_sh.sh_offset as usize; 147 + let size = jump_sh.sh_size as usize; 148 + let entry_size = 16; 149 + 150 + if size.is_multiple_of(entry_size) && offset + size <= data.len() { 151 + let count = size / entry_size; 152 + for i in 0..count { 153 + let base = offset + i * entry_size; 154 + let entry_vaddr = jump_sh.sh_addr + (i * entry_size) as u64; 155 + 156 + let code_offset = i32::from_le_bytes(data[base..base + 4].try_into().unwrap()); 157 + let target_offset = 158 + i32::from_le_bytes(data[base + 4..base + 8].try_into().unwrap()); 159 + let key_addr = i64::from_le_bytes(data[base + 8..base + 16].try_into().unwrap()); 160 + 161 + let code_addr = (entry_vaddr as i64 + code_offset as i64) as u64; 162 + let target_addr = (entry_vaddr as i64 + 4 + target_offset as i64) as u64; 163 + 164 + jump_entries.push(JumpEntry { 165 + code_addr, 166 + target_addr, 167 + key_addr: key_addr as u64, 168 + }); 169 + } 170 + } 171 + } 172 + 173 + let startup_64 = 174 + find_symbol_exact(&elf, "startup_64").ok_or(TurnstileError::SymbolNotFound("startup_64"))?; 175 + 176 + let file_offset = vaddr_to_file_offset(&elf, startup_64) 177 + .ok_or(TurnstileError::InvalidVmlinux("startup_64 vaddr not in any PT_LOAD segment"))?; 178 + 179 + if file_offset + KASLR_PATTERN_SIZE > data.len() { 180 + return Err(TurnstileError::InvalidVmlinux( 181 + "startup_64 pattern extends beyond file bounds", 182 + )); 183 + } 184 + 185 + let mut kaslr_pattern = [0u8; KASLR_PATTERN_SIZE]; 186 + kaslr_pattern.copy_from_slice(&data[file_offset..file_offset + KASLR_PATTERN_SIZE]); 187 + 188 + Ok(KernelLayout { 189 + text, 190 + rodata, 191 + stext, 192 + etext, 193 + srodata, 194 + erodata, 195 + function_entries, 196 + jump_entries, 197 + kaslr_pattern, 198 + kaslr_pattern_vaddr: startup_64, 199 + }) 200 + } 201 + 202 + pub fn resolve_symbol(layout: &KernelLayout, addr: u64) -> Option<(&str, u64)> { 203 + let idx = layout 204 + .function_entries 205 + .binary_search_by_key(&addr, |f| f.vaddr); 206 + match idx { 207 + Ok(i) => Some((&layout.function_entries[i].name, 0)), 208 + Err(i) if i > 0 => { 209 + let entry = &layout.function_entries[i - 1]; 210 + Some((&entry.name, addr - entry.vaddr)) 211 + } 212 + _ => None, 213 + } 214 + }
+27
turnstile-ctl/src/error.rs
··· 1 + use thiserror::Error; 2 + 3 + #[derive(Error, Debug)] 4 + pub enum TurnstileError { 5 + #[error("io error: {0}")] 6 + Io(#[from] std::io::Error), 7 + #[error("elf parse error: {0}")] 8 + Elf(String), 9 + #[error("section not found: {0}")] 10 + SectionNotFound(&'static str), 11 + #[error("symbol not found: {0}")] 12 + SymbolNotFound(&'static str), 13 + #[error("kaslr detection failed")] 14 + KaslrDetectionFailed, 15 + #[error("hypercall failed: {0}")] 16 + Hypercall(i32), 17 + #[error("domain not found: {0}")] 18 + DomainNotFound(u16), 19 + #[error("invalid vmlinux: {0}")] 20 + InvalidVmlinux(&'static str), 21 + #[error("invalid policy: {0}")] 22 + InvalidPolicy(&'static str), 23 + #[error("metadata limit exceeded: {0} entries (max {1})")] 24 + MetadataLimitExceeded(usize, usize), 25 + } 26 + 27 + pub type Result<T> = std::result::Result<T, TurnstileError>;
+181
turnstile-ctl/src/guest.rs
··· 1 + use std::fs::{File, OpenOptions}; 2 + use std::os::unix::io::AsRawFd; 3 + 4 + use crate::elf::KernelLayout; 5 + use crate::error::{Result, TurnstileError}; 6 + 7 + const PAGE_SIZE: usize = 4096; 8 + const PAGE_SHIFT: u32 = 12; 9 + 10 + pub const KERNEL_VIRT_BASE: u64 = 0xffffffff80000000; 11 + const PHYSMAP_BASE: u64 = 0xffff800000000000; 12 + 13 + pub const KERNEL_PHYS_START: u64 = 0x100_0000; 14 + const KERNEL_PHYS_END: u64 = 0x4000_0000; 15 + const KERNEL_PHYS_ALIGN: u64 = 0x20_0000; 16 + 17 + #[repr(C)] 18 + struct PrivcmdMmapEntry { 19 + va: u64, 20 + mfn: u64, 21 + npages: u64, 22 + } 23 + 24 + #[repr(C)] 25 + struct PrivcmdMmap { 26 + num: i32, 27 + dom: u16, 28 + entry: *mut PrivcmdMmapEntry, 29 + } 30 + 31 + nix::ioctl_readwrite!(privcmd_mmap, b'P', 2, PrivcmdMmap); 32 + 33 + pub struct MappedPage { 34 + ptr: *mut u8, 35 + size: usize, 36 + } 37 + 38 + impl MappedPage { 39 + pub fn as_slice(&self) -> &[u8] { 40 + unsafe { std::slice::from_raw_parts(self.ptr, self.size) } 41 + } 42 + } 43 + 44 + impl Drop for MappedPage { 45 + fn drop(&mut self) { 46 + if !self.ptr.is_null() { 47 + unsafe { 48 + libc::munmap(self.ptr as *mut libc::c_void, self.size); 49 + } 50 + } 51 + } 52 + } 53 + 54 + pub struct GuestMemory { 55 + privcmd: File, 56 + domid: u16, 57 + } 58 + 59 + impl GuestMemory { 60 + pub fn new(domid: u16) -> Result<Self> { 61 + let privcmd = OpenOptions::new() 62 + .read(true) 63 + .write(true) 64 + .open("/dev/xen/privcmd")?; 65 + Ok(Self { privcmd, domid }) 66 + } 67 + 68 + pub fn map_page(&self, gfn: u64) -> Result<MappedPage> { 69 + let ptr = unsafe { 70 + libc::mmap( 71 + std::ptr::null_mut(), 72 + PAGE_SIZE, 73 + libc::PROT_READ, 74 + libc::MAP_SHARED, 75 + self.privcmd.as_raw_fd(), 76 + 0, 77 + ) 78 + }; 79 + 80 + if ptr == libc::MAP_FAILED { 81 + return Err(TurnstileError::Io(std::io::Error::last_os_error())); 82 + } 83 + 84 + let mut entry = PrivcmdMmapEntry { 85 + va: ptr as u64, 86 + mfn: gfn, 87 + npages: 1, 88 + }; 89 + 90 + let mut cmd = PrivcmdMmap { 91 + num: 1, 92 + dom: self.domid, 93 + entry: &mut entry, 94 + }; 95 + 96 + let ret = unsafe { privcmd_mmap(self.privcmd.as_raw_fd(), &mut cmd) }; 97 + 98 + if let Err(errno) = ret { 99 + unsafe { 100 + libc::munmap(ptr, PAGE_SIZE); 101 + } 102 + return Err(TurnstileError::Io(std::io::Error::from(errno))); 103 + } 104 + 105 + Ok(MappedPage { 106 + ptr: ptr as *mut u8, 107 + size: PAGE_SIZE, 108 + }) 109 + } 110 + 111 + pub fn read_gpa(&self, gpa: u64, buf: &mut [u8]) -> Result<()> { 112 + let mut offset = 0usize; 113 + let mut remaining = buf.len(); 114 + let mut current_gpa = gpa; 115 + 116 + while remaining > 0 { 117 + let gfn = current_gpa >> PAGE_SHIFT; 118 + let page_offset = (current_gpa & (PAGE_SIZE as u64 - 1)) as usize; 119 + let chunk = std::cmp::min(remaining, PAGE_SIZE - page_offset); 120 + 121 + let page = self.map_page(gfn)?; 122 + let src = &page.as_slice()[page_offset..page_offset + chunk]; 123 + buf[offset..offset + chunk].copy_from_slice(src); 124 + 125 + offset += chunk; 126 + remaining -= chunk; 127 + current_gpa += chunk as u64; 128 + } 129 + 130 + Ok(()) 131 + } 132 + 133 + fn check_pattern_at(&self, gpa: u64, pattern: &[u8; 32]) -> Result<bool> { 134 + let mut buf = [0u8; 32]; 135 + self.read_gpa(gpa, &mut buf)?; 136 + Ok(buf == *pattern) 137 + } 138 + 139 + pub fn detect_kaslr_slide(&self, layout: &KernelLayout) -> Result<i64> { 140 + let pattern = &layout.kaslr_pattern; 141 + let expected_vaddr = layout.kaslr_pattern_vaddr; 142 + 143 + for gpa in (KERNEL_PHYS_START..KERNEL_PHYS_END).step_by(KERNEL_PHYS_ALIGN as usize) { 144 + if let Ok(found) = self.check_pattern_at(gpa, pattern) { 145 + if found { 146 + let expected_phys = expected_vaddr - KERNEL_VIRT_BASE; 147 + let slide = gpa as i64 - expected_phys as i64; 148 + return Ok(slide); 149 + } 150 + } 151 + } 152 + 153 + Err(TurnstileError::KaslrDetectionFailed) 154 + } 155 + } 156 + 157 + fn vaddr_to_gpa(vaddr: u64) -> Option<u64> { 158 + if vaddr >= KERNEL_VIRT_BASE { 159 + Some(vaddr - KERNEL_VIRT_BASE) 160 + } else if vaddr >= PHYSMAP_BASE { 161 + Some(vaddr - PHYSMAP_BASE) 162 + } else { 163 + None 164 + } 165 + } 166 + 167 + pub fn calculate_protected_gpas(layout: &KernelLayout, slide: i64) -> Vec<(u64, u64)> { 168 + let mut ranges = Vec::new(); 169 + 170 + let text_vaddr = (layout.text.vaddr as i64 + slide) as u64; 171 + if let Some(text_gpa) = vaddr_to_gpa(text_vaddr) { 172 + ranges.push((text_gpa, layout.text.size)); 173 + } 174 + 175 + let rodata_vaddr = (layout.rodata.vaddr as i64 + slide) as u64; 176 + if let Some(rodata_gpa) = vaddr_to_gpa(rodata_vaddr) { 177 + ranges.push((rodata_gpa, layout.rodata.size)); 178 + } 179 + 180 + ranges 181 + }
+150
turnstile-ctl/src/hypercall.rs
··· 1 + use std::fs::{File, OpenOptions}; 2 + use std::os::unix::io::AsRawFd; 3 + 4 + use crate::error::{Result, TurnstileError}; 5 + use crate::xen::*; 6 + 7 + pub struct XenHandle { 8 + privcmd: File, 9 + } 10 + 11 + impl XenHandle { 12 + pub fn open() -> Result<Self> { 13 + let privcmd = OpenOptions::new() 14 + .read(true) 15 + .write(true) 16 + .open("/dev/xen/privcmd")?; 17 + Ok(Self { privcmd }) 18 + } 19 + 20 + fn hypercall(&self, op: u32, domid: u16, arg: u64) -> Result<i64> { 21 + let mut hc = PrivcmdHypercall { 22 + op: HYPERVISOR_TURNSTILE, 23 + arg: [op as _, domid as _, arg, 0, 0], 24 + }; 25 + 26 + let ret = unsafe { privcmd_hypercall(self.privcmd.as_raw_fd(), &mut hc) }; 27 + 28 + match ret { 29 + Ok(v) => Ok(v as i64), 30 + Err(nix::errno::Errno::ESRCH) => Err(TurnstileError::DomainNotFound(domid)), 31 + Err(e) => Err(TurnstileError::Hypercall(e as i32)), 32 + } 33 + } 34 + 35 + pub fn set_policy(&self, domid: u16, policy: u32) -> Result<()> { 36 + let op = TurnstileOpSetPolicy { policy, _pad: 0 }; 37 + self.hypercall( 38 + TURNSTILE_OP_SET_POLICY, 39 + domid, 40 + &op as *const _ as u64, 41 + )?; 42 + Ok(()) 43 + } 44 + 45 + pub fn protect_range(&self, domid: u16, gpa_start: u64, length: u64) -> Result<()> { 46 + let op = TurnstileOpProtectRange { gpa_start, length }; 47 + self.hypercall( 48 + TURNSTILE_OP_PROTECT_RANGE, 49 + domid, 50 + &op as *const _ as u64, 51 + )?; 52 + Ok(()) 53 + } 54 + 55 + pub fn unprotect_range(&self, domid: u16, gpa_start: u64, length: u64) -> Result<()> { 56 + let op = TurnstileOpProtectRange { gpa_start, length }; 57 + self.hypercall( 58 + TURNSTILE_OP_UNPROTECT_RANGE, 59 + domid, 60 + &op as *const _ as u64, 61 + )?; 62 + Ok(()) 63 + } 64 + 65 + pub fn get_stats(&self, domid: u16) -> Result<TurnstileStats> { 66 + let mut stats = TurnstileStats::default(); 67 + self.hypercall( 68 + TURNSTILE_OP_GET_STATS, 69 + domid, 70 + &mut stats as *mut _ as u64, 71 + )?; 72 + Ok(stats) 73 + } 74 + 75 + pub fn get_violations( 76 + &self, 77 + domid: u16, 78 + buffer: &mut [TurnstileViolation], 79 + ) -> Result<(u32, u32)> { 80 + let mut op = TurnstileOpGetViolations { 81 + buffer_ptr: buffer.as_ptr() as u64, 82 + max_entries: buffer.len() as u32, 83 + entries_returned: 0, 84 + overflow_count: 0, 85 + _pad: 0, 86 + }; 87 + 88 + self.hypercall( 89 + TURNSTILE_OP_GET_VIOLATIONS, 90 + domid, 91 + &mut op as *mut _ as u64, 92 + )?; 93 + 94 + Ok((op.entries_returned, op.overflow_count)) 95 + } 96 + 97 + pub fn upload_func_entries(&self, domid: u16, entries: &[u64]) -> Result<()> { 98 + if entries.is_empty() { 99 + return Ok(()); 100 + } 101 + 102 + if entries.len() > TURNSTILE_MAX_FUNC_ENTRIES { 103 + return Err(TurnstileError::MetadataLimitExceeded( 104 + entries.len(), 105 + TURNSTILE_MAX_FUNC_ENTRIES, 106 + )); 107 + } 108 + 109 + let op = TurnstileOpUploadMetadata { 110 + buffer_ptr: entries.as_ptr() as u64, 111 + metadata_type: TURNSTILE_META_FUNC_ENTRIES, 112 + count: entries.len() as u32, 113 + }; 114 + 115 + self.hypercall( 116 + TURNSTILE_OP_UPLOAD_METADATA, 117 + domid, 118 + &op as *const _ as u64, 119 + )?; 120 + 121 + Ok(()) 122 + } 123 + 124 + pub fn upload_jump_entries(&self, domid: u16, entries: &[JumpEntryUpload]) -> Result<()> { 125 + if entries.is_empty() { 126 + return Ok(()); 127 + } 128 + 129 + if entries.len() > TURNSTILE_MAX_JUMP_ENTRIES { 130 + return Err(TurnstileError::MetadataLimitExceeded( 131 + entries.len(), 132 + TURNSTILE_MAX_JUMP_ENTRIES, 133 + )); 134 + } 135 + 136 + let op = TurnstileOpUploadMetadata { 137 + buffer_ptr: entries.as_ptr() as u64, 138 + metadata_type: TURNSTILE_META_JUMP_ENTRIES, 139 + count: entries.len() as u32, 140 + }; 141 + 142 + self.hypercall( 143 + TURNSTILE_OP_UPLOAD_METADATA, 144 + domid, 145 + &op as *const _ as u64, 146 + )?; 147 + 148 + Ok(()) 149 + } 150 + }
+250
turnstile-ctl/src/main.rs
··· 1 + mod elf; 2 + mod error; 3 + mod guest; 4 + mod hypercall; 5 + mod monitor; 6 + mod xen; 7 + 8 + use std::path::PathBuf; 9 + use std::time::Duration; 10 + 11 + use clap::{Parser, Subcommand}; 12 + 13 + use error::{Result, TurnstileError}; 14 + use guest::{calculate_protected_gpas, GuestMemory}; 15 + use hypercall::XenHandle; 16 + use monitor::Monitor; 17 + use xen::{ 18 + JumpEntryUpload, TURNSTILE_POLICY_AUDIT, TURNSTILE_POLICY_DISABLED, TURNSTILE_POLICY_ENFORCE, 19 + }; 20 + 21 + #[derive(Parser)] 22 + #[command(name = "turnstile-ctl")] 23 + #[command(about = "turnstile hypervisor control utility")] 24 + struct Cli { 25 + #[command(subcommand)] 26 + command: Commands, 27 + } 28 + 29 + #[derive(Subcommand)] 30 + enum Commands { 31 + Dump { 32 + #[arg(help = "path to vmlinux")] 33 + vmlinux: PathBuf, 34 + }, 35 + Protect { 36 + #[arg(help = "domain id")] 37 + domid: u16, 38 + #[arg(help = "path to vmlinux")] 39 + vmlinux: PathBuf, 40 + }, 41 + Disable { 42 + #[arg(help = "domain id")] 43 + domid: u16, 44 + }, 45 + Policy { 46 + #[arg(help = "domain id")] 47 + domid: u16, 48 + #[arg(help = "policy: disabled, audit, or enforce")] 49 + policy: String, 50 + }, 51 + Monitor { 52 + #[arg(help = "domain id")] 53 + domid: u16, 54 + #[arg(help = "path to vmlinux")] 55 + vmlinux: PathBuf, 56 + #[arg(short, long, default_value = "100", help = "poll interval in milliseconds")] 57 + interval_ms: u64, 58 + }, 59 + Stats { 60 + #[arg(help = "domain id")] 61 + domid: u16, 62 + }, 63 + } 64 + 65 + fn main() { 66 + let cli = Cli::parse(); 67 + 68 + let result = match cli.command { 69 + Commands::Dump { vmlinux } => dump_vmlinux(&vmlinux), 70 + Commands::Protect { domid, vmlinux } => cmd_protect(domid, &vmlinux), 71 + Commands::Disable { domid } => cmd_disable(domid), 72 + Commands::Policy { domid, policy } => cmd_policy(domid, &policy), 73 + Commands::Monitor { 74 + domid, 75 + vmlinux, 76 + interval_ms, 77 + } => cmd_monitor(domid, &vmlinux, interval_ms), 78 + Commands::Stats { domid } => cmd_stats(domid), 79 + }; 80 + 81 + if let Err(e) = result { 82 + eprintln!("error: {}", e); 83 + std::process::exit(1); 84 + } 85 + } 86 + 87 + fn dump_vmlinux(path: &std::path::Path) -> Result<()> { 88 + let layout = elf::parse_vmlinux(path)?; 89 + 90 + println!("kernel layout from {:?}", path); 91 + println!(); 92 + 93 + println!("sections:"); 94 + println!( 95 + " .text: 0x{:016x} - 0x{:016x} ({} bytes, flags 0x{:x})", 96 + layout.text.vaddr, 97 + layout.text.vaddr + layout.text.size, 98 + layout.text.size, 99 + layout.text.flags 100 + ); 101 + println!( 102 + " .rodata: 0x{:016x} - 0x{:016x} ({} bytes, flags 0x{:x})", 103 + layout.rodata.vaddr, 104 + layout.rodata.vaddr + layout.rodata.size, 105 + layout.rodata.size, 106 + layout.rodata.flags 107 + ); 108 + println!(); 109 + 110 + println!("symbol bounds:"); 111 + println!( 112 + " _stext..._etext: 0x{:016x} - 0x{:016x}", 113 + layout.stext, layout.etext 114 + ); 115 + println!( 116 + " __start_rodata...__end_rodata: 0x{:016x} - 0x{:016x}", 117 + layout.srodata, layout.erodata 118 + ); 119 + println!(); 120 + 121 + println!("entries:"); 122 + println!(" function entries: {}", layout.function_entries.len()); 123 + println!(" jump table entries: {}", layout.jump_entries.len()); 124 + println!(); 125 + 126 + println!("kaslr detection:"); 127 + println!(" pattern vaddr: 0x{:016x}", layout.kaslr_pattern_vaddr); 128 + print!(" pattern bytes: "); 129 + for b in &layout.kaslr_pattern { 130 + print!("{:02x}", b); 131 + } 132 + println!(); 133 + 134 + Ok(()) 135 + } 136 + 137 + fn cmd_protect(domid: u16, vmlinux: &std::path::Path) -> Result<()> { 138 + println!("parsing vmlinux..."); 139 + let layout = elf::parse_vmlinux(vmlinux)?; 140 + 141 + println!("opening guest memory for domain {}...", domid); 142 + let guest = GuestMemory::new(domid)?; 143 + 144 + println!("detecting kaslr slide..."); 145 + let slide = guest.detect_kaslr_slide(&layout)?; 146 + println!(" kaslr slide: {:#x}", slide); 147 + 148 + let xen = XenHandle::open()?; 149 + 150 + let ranges = calculate_protected_gpas(&layout, slide); 151 + println!("protecting {} memory ranges:", ranges.len()); 152 + for (gpa, len) in &ranges { 153 + println!(" gpa {:#x} - {:#x} ({} bytes)", gpa, gpa + len, len); 154 + xen.protect_range(domid, *gpa, *len)?; 155 + } 156 + 157 + let func_addrs: Vec<u64> = layout 158 + .function_entries 159 + .iter() 160 + .map(|f| (f.vaddr as i64 + slide) as u64) 161 + .collect(); 162 + 163 + println!("uploading {} function entries...", func_addrs.len()); 164 + xen.upload_func_entries(domid, &func_addrs)?; 165 + 166 + let jump_uploads: Vec<JumpEntryUpload> = layout 167 + .jump_entries 168 + .iter() 169 + .map(|j| JumpEntryUpload { 170 + code_addr: (j.code_addr as i64 + slide) as u64, 171 + target_addr: (j.target_addr as i64 + slide) as u64, 172 + key_addr: (j.key_addr as i64 + slide) as u64, 173 + }) 174 + .collect(); 175 + 176 + println!("uploading {} jump table entries...", jump_uploads.len()); 177 + xen.upload_jump_entries(domid, &jump_uploads)?; 178 + 179 + println!("setting policy to audit..."); 180 + xen.set_policy(domid, TURNSTILE_POLICY_AUDIT)?; 181 + 182 + println!("protection enabled for domain {}", domid); 183 + 184 + Ok(()) 185 + } 186 + 187 + fn cmd_disable(domid: u16) -> Result<()> { 188 + let xen = XenHandle::open()?; 189 + xen.set_policy(domid, TURNSTILE_POLICY_DISABLED)?; 190 + println!("enforcement disabled for domain {} (ept protections remain)", domid); 191 + Ok(()) 192 + } 193 + 194 + fn cmd_policy(domid: u16, policy_str: &str) -> Result<()> { 195 + let policy = match policy_str.to_lowercase().as_str() { 196 + "disabled" | "off" | "0" => TURNSTILE_POLICY_DISABLED, 197 + "audit" | "log" | "1" => TURNSTILE_POLICY_AUDIT, 198 + "enforce" | "block" | "2" => TURNSTILE_POLICY_ENFORCE, 199 + _ => { 200 + return Err(TurnstileError::InvalidPolicy( 201 + "use disabled, audit, or enforce", 202 + )); 203 + } 204 + }; 205 + 206 + let xen = XenHandle::open()?; 207 + xen.set_policy(domid, policy)?; 208 + 209 + let name = match policy { 210 + TURNSTILE_POLICY_DISABLED => "disabled", 211 + TURNSTILE_POLICY_AUDIT => "audit", 212 + TURNSTILE_POLICY_ENFORCE => "enforce", 213 + _ => "unknown", 214 + }; 215 + 216 + println!("policy set to {} for domain {}", name, domid); 217 + Ok(()) 218 + } 219 + 220 + fn cmd_monitor(domid: u16, vmlinux: &std::path::Path, interval_ms: u64) -> Result<()> { 221 + println!("parsing vmlinux..."); 222 + let layout = elf::parse_vmlinux(vmlinux)?; 223 + 224 + println!("opening guest memory for domain {}...", domid); 225 + let guest = GuestMemory::new(domid)?; 226 + 227 + println!("detecting kaslr slide..."); 228 + let slide = guest.detect_kaslr_slide(&layout)?; 229 + println!(" kaslr slide: {:#x}", slide); 230 + 231 + let xen = XenHandle::open()?; 232 + let monitor = Monitor::new(xen, domid, layout, slide); 233 + monitor.run(Duration::from_millis(interval_ms)) 234 + } 235 + 236 + fn cmd_stats(domid: u16) -> Result<()> { 237 + let xen = XenHandle::open()?; 238 + let stats = xen.get_stats(domid)?; 239 + 240 + println!("turnstile statistics for domain {}:", domid); 241 + println!(" violations total: {}", stats.violations_total); 242 + println!(" violations blocked: {}", stats.violations_blocked); 243 + println!(" violations allowed: {}", stats.violations_allowed); 244 + println!(" implicit ftrace: {}", stats.implicit_ftrace); 245 + println!(" implicit static key: {}", stats.implicit_static_key); 246 + println!(" explicit write grants: {}", stats.explicit_write_grants); 247 + println!(" ring overflows: {}", stats.ring_overflows); 248 + 249 + Ok(()) 250 + }
+134
turnstile-ctl/src/monitor.rs
··· 1 + use std::time::Duration; 2 + 3 + use crate::elf::KernelLayout; 4 + use crate::error::Result; 5 + use crate::guest::{KERNEL_PHYS_START, KERNEL_VIRT_BASE}; 6 + use crate::hypercall::XenHandle; 7 + use crate::xen::TurnstileViolation; 8 + 9 + const VIOLATION_BUFFER_SIZE: usize = 64; 10 + 11 + pub struct Monitor { 12 + xen: XenHandle, 13 + domid: u16, 14 + layout: KernelLayout, 15 + slide: i64, 16 + } 17 + 18 + impl Monitor { 19 + pub fn new(xen: XenHandle, domid: u16, layout: KernelLayout, slide: i64) -> Self { 20 + Self { 21 + xen, 22 + domid, 23 + layout, 24 + slide, 25 + } 26 + } 27 + 28 + pub fn run(&self, poll_interval: Duration) -> Result<()> { 29 + let mut buffer = vec![TurnstileViolation::default(); VIOLATION_BUFFER_SIZE]; 30 + 31 + println!("monitoring domain {} for write violations", self.domid); 32 + println!("poll interval: {}ms", poll_interval.as_millis()); 33 + println!("kaslr slide: {:#x}", self.slide); 34 + println!(); 35 + 36 + loop { 37 + let (count, overflow) = self.xen.get_violations(self.domid, &mut buffer)?; 38 + 39 + if overflow > 0 { 40 + eprintln!("warning: {} violations lost to ring overflow", overflow); 41 + } 42 + 43 + for v in buffer.iter().take(count as usize) { 44 + self.format_violation(v); 45 + } 46 + 47 + std::thread::sleep(poll_interval); 48 + } 49 + } 50 + 51 + fn format_violation(&self, v: &TurnstileViolation) { 52 + let response_str = if v.response == 0 { "ALLOWED" } else { "BLOCKED" }; 53 + 54 + println!("--- violation seq={} ---", v.seq); 55 + println!(" timestamp: {}", v.timestamp); 56 + println!(" gpa: {:#018x} ({})", v.gpa, self.resolve_gpa(v.gpa)); 57 + println!( 58 + " rip: {:#018x} ({})", 59 + v.rip, 60 + self.resolve_addr(v.rip) 61 + ); 62 + println!(" cr3: {:#018x}", v.cr3); 63 + println!(" response: {}", response_str); 64 + println!(" access: {:#x}", v.access_flags); 65 + 66 + let insn_len = v.insn_bytes.iter().take_while(|&&b| b != 0).count(); 67 + let insn_len = if insn_len == 0 { 16 } else { insn_len }; 68 + 69 + print!(" insn: "); 70 + for b in v.insn_bytes.iter().take(insn_len) { 71 + print!("{:02x} ", b); 72 + } 73 + 74 + #[cfg(feature = "disasm")] 75 + { 76 + let disasm = self.disassemble(&v.insn_bytes[..insn_len], v.rip); 77 + print!(" ; {}", disasm); 78 + } 79 + 80 + println!(); 81 + println!(); 82 + } 83 + 84 + fn resolve_addr(&self, vaddr: u64) -> String { 85 + let unslid = (vaddr as i64 - self.slide) as u64; 86 + 87 + if let Some((name, offset)) = crate::elf::resolve_symbol(&self.layout, unslid) { 88 + if offset == 0 { 89 + return name.to_string(); 90 + } 91 + return format!("{}+{:#x}", name, offset); 92 + } 93 + 94 + if unslid >= self.layout.text.vaddr 95 + && unslid < self.layout.text.vaddr + self.layout.text.size 96 + { 97 + let offset = unslid - self.layout.text.vaddr; 98 + return format!(".text+{:#x}", offset); 99 + } 100 + 101 + if unslid >= self.layout.rodata.vaddr 102 + && unslid < self.layout.rodata.vaddr + self.layout.rodata.size 103 + { 104 + let offset = unslid - self.layout.rodata.vaddr; 105 + return format!(".rodata+{:#x}", offset); 106 + } 107 + 108 + format!("{:#x}", unslid) 109 + } 110 + 111 + fn resolve_gpa(&self, gpa: u64) -> String { 112 + if gpa < KERNEL_PHYS_START { 113 + return format!("gpa:{:#x}", gpa); 114 + } 115 + let vaddr = KERNEL_VIRT_BASE + (gpa - KERNEL_PHYS_START) + self.slide as u64; 116 + self.resolve_addr(vaddr) 117 + } 118 + 119 + #[cfg(feature = "disasm")] 120 + fn disassemble(&self, bytes: &[u8], rip: u64) -> String { 121 + use iced_x86::{Decoder, DecoderOptions, Formatter, IntelFormatter}; 122 + 123 + let mut decoder = Decoder::with_ip(64, bytes, rip, DecoderOptions::NONE); 124 + let mut formatter = IntelFormatter::new(); 125 + 126 + if let Some(insn) = decoder.iter().next() { 127 + let mut output = String::new(); 128 + formatter.format(&insn, &mut output); 129 + output 130 + } else { 131 + "(decode failed)".to_string() 132 + } 133 + } 134 + }
+116
turnstile-ctl/src/xen.rs
··· 1 + use std::os::raw::c_ulong; 2 + 3 + pub const HYPERVISOR_TURNSTILE: c_ulong = 50; 4 + 5 + pub const TURNSTILE_OP_SET_POLICY: u32 = 0; 6 + pub const TURNSTILE_OP_PROTECT_RANGE: u32 = 1; 7 + pub const TURNSTILE_OP_UNPROTECT_RANGE: u32 = 2; 8 + pub const TURNSTILE_OP_REQUEST_WRITE: u32 = 3; 9 + pub const TURNSTILE_OP_GET_VIOLATIONS: u32 = 4; 10 + pub const TURNSTILE_OP_GET_STATS: u32 = 5; 11 + pub const TURNSTILE_OP_UPLOAD_METADATA: u32 = 6; 12 + 13 + pub const TURNSTILE_POLICY_DISABLED: u32 = 0; 14 + pub const TURNSTILE_POLICY_AUDIT: u32 = 1; 15 + pub const TURNSTILE_POLICY_ENFORCE: u32 = 2; 16 + 17 + pub const TURNSTILE_MAX_RANGES: usize = 256; 18 + pub const TURNSTILE_RING_SIZE: usize = 4096; 19 + pub const TURNSTILE_MAX_FUNC_ENTRIES: usize = 65536; 20 + pub const TURNSTILE_MAX_JUMP_ENTRIES: usize = 32768; 21 + 22 + pub const TURNSTILE_META_FUNC_ENTRIES: u32 = 0; 23 + pub const TURNSTILE_META_JUMP_ENTRIES: u32 = 1; 24 + 25 + #[repr(C)] 26 + #[derive(Debug, Clone, Copy, Default)] 27 + pub struct TurnstileViolation { 28 + pub timestamp: u64, 29 + pub gpa: u64, 30 + pub rip: u64, 31 + pub cr3: u64, 32 + pub access_flags: u32, 33 + pub response: u32, 34 + pub insn_bytes: [u8; 16], 35 + pub seq: u32, 36 + pub _pad: u32, 37 + } 38 + 39 + #[repr(C)] 40 + #[derive(Debug, Clone, Copy, Default)] 41 + pub struct TurnstileRange { 42 + pub gpa_start: u64, 43 + pub length: u64, 44 + pub original_type: u32, 45 + pub flags: u32, 46 + } 47 + 48 + #[repr(C)] 49 + #[derive(Debug, Clone, Copy, Default)] 50 + pub struct TurnstileStats { 51 + pub violations_total: u64, 52 + pub violations_blocked: u64, 53 + pub violations_allowed: u64, 54 + pub implicit_ftrace: u64, 55 + pub implicit_static_key: u64, 56 + pub explicit_write_grants: u64, 57 + pub ring_overflows: u64, 58 + } 59 + 60 + #[repr(C)] 61 + #[derive(Debug, Clone, Copy)] 62 + pub struct TurnstileOpSetPolicy { 63 + pub policy: u32, 64 + pub _pad: u32, 65 + } 66 + 67 + #[repr(C)] 68 + #[derive(Debug, Clone, Copy)] 69 + pub struct TurnstileOpProtectRange { 70 + pub gpa_start: u64, 71 + pub length: u64, 72 + } 73 + 74 + #[repr(C)] 75 + #[derive(Debug, Clone, Copy)] 76 + pub struct TurnstileOpRequestWrite { 77 + pub gpa_start: u64, 78 + pub length: u64, 79 + pub timeout_ms: u32, 80 + pub _pad: u32, 81 + } 82 + 83 + #[repr(C)] 84 + #[derive(Debug, Clone, Copy)] 85 + pub struct TurnstileOpGetViolations { 86 + pub buffer_ptr: u64, 87 + pub max_entries: u32, 88 + pub entries_returned: u32, 89 + pub overflow_count: u32, 90 + pub _pad: u32, 91 + } 92 + 93 + #[repr(C)] 94 + #[derive(Debug, Clone, Copy)] 95 + pub struct TurnstileOpUploadMetadata { 96 + pub buffer_ptr: u64, 97 + pub metadata_type: u32, 98 + pub count: u32, 99 + } 100 + 101 + #[repr(C)] 102 + #[derive(Debug, Clone, Copy)] 103 + pub struct JumpEntryUpload { 104 + pub code_addr: u64, 105 + pub target_addr: u64, 106 + pub key_addr: u64, 107 + } 108 + 109 + #[repr(C)] 110 + #[derive(Debug)] 111 + pub struct PrivcmdHypercall { 112 + pub op: c_ulong, 113 + pub arg: [c_ulong; 5], 114 + } 115 + 116 + nix::ioctl_readwrite!(privcmd_hypercall, b'P', 0, PrivcmdHypercall);
+11
turnstile-guest/Makefile
··· 1 + obj-m := turnstile_guest.o 2 + 3 + ccflags-y := -DCONFIG_XEN 4 + 5 + KDIR ?= /lib/modules/$(shell uname -r)/build 6 + 7 + all: 8 + $(MAKE) -C $(KDIR) M=$(PWD) modules 9 + 10 + clean: 11 + $(MAKE) -C $(KDIR) M=$(PWD) clean
+236
turnstile-guest/turnstile_guest.c
··· 1 + #include <linux/module.h> 2 + #include <linux/kernel.h> 3 + #include <linux/version.h> 4 + #include <linux/kallsyms.h> 5 + #include <linux/kprobes.h> 6 + #include <linux/mm.h> 7 + #include <linux/slab.h> 8 + 9 + #ifdef CONFIG_XEN 10 + #include <xen/xen.h> 11 + #include <xen/interface/xen.h> 12 + #include <asm/xen/hypercall.h> 13 + #endif 14 + 15 + MODULE_LICENSE("GPL"); 16 + MODULE_AUTHOR("turnstile"); 17 + MODULE_DESCRIPTION("turnstile guest module for livepatch support"); 18 + 19 + #define TURNSTILE_HYPERCALL_NR 50 20 + #define TURNSTILE_OP_REQUEST_WRITE 3 21 + 22 + struct turnstile_op_request_write { 23 + uint64_t gpa_start; 24 + uint64_t length; 25 + uint32_t timeout_ms; 26 + uint32_t _pad; 27 + }; 28 + 29 + static unsigned long kernel_text_start; 30 + static unsigned long kernel_text_end; 31 + static unsigned long kernel_rodata_start; 32 + static unsigned long kernel_rodata_end; 33 + 34 + static bool turnstile_enabled; 35 + 36 + #ifdef CONFIG_KPROBES 37 + static unsigned long kprobe_get_symbol(const char *name) 38 + { 39 + struct kprobe kp = { .symbol_name = name }; 40 + unsigned long addr = 0; 41 + 42 + if (register_kprobe(&kp) == 0) { 43 + addr = (unsigned long)kp.addr; 44 + unregister_kprobe(&kp); 45 + } 46 + return addr; 47 + } 48 + #endif 49 + 50 + static unsigned long resolve_symbol(const char *name) 51 + { 52 + #ifdef CONFIG_KALLSYMS 53 + #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0) 54 + #ifdef CONFIG_KPROBES 55 + return kprobe_get_symbol(name); 56 + #else 57 + return 0; 58 + #endif 59 + #else 60 + return kallsyms_lookup_name(name); 61 + #endif 62 + #else 63 + return 0; 64 + #endif 65 + } 66 + 67 + static bool addr_in_protected_range(unsigned long addr) 68 + { 69 + return (addr >= kernel_text_start && addr < kernel_text_end) || 70 + (addr >= kernel_rodata_start && addr < kernel_rodata_end); 71 + } 72 + 73 + static unsigned long virt_to_gpa(unsigned long vaddr) 74 + { 75 + if (vaddr < PAGE_OFFSET) 76 + return 0; 77 + 78 + #ifdef CONFIG_X86_64 79 + return __pa(vaddr); 80 + #else 81 + return vaddr - PAGE_OFFSET; 82 + #endif 83 + } 84 + 85 + #ifdef CONFIG_XEN 86 + static int turnstile_hypercall(unsigned int op, void *arg) 87 + { 88 + return _hypercall3(long, TURNSTILE_HYPERCALL_NR, op, DOMID_SELF, arg); 89 + } 90 + #else 91 + static int turnstile_hypercall(unsigned int op, void *arg) 92 + { 93 + return -ENOSYS; 94 + } 95 + #endif 96 + 97 + static int request_write_access(unsigned long addr, unsigned long len) 98 + { 99 + struct turnstile_op_request_write op; 100 + unsigned long gpa; 101 + 102 + if (!turnstile_enabled) 103 + return 0; 104 + 105 + gpa = virt_to_gpa(addr); 106 + if (!gpa) { 107 + pr_warn("turnstile: invalid address %pK\n", (void *)addr); 108 + return -EINVAL; 109 + } 110 + 111 + op.gpa_start = gpa; 112 + op.length = len; 113 + op.timeout_ms = 100; 114 + op._pad = 0; 115 + 116 + return turnstile_hypercall(TURNSTILE_OP_REQUEST_WRITE, &op); 117 + } 118 + 119 + int turnstile_request_patch_access(unsigned long addr, unsigned long len) 120 + { 121 + int ret; 122 + 123 + if (!addr_in_protected_range(addr)) 124 + return 0; 125 + 126 + pr_info("turnstile: requesting write access for %pK len %lu\n", (void *)addr, len); 127 + 128 + ret = request_write_access(addr, len); 129 + if (ret < 0) 130 + pr_warn("turnstile: write access request failed: %d\n", ret); 131 + 132 + return ret; 133 + } 134 + EXPORT_SYMBOL_GPL(turnstile_request_patch_access); 135 + 136 + int turnstile_request_patch_access_range(unsigned long start, unsigned long end) 137 + { 138 + if (end <= start) 139 + return -EINVAL; 140 + 141 + return turnstile_request_patch_access(start, end - start); 142 + } 143 + EXPORT_SYMBOL_GPL(turnstile_request_patch_access_range); 144 + 145 + static int resolve_kernel_symbols(void) 146 + { 147 + unsigned long addr; 148 + 149 + addr = resolve_symbol("_stext"); 150 + if (!addr) { 151 + pr_warn("turnstile: failed to resolve _stext\n"); 152 + return -ENOENT; 153 + } 154 + kernel_text_start = addr; 155 + 156 + addr = resolve_symbol("_etext"); 157 + if (!addr) { 158 + pr_warn("turnstile: failed to resolve _etext\n"); 159 + return -ENOENT; 160 + } 161 + kernel_text_end = addr; 162 + 163 + addr = resolve_symbol("__start_rodata"); 164 + if (!addr) { 165 + pr_warn("turnstile: __start_rodata not found, using _etext\n"); 166 + kernel_rodata_start = kernel_text_end; 167 + } else { 168 + kernel_rodata_start = addr; 169 + } 170 + 171 + addr = resolve_symbol("__end_rodata"); 172 + if (!addr) { 173 + addr = resolve_symbol("__init_begin"); 174 + if (!addr) { 175 + pr_warn("turnstile: failed to resolve rodata end\n"); 176 + kernel_rodata_end = kernel_rodata_start; 177 + } else { 178 + kernel_rodata_end = addr; 179 + } 180 + } else { 181 + kernel_rodata_end = addr; 182 + } 183 + 184 + return 0; 185 + } 186 + 187 + static int __init turnstile_guest_init(void) 188 + { 189 + int ret; 190 + 191 + #ifdef CONFIG_XEN 192 + if (!xen_domain()) { 193 + pr_info("turnstile: not running on xen, module inactive\n"); 194 + turnstile_enabled = false; 195 + return 0; 196 + } 197 + 198 + if (!xen_pv_domain() && !xen_hvm_domain()) { 199 + pr_info("turnstile: unsupported xen domain type\n"); 200 + turnstile_enabled = false; 201 + return 0; 202 + } 203 + #else 204 + pr_info("turnstile: compiled without xen support, module inactive\n"); 205 + turnstile_enabled = false; 206 + return 0; 207 + #endif 208 + 209 + ret = resolve_kernel_symbols(); 210 + if (ret) { 211 + pr_warn("turnstile: symbol resolution failed, protection ranges unknown\n"); 212 + kernel_text_start = 0; 213 + kernel_text_end = 0; 214 + kernel_rodata_start = 0; 215 + kernel_rodata_end = 0; 216 + } 217 + 218 + turnstile_enabled = true; 219 + 220 + pr_info("turnstile: guest module loaded\n"); 221 + pr_info("turnstile: text range: %pK - %pK\n", 222 + (void *)kernel_text_start, (void *)kernel_text_end); 223 + pr_info("turnstile: rodata range: %pK - %pK\n", 224 + (void *)kernel_rodata_start, (void *)kernel_rodata_end); 225 + 226 + return 0; 227 + } 228 + 229 + static void __exit turnstile_guest_exit(void) 230 + { 231 + turnstile_enabled = false; 232 + pr_info("turnstile: guest module unloaded\n"); 233 + } 234 + 235 + module_init(turnstile_guest_init); 236 + module_exit(turnstile_guest_exit);
+617
xen-patch/turnstile.c
··· 1 + #include <xen/sched.h> 2 + #include <xen/domain.h> 3 + #include <xen/guest_access.h> 4 + #include <xen/hypercall.h> 5 + #include <xen/mm.h> 6 + #include <xen/errno.h> 7 + #include <xen/lib.h> 8 + #include <asm/p2m.h> 9 + #include <asm/turnstile.h> 10 + 11 + static struct turnstile_domain_state *get_turnstile_state(struct domain *d) 12 + { 13 + return d->arch.turnstile; 14 + } 15 + 16 + int turnstile_domain_init(struct domain *d) 17 + { 18 + struct turnstile_domain_state *state; 19 + 20 + state = xzalloc(struct turnstile_domain_state); 21 + if ( !state ) 22 + return -ENOMEM; 23 + 24 + rwlock_init(&state->lock); 25 + state->policy = TURNSTILE_POLICY_DISABLED; 26 + state->ring_head = 0; 27 + state->ring_tail = 0; 28 + state->ring_seq = 0; 29 + state->overflow_count = 0; 30 + state->num_ranges = 0; 31 + state->func_entries = NULL; 32 + state->num_func_entries = 0; 33 + state->jump_entries = NULL; 34 + state->num_jump_entries = 0; 35 + state->write_grant.active = false; 36 + 37 + d->arch.turnstile = state; 38 + 39 + return 0; 40 + } 41 + 42 + void turnstile_domain_destroy(struct domain *d) 43 + { 44 + struct turnstile_domain_state *state = get_turnstile_state(d); 45 + 46 + if ( !state ) 47 + return; 48 + 49 + xfree(state->func_entries); 50 + xfree(state->jump_entries); 51 + xfree(state); 52 + d->arch.turnstile = NULL; 53 + } 54 + 55 + static bool gpa_in_range(struct turnstile_domain_state *state, uint64_t gpa) 56 + { 57 + unsigned int i; 58 + 59 + for ( i = 0; i < state->num_ranges; i++ ) 60 + { 61 + if ( state->ranges[i].active && 62 + gpa >= state->ranges[i].gpa_start && 63 + gpa < state->ranges[i].gpa_end ) 64 + return true; 65 + } 66 + return false; 67 + } 68 + 69 + static bool check_write_grant(struct turnstile_domain_state *state, uint64_t gpa, 70 + bool *grant_expired) 71 + { 72 + *grant_expired = false; 73 + 74 + if ( !state->write_grant.active ) 75 + return false; 76 + 77 + if ( NOW() > state->write_grant.expiry ) 78 + { 79 + *grant_expired = true; 80 + return false; 81 + } 82 + 83 + return gpa >= state->write_grant.gpa_start && 84 + gpa < state->write_grant.gpa_end; 85 + } 86 + 87 + static int check_implicit_ftrace(struct turnstile_domain_state *state, 88 + uint64_t gpa, const uint8_t *insn_bytes) 89 + { 90 + unsigned int i; 91 + uint64_t vaddr = gpa + 0xffff800000000000ULL; 92 + 93 + for ( i = 0; i < state->num_func_entries; i++ ) 94 + { 95 + if ( state->func_entries[i] == vaddr ) 96 + { 97 + if ( insn_bytes[0] == 0xe8 ) 98 + return TURNSTILE_IMPLICIT_FTRACE; 99 + 100 + if ( insn_bytes[0] == 0x0f && insn_bytes[1] == 0x1f && 101 + insn_bytes[2] == 0x44 && insn_bytes[3] == 0x00 && 102 + insn_bytes[4] == 0x00 ) 103 + return TURNSTILE_IMPLICIT_FTRACE; 104 + 105 + if ( insn_bytes[0] == 0x90 || 106 + (insn_bytes[0] == 0x66 && insn_bytes[1] == 0x90) ) 107 + return TURNSTILE_IMPLICIT_FTRACE; 108 + } 109 + } 110 + return TURNSTILE_IMPLICIT_DENIED; 111 + } 112 + 113 + static int check_implicit_static_key(struct turnstile_domain_state *state, 114 + uint64_t gpa, const uint8_t *insn_bytes) 115 + { 116 + unsigned int i; 117 + uint64_t vaddr = gpa + 0xffff800000000000ULL; 118 + 119 + for ( i = 0; i < state->num_jump_entries; i++ ) 120 + { 121 + uint64_t code_addr = state->jump_entries[i * 3]; 122 + if ( code_addr == vaddr ) 123 + { 124 + if ( insn_bytes[0] == 0x0f && insn_bytes[1] == 0x1f ) 125 + return TURNSTILE_IMPLICIT_STATIC; 126 + 127 + if ( insn_bytes[0] == 0xe9 ) 128 + return TURNSTILE_IMPLICIT_STATIC; 129 + 130 + if ( insn_bytes[0] == 0xeb ) 131 + return TURNSTILE_IMPLICIT_STATIC; 132 + 133 + if ( insn_bytes[0] == 0x90 ) 134 + return TURNSTILE_IMPLICIT_STATIC; 135 + 136 + if ( insn_bytes[0] == 0x66 && insn_bytes[1] == 0x90 ) 137 + return TURNSTILE_IMPLICIT_STATIC; 138 + } 139 + } 140 + return TURNSTILE_IMPLICIT_DENIED; 141 + } 142 + 143 + static void log_violation(struct turnstile_domain_state *state, 144 + uint64_t gpa, uint64_t rip, uint64_t cr3, 145 + uint32_t access_flags, uint32_t response, 146 + const uint8_t *insn_bytes) 147 + { 148 + struct turnstile_violation *v; 149 + uint32_t head; 150 + uint32_t next_head; 151 + 152 + head = state->ring_head; 153 + next_head = (head + 1) % TURNSTILE_RING_SIZE; 154 + 155 + if ( next_head == state->ring_tail ) 156 + { 157 + state->overflow_count++; 158 + state->stats.ring_overflows++; 159 + state->ring_tail = (state->ring_tail + 1) % TURNSTILE_RING_SIZE; 160 + } 161 + 162 + v = &state->ring[head]; 163 + v->timestamp = NOW(); 164 + v->gpa = gpa; 165 + v->rip = rip; 166 + v->cr3 = cr3; 167 + v->access_flags = access_flags; 168 + v->response = response; 169 + v->seq = state->ring_seq++; 170 + memcpy(v->insn_bytes, insn_bytes, 16); 171 + 172 + state->ring_head = next_head; 173 + state->stats.violations_total++; 174 + 175 + if ( response ) 176 + state->stats.violations_blocked++; 177 + else 178 + state->stats.violations_allowed++; 179 + } 180 + 181 + int turnstile_check_violation(struct domain *d, uint64_t gpa, uint64_t rip, 182 + uint64_t cr3, uint32_t access_flags, 183 + const uint8_t *insn_bytes) 184 + { 185 + struct turnstile_domain_state *state = get_turnstile_state(d); 186 + int implicit; 187 + int block = 0; 188 + bool in_range; 189 + bool grant_valid; 190 + bool grant_expired; 191 + uint32_t policy; 192 + 193 + if ( !state || state->policy == TURNSTILE_POLICY_DISABLED ) 194 + return 0; 195 + 196 + read_lock(&state->lock); 197 + 198 + in_range = gpa_in_range(state, gpa); 199 + if ( !in_range ) 200 + { 201 + read_unlock(&state->lock); 202 + return 0; 203 + } 204 + 205 + grant_valid = check_write_grant(state, gpa, &grant_expired); 206 + if ( grant_valid ) 207 + { 208 + read_unlock(&state->lock); 209 + write_lock(&state->lock); 210 + state->stats.explicit_write_grants++; 211 + write_unlock(&state->lock); 212 + return 0; 213 + } 214 + 215 + implicit = check_implicit_ftrace(state, gpa, insn_bytes); 216 + if ( implicit == TURNSTILE_IMPLICIT_FTRACE ) 217 + { 218 + read_unlock(&state->lock); 219 + write_lock(&state->lock); 220 + state->stats.implicit_ftrace++; 221 + write_unlock(&state->lock); 222 + return 0; 223 + } 224 + 225 + implicit = check_implicit_static_key(state, gpa, insn_bytes); 226 + if ( implicit == TURNSTILE_IMPLICIT_STATIC ) 227 + { 228 + read_unlock(&state->lock); 229 + write_lock(&state->lock); 230 + state->stats.implicit_static_key++; 231 + write_unlock(&state->lock); 232 + return 0; 233 + } 234 + 235 + policy = state->policy; 236 + read_unlock(&state->lock); 237 + 238 + if ( policy == TURNSTILE_POLICY_ENFORCE ) 239 + block = 1; 240 + 241 + write_lock(&state->lock); 242 + if ( grant_expired ) 243 + state->write_grant.active = false; 244 + log_violation(state, gpa, rip, cr3, access_flags, block, insn_bytes); 245 + write_unlock(&state->lock); 246 + 247 + return block; 248 + } 249 + 250 + static long turnstile_set_policy(struct domain *d, 251 + XEN_GUEST_HANDLE_PARAM(void) arg) 252 + { 253 + struct turnstile_domain_state *state = get_turnstile_state(d); 254 + struct turnstile_op_set_policy op; 255 + 256 + if ( !state ) 257 + return -EINVAL; 258 + 259 + if ( copy_from_guest(&op, arg, 1) ) 260 + return -EFAULT; 261 + 262 + if ( op.policy > TURNSTILE_POLICY_ENFORCE ) 263 + return -EINVAL; 264 + 265 + write_lock(&state->lock); 266 + state->policy = op.policy; 267 + write_unlock(&state->lock); 268 + 269 + return 0; 270 + } 271 + 272 + static long turnstile_protect_range(struct domain *d, 273 + XEN_GUEST_HANDLE_PARAM(void) arg) 274 + { 275 + struct turnstile_domain_state *state = get_turnstile_state(d); 276 + struct turnstile_op_protect_range op; 277 + unsigned int i; 278 + int slot = -1; 279 + 280 + if ( !state ) 281 + return -EINVAL; 282 + 283 + if ( copy_from_guest(&op, arg, 1) ) 284 + return -EFAULT; 285 + 286 + if ( op.length == 0 ) 287 + return -EINVAL; 288 + 289 + write_lock(&state->lock); 290 + 291 + for ( i = 0; i < state->num_ranges; i++ ) 292 + { 293 + if ( state->ranges[i].active && 294 + state->ranges[i].gpa_start == op.gpa_start && 295 + state->ranges[i].gpa_end == op.gpa_start + op.length ) 296 + { 297 + write_unlock(&state->lock); 298 + return 0; 299 + } 300 + } 301 + 302 + for ( i = 0; i < TURNSTILE_MAX_RANGES; i++ ) 303 + { 304 + if ( !state->ranges[i].active ) 305 + { 306 + slot = i; 307 + break; 308 + } 309 + } 310 + 311 + if ( slot < 0 ) 312 + { 313 + write_unlock(&state->lock); 314 + return -ENOSPC; 315 + } 316 + 317 + state->ranges[slot].gpa_start = op.gpa_start; 318 + state->ranges[slot].gpa_end = op.gpa_start + op.length; 319 + state->ranges[slot].active = true; 320 + state->ranges[slot].original_type = p2m_ram_rw; 321 + 322 + if ( (unsigned int)slot >= state->num_ranges ) 323 + state->num_ranges = slot + 1; 324 + 325 + write_unlock(&state->lock); 326 + 327 + return 0; 328 + } 329 + 330 + static long turnstile_unprotect_range(struct domain *d, 331 + XEN_GUEST_HANDLE_PARAM(void) arg) 332 + { 333 + struct turnstile_domain_state *state = get_turnstile_state(d); 334 + struct turnstile_op_protect_range op; 335 + unsigned int i; 336 + bool found = false; 337 + 338 + if ( !state ) 339 + return -EINVAL; 340 + 341 + if ( copy_from_guest(&op, arg, 1) ) 342 + return -EFAULT; 343 + 344 + write_lock(&state->lock); 345 + 346 + for ( i = 0; i < state->num_ranges; i++ ) 347 + { 348 + if ( state->ranges[i].active && 349 + state->ranges[i].gpa_start == op.gpa_start ) 350 + { 351 + state->ranges[i].active = false; 352 + found = true; 353 + break; 354 + } 355 + } 356 + 357 + write_unlock(&state->lock); 358 + 359 + return found ? 0 : -ENOENT; 360 + } 361 + 362 + static long turnstile_request_write(struct domain *d, 363 + XEN_GUEST_HANDLE_PARAM(void) arg) 364 + { 365 + struct turnstile_domain_state *state = get_turnstile_state(d); 366 + struct turnstile_op_request_write op; 367 + 368 + if ( !state ) 369 + return -EINVAL; 370 + 371 + if ( copy_from_guest(&op, arg, 1) ) 372 + return -EFAULT; 373 + 374 + if ( op.timeout_ms > 1000 ) 375 + return -EINVAL; 376 + 377 + if ( op.length == 0 ) 378 + return -EINVAL; 379 + 380 + write_lock(&state->lock); 381 + 382 + if ( !gpa_in_range(state, op.gpa_start) ) 383 + { 384 + write_unlock(&state->lock); 385 + return -ENOENT; 386 + } 387 + 388 + state->write_grant.gpa_start = op.gpa_start; 389 + state->write_grant.gpa_end = op.gpa_start + op.length; 390 + state->write_grant.expiry = NOW() + MILLISECS(op.timeout_ms); 391 + state->write_grant.active = true; 392 + 393 + write_unlock(&state->lock); 394 + 395 + return 0; 396 + } 397 + 398 + static long turnstile_get_violations(struct domain *d, 399 + XEN_GUEST_HANDLE_PARAM(void) arg) 400 + { 401 + struct turnstile_domain_state *state = get_turnstile_state(d); 402 + struct turnstile_op_get_violations op; 403 + void *buf_ptr; 404 + uint32_t count = 0; 405 + 406 + if ( !state ) 407 + return -EINVAL; 408 + 409 + if ( copy_from_guest(&op, arg, 1) ) 410 + return -EFAULT; 411 + 412 + if ( op.max_entries == 0 ) 413 + return -EINVAL; 414 + 415 + buf_ptr = (void *)(unsigned long)op.buffer_ptr; 416 + 417 + write_lock(&state->lock); 418 + 419 + while ( state->ring_tail != state->ring_head && count < op.max_entries ) 420 + { 421 + struct turnstile_violation *v = &state->ring[state->ring_tail]; 422 + void *dest = buf_ptr + (count * sizeof(struct turnstile_violation)); 423 + 424 + if ( raw_copy_to_guest(dest, v, sizeof(struct turnstile_violation)) ) 425 + { 426 + write_unlock(&state->lock); 427 + return -EFAULT; 428 + } 429 + 430 + state->ring_tail = (state->ring_tail + 1) % TURNSTILE_RING_SIZE; 431 + count++; 432 + } 433 + 434 + op.entries_returned = count; 435 + op.overflow_count = state->overflow_count; 436 + state->overflow_count = 0; 437 + 438 + write_unlock(&state->lock); 439 + 440 + if ( copy_to_guest(arg, &op, 1) ) 441 + return -EFAULT; 442 + 443 + return 0; 444 + } 445 + 446 + static long turnstile_get_stats(struct domain *d, 447 + XEN_GUEST_HANDLE_PARAM(void) arg) 448 + { 449 + struct turnstile_domain_state *state = get_turnstile_state(d); 450 + struct turnstile_stats stats; 451 + 452 + if ( !state ) 453 + return -EINVAL; 454 + 455 + read_lock(&state->lock); 456 + stats = state->stats; 457 + read_unlock(&state->lock); 458 + 459 + if ( copy_to_guest(arg, &stats, 1) ) 460 + return -EFAULT; 461 + 462 + return 0; 463 + } 464 + 465 + static long turnstile_upload_metadata(struct domain *d, 466 + XEN_GUEST_HANDLE_PARAM(void) arg) 467 + { 468 + struct turnstile_domain_state *state = get_turnstile_state(d); 469 + struct turnstile_op_upload_metadata op; 470 + void *buf_ptr; 471 + uint64_t *new_entries; 472 + unsigned int alloc_count; 473 + 474 + if ( !state ) 475 + return -EINVAL; 476 + 477 + if ( copy_from_guest(&op, arg, 1) ) 478 + return -EFAULT; 479 + 480 + if ( op.count == 0 ) 481 + return -EINVAL; 482 + 483 + if ( op.metadata_type == TURNSTILE_META_FUNC_ENTRIES ) 484 + { 485 + if ( op.count > TURNSTILE_MAX_FUNC_ENTRIES ) 486 + return -EINVAL; 487 + 488 + alloc_count = op.count; 489 + } 490 + else if ( op.metadata_type == TURNSTILE_META_JUMP_ENTRIES ) 491 + { 492 + if ( op.count > TURNSTILE_MAX_JUMP_ENTRIES ) 493 + return -EINVAL; 494 + 495 + alloc_count = op.count * 3; 496 + } 497 + else 498 + { 499 + return -EINVAL; 500 + } 501 + 502 + new_entries = xmalloc_array(uint64_t, alloc_count); 503 + if ( !new_entries ) 504 + return -ENOMEM; 505 + 506 + buf_ptr = (void *)(unsigned long)op.buffer_ptr; 507 + 508 + if ( raw_copy_from_guest(new_entries, buf_ptr, alloc_count * sizeof(uint64_t)) ) 509 + { 510 + xfree(new_entries); 511 + return -EFAULT; 512 + } 513 + 514 + write_lock(&state->lock); 515 + 516 + if ( op.metadata_type == TURNSTILE_META_FUNC_ENTRIES ) 517 + { 518 + xfree(state->func_entries); 519 + state->func_entries = new_entries; 520 + state->num_func_entries = op.count; 521 + } 522 + else 523 + { 524 + xfree(state->jump_entries); 525 + state->jump_entries = new_entries; 526 + state->num_jump_entries = op.count; 527 + } 528 + 529 + write_unlock(&state->lock); 530 + 531 + return 0; 532 + } 533 + 534 + long do_turnstile_op(unsigned int op, domid_t domid, XEN_GUEST_HANDLE_PARAM(void) arg) 535 + { 536 + struct domain *d; 537 + long ret; 538 + bool self = false; 539 + 540 + if ( domid == DOMID_SELF ) 541 + { 542 + if ( op != TURNSTILE_OP_REQUEST_WRITE ) 543 + return -EPERM; 544 + 545 + d = current->domain; 546 + get_knownalive_domain(d); 547 + self = true; 548 + } 549 + else 550 + { 551 + if ( !is_control_domain(current->domain) ) 552 + return -EPERM; 553 + 554 + d = get_domain_by_id(domid); 555 + if ( !d ) 556 + return -ESRCH; 557 + } 558 + 559 + if ( !is_hvm_domain(d) ) 560 + { 561 + put_domain(d); 562 + return -EINVAL; 563 + } 564 + 565 + if ( !get_turnstile_state(d) ) 566 + { 567 + if ( self ) 568 + { 569 + put_domain(d); 570 + return -EINVAL; 571 + } 572 + 573 + ret = turnstile_domain_init(d); 574 + if ( ret ) 575 + { 576 + put_domain(d); 577 + return ret; 578 + } 579 + } 580 + 581 + switch ( op ) 582 + { 583 + case TURNSTILE_OP_SET_POLICY: 584 + ret = turnstile_set_policy(d, arg); 585 + break; 586 + 587 + case TURNSTILE_OP_PROTECT_RANGE: 588 + ret = turnstile_protect_range(d, arg); 589 + break; 590 + 591 + case TURNSTILE_OP_UNPROTECT_RANGE: 592 + ret = turnstile_unprotect_range(d, arg); 593 + break; 594 + 595 + case TURNSTILE_OP_REQUEST_WRITE: 596 + ret = turnstile_request_write(d, arg); 597 + break; 598 + 599 + case TURNSTILE_OP_GET_VIOLATIONS: 600 + ret = turnstile_get_violations(d, arg); 601 + break; 602 + 603 + case TURNSTILE_OP_GET_STATS: 604 + ret = turnstile_get_stats(d, arg); 605 + break; 606 + 607 + case TURNSTILE_OP_UPLOAD_METADATA: 608 + ret = turnstile_upload_metadata(d, arg); 609 + break; 610 + 611 + default: 612 + ret = -ENOSYS; 613 + } 614 + 615 + put_domain(d); 616 + return ret; 617 + }
+138
xen-patch/turnstile.h
··· 1 + #ifndef XEN_TURNSTILE_H 2 + #define XEN_TURNSTILE_H 3 + 4 + #ifdef __XEN__ 5 + #include <xen/types.h> 6 + #else 7 + #include <stdint.h> 8 + #endif 9 + 10 + #define TURNSTILE_OP_SET_POLICY 0 11 + #define TURNSTILE_OP_PROTECT_RANGE 1 12 + #define TURNSTILE_OP_UNPROTECT_RANGE 2 13 + #define TURNSTILE_OP_REQUEST_WRITE 3 14 + #define TURNSTILE_OP_GET_VIOLATIONS 4 15 + #define TURNSTILE_OP_GET_STATS 5 16 + #define TURNSTILE_OP_UPLOAD_METADATA 6 17 + 18 + #define TURNSTILE_POLICY_DISABLED 0 19 + #define TURNSTILE_POLICY_AUDIT 1 20 + #define TURNSTILE_POLICY_ENFORCE 2 21 + 22 + #define TURNSTILE_MAX_RANGES 256 23 + #define TURNSTILE_RING_SIZE 4096 24 + #define TURNSTILE_MAX_FUNC_ENTRIES 65536 25 + #define TURNSTILE_MAX_JUMP_ENTRIES 32768 26 + 27 + #define TURNSTILE_IMPLICIT_DENIED 0 28 + #define TURNSTILE_IMPLICIT_FTRACE 1 29 + #define TURNSTILE_IMPLICIT_STATIC 2 30 + 31 + struct turnstile_violation { 32 + uint64_t timestamp; 33 + uint64_t gpa; 34 + uint64_t rip; 35 + uint64_t cr3; 36 + uint32_t access_flags; 37 + uint32_t response; 38 + uint8_t insn_bytes[16]; 39 + uint32_t seq; 40 + uint32_t _pad; 41 + }; 42 + 43 + struct turnstile_stats { 44 + uint64_t violations_total; 45 + uint64_t violations_blocked; 46 + uint64_t violations_allowed; 47 + uint64_t implicit_ftrace; 48 + uint64_t implicit_static_key; 49 + uint64_t explicit_write_grants; 50 + uint64_t ring_overflows; 51 + }; 52 + 53 + struct turnstile_op_set_policy { 54 + uint32_t policy; 55 + uint32_t _pad; 56 + }; 57 + 58 + struct turnstile_op_protect_range { 59 + uint64_t gpa_start; 60 + uint64_t length; 61 + }; 62 + 63 + struct turnstile_op_request_write { 64 + uint64_t gpa_start; 65 + uint64_t length; 66 + uint32_t timeout_ms; 67 + uint32_t _pad; 68 + }; 69 + 70 + struct turnstile_op_get_violations { 71 + uint64_t buffer_ptr; 72 + uint32_t max_entries; 73 + uint32_t entries_returned; 74 + uint32_t overflow_count; 75 + uint32_t _pad; 76 + }; 77 + 78 + struct turnstile_op_upload_metadata { 79 + uint64_t buffer_ptr; 80 + uint32_t metadata_type; 81 + uint32_t count; 82 + }; 83 + 84 + #define TURNSTILE_META_FUNC_ENTRIES 0 85 + #define TURNSTILE_META_JUMP_ENTRIES 1 86 + 87 + #ifdef __XEN__ 88 + #include <xen/sched.h> 89 + #include <xen/spinlock.h> 90 + #include <xen/time.h> 91 + #include <asm/p2m.h> 92 + 93 + struct turnstile_range { 94 + uint64_t gpa_start; 95 + uint64_t gpa_end; 96 + p2m_type_t original_type; 97 + bool active; 98 + }; 99 + 100 + struct turnstile_write_grant { 101 + uint64_t gpa_start; 102 + uint64_t gpa_end; 103 + s_time_t expiry; 104 + bool active; 105 + }; 106 + 107 + struct turnstile_domain_state { 108 + rwlock_t lock; 109 + uint32_t policy; 110 + 111 + struct turnstile_range ranges[TURNSTILE_MAX_RANGES]; 112 + unsigned int num_ranges; 113 + 114 + struct turnstile_violation ring[TURNSTILE_RING_SIZE]; 115 + uint32_t ring_head; 116 + uint32_t ring_tail; 117 + uint32_t ring_seq; 118 + uint32_t overflow_count; 119 + 120 + uint64_t *func_entries; 121 + unsigned int num_func_entries; 122 + 123 + uint64_t *jump_entries; 124 + unsigned int num_jump_entries; 125 + 126 + struct turnstile_write_grant write_grant; 127 + struct turnstile_stats stats; 128 + }; 129 + 130 + int turnstile_domain_init(struct domain *d); 131 + void turnstile_domain_destroy(struct domain *d); 132 + long do_turnstile_op(unsigned int op, domid_t domid, XEN_GUEST_HANDLE_PARAM(void) arg); 133 + int turnstile_check_violation(struct domain *d, uint64_t gpa, uint64_t rip, 134 + uint64_t cr3, uint32_t access_flags, 135 + const uint8_t *insn_bytes); 136 + #endif 137 + 138 + #endif