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