Repository navigation
Expand file tree
/
Copy pathCMakeLists.txt
More file actions
267 lines (248 loc) · 14.2 KB
/
Copy pathCMakeLists.txt
File metadata and controls
267 lines (248 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
cmake_minimum_required(VERSION 3.22)
project(teesim LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Weak-link NDK symbols newer than the minimum SDK so the code builds against a
# low min-SDK; the symbols we use are present on the devices we target.
add_compile_definitions(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__)
# External inputs. libbinder_ndk and liblog come from the NDK sysroot; BoringSSL is
# resolved at runtime inside keystore2, so only its headers (for the Rust bindings)
# are needed at build time. The build depends on no device, so any ABI cross-builds.
set(BORINGSSL_DIR "${CMAKE_SOURCE_DIR}/third_party/boringssl" CACHE PATH "BoringSSL source directory containing include/")
# Native API floor for the Rust static lib (cargo ndk --platform). Track the same min-SDK
# AGP compiles the C++ at (ANDROID_PLATFORM, e.g. android-29) so both halves share one floor.
if(ANDROID_PLATFORM MATCHES "android-([0-9]+)")
set(ANDROID_API_LEVEL "${CMAKE_MATCH_1}")
else()
set(ANDROID_API_LEVEL "29")
endif()
# --- LSPlt: memory-map scanning and PLT/GOT hooking -------------------------
set(LSPLT_JNI ${CMAKE_SOURCE_DIR}/third_party/lsplt/lsplt/src/main/jni)
add_library(lsplt STATIC ${LSPLT_JNI}/lsplt.cc ${LSPLT_JNI}/elf_util.cc)
target_include_directories(lsplt PUBLIC ${LSPLT_JNI}/include)
target_include_directories(lsplt PRIVATE ${LSPLT_JNI})
# --- The injector -----------------------------------------------------------
add_executable(inject injector/main.cpp injector/utils.cpp common/log_context.cpp)
target_include_directories(inject PRIVATE injector/include)
target_link_libraries(inject PRIVATE lsplt log)
target_link_options(inject PRIVATE -static-libstdc++)
# --- The WebUI's admin-socket client ----------------------------------------
# The daemon serves its admin endpoint on a root-only filesystem unix socket (see KeyAdmin.kt); a
# WebView cannot fetch() a unix socket, so the WebUI reaches it through its root-exec bridge and this
# tiny HTTP-over-UDS client. Plain C, no dependencies — it just pumps one request/response.
add_executable(teesim-uds injector/teesim_uds.c)
# --- The daemon's in-process log reader -------------------------------------
# Loaded (System.load) into the app_process daemon by LogTail. Reads logd directly through liblog's
# reader API instead of spawning a `logcat` child (which carried the whole device log stream through a
# pipe and let a flood amplify — see #265): it filters in C to the lines we keep, serves them to the
# WebUI from a bounded ring, and mirrors them to rotating files under /data/adb/teesim/log. The liblog
# reader symbols are absent from the NDK stub, so they stay undefined here (-Wl,-z,undefs) and resolve
# at runtime against the device's real liblog.so.
add_library(teesim_logcat SHARED logcat/logcat.cpp)
target_include_directories(teesim_logcat PRIVATE logcat)
target_link_libraries(teesim_logcat PRIVATE log)
target_link_options(teesim_logcat PRIVATE
-static-libstdc++
-Wl,-z,undefs
-Wl,--version-script=${CMAKE_SOURCE_DIR}/logcat/exports.map
-Wl,--gc-sections)
# --- KeyMint AIDL, generated with the NDK backend ---------------------------
# fromBinder's local-binder fallback (checked below) only exists in aidl from build-tools >= 35, so
# prefer the newest installed build-tools over whatever aidl happens to be first on PATH or first in
# the (alphabetically sorted) glob -- e.g. a leftover 34.0.0 sorts before 36.0.0 and silently wins.
# AIDL_BIN is a plain variable, not a find_program cache entry, so it is recomputed on every configure
# and never sticks to a stale detection; an explicit -DAIDL_BIN=... on the command line still wins.
if(NOT AIDL_BIN AND DEFINED ENV{ANDROID_HOME})
file(GLOB _bt $ENV{ANDROID_HOME}/build-tools/*)
list(SORT _bt COMPARE NATURAL ORDER DESCENDING)
foreach(_d ${_bt})
if(EXISTS ${_d}/aidl)
set(AIDL_BIN ${_d}/aidl)
break()
endif()
endforeach()
endif()
if(NOT AIDL_BIN)
find_program(_aidl_on_path aidl)
set(AIDL_BIN ${_aidl_on_path})
endif()
if(NOT AIDL_BIN)
message(FATAL_ERROR "aidl not found; set -DAIDL_BIN=/path/to/aidl")
endif()
set(SEC ${CMAKE_SOURCE_DIR}/third_party/interfaces/security)
set(KM_AIDL ${SEC}/keymint/aidl)
set(SC_AIDL ${SEC}/secureclock/aidl)
set(SS_AIDL ${SEC}/sharedsecret/aidl)
set(AIDL_GEN ${CMAKE_BINARY_DIR}/aidl-gen)
if(NOT EXISTS ${AIDL_GEN}/.done)
file(MAKE_DIRECTORY ${AIDL_GEN}/src ${AIDL_GEN}/include)
foreach(pkg keymint secureclock sharedsecret)
file(GLOB _aidls ${SEC}/${pkg}/aidl/android/hardware/security/${pkg}/*.aidl)
execute_process(
COMMAND ${AIDL_BIN} --lang=ndk --structured --stability=vintf --min_sdk_version=29
-I ${KM_AIDL} -I ${SC_AIDL} -I ${SS_AIDL}
-o ${AIDL_GEN}/src -h ${AIDL_GEN}/include ${_aidls}
RESULT_VARIABLE _rc)
if(NOT _rc EQUAL 0)
message(FATAL_ERROR "aidl generation failed for ${pkg}")
endif()
endforeach()
# The NDK AIDL's fromBinder already handles a LOCAL binder whose AIBinder_Class differs from our
# statically-linked copy (a legacy km_compat IKeyMintDevice / IKeyMintOperation): when
# AIBinder_associateClass fails it falls back to a descriptor match and builds a Bp, but only under
# #if __ANDROID_API__ >= 31. keymint_aidl is compiled at API 31 below to enable that branch; require
# the generated code to actually contain it so an aidl too old to emit the fallback fails the build
# here rather than silently returning null on legacy devices.
if(NOT EXISTS ${AIDL_GEN}/src/android/hardware/security/keymint/IKeyMintDevice.cpp)
message(FATAL_ERROR "keymint: aidl did not generate IKeyMintDevice.cpp")
endif()
file(READ ${AIDL_GEN}/src/android/hardware/security/keymint/IKeyMintDevice.cpp _dev_cpp)
if(NOT _dev_cpp MATCHES "AIBinder_Class_getDescriptor")
message(FATAL_ERROR "keymint: generated fromBinder lacks the local-binder fallback; "
"build with build-tools >= 35 (36 recommended)")
endif()
file(TOUCH ${AIDL_GEN}/.done)
endif()
set(FN ${CMAKE_SOURCE_DIR}/third_party/frameworks-native/libs/binder/ndk)
file(GLOB_RECURSE AIDL_SRCS ${AIDL_GEN}/src/*.cpp)
add_library(keymint_aidl STATIC ${AIDL_SRCS})
target_include_directories(keymint_aidl PUBLIC
${AIDL_GEN}/include ${FN}/include_cpp ${FN}/include_ndk ${FN}/include_platform)
# keymint_aidl links only into teesim_keymint.so, which is injected into keystore2 (API 31+ only), so
# compile it at API 31 to enable the AIDL's own local-binder fallback in fromBinder (gated
# #if __ANDROID_API__ >= 31). The AIBinder_Class_getDescriptor it then calls is left undefined here and
# resolved at runtime against keystore2's libbinder_ndk (teesim_keymint links with -Wl,-z,undefs). The
# rest of the module keeps its lower min-SDK floor; only this static lib is raised. The NDK toolchain's
# compiler target ends in the API level, so bump that suffix to 31.
if(CMAKE_CXX_COMPILER_TARGET MATCHES "[0-9]+$")
string(REGEX REPLACE "[0-9]+$" "31" _km_target "${CMAKE_CXX_COMPILER_TARGET}")
target_compile_options(keymint_aidl PRIVATE "--target=${_km_target}")
else()
message(FATAL_ERROR "keymint: cannot derive an API-31 target from '${CMAKE_CXX_COMPILER_TARGET}'")
endif()
# --- The in-process TA (Rust static library) --------------------------------
if(ANDROID_ABI STREQUAL "arm64-v8a")
set(RUST_TRIPLE aarch64-linux-android)
elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
set(RUST_TRIPLE armv7-linux-androideabi)
elseif(ANDROID_ABI STREQUAL "x86_64")
set(RUST_TRIPLE x86_64-linux-android)
elseif(ANDROID_ABI STREQUAL "x86")
set(RUST_TRIPLE i686-linux-android)
endif()
set(RUST_A ${CMAKE_SOURCE_DIR}/rust/target/${RUST_TRIPLE}/release/libteesim_km.a)
# ALWAYS invoke build.sh and let cargo decide incrementally what to rebuild. An OUTPUT custom command
# with no source DEPENDS only runs when the .a is missing, so once built it was never refreshed on a
# Rust source change — that once linked a stale 14-argument teesim_km_init_ex against a 16-argument C++
# caller (ids read from the wrong stack slot → SIGSEGV, keystore2 crash loop). cargo leaves the .a's
# mtime untouched when nothing changed, so this does not needlessly relink the interceptor.
add_custom_target(rust_ta
BYPRODUCTS ${RUST_A}
COMMAND ${CMAKE_COMMAND} -E env
ABI=${ANDROID_ABI} API=${ANDROID_API_LEVEL} NDK_HOME=${ANDROID_NDK}
BORINGSSL=${BORINGSSL_DIR}
bash ${CMAKE_SOURCE_DIR}/rust/build.sh
VERBATIM USES_TERMINAL)
# --- How the TAs reach BoringSSL --------------------------------------------
# The two interceptors resolve BoringSSL differently, and a single configure now
# builds both:
# * keymint (keystore2, Android 12+) links a stub libcrypto.so — empty functions
# carrying the right SONAME — and lets the host process's real libcrypto provide
# the symbols at runtime. exports.map hides everything but `entry`, so the stub
# never leaks.
# * keystore (Android 10/11) bundles a modern static BoringSSL, because the host's
# libcrypto is too old. That static `crypto` target is built internally below via
# add_subdirectory, inheriting this build's Android toolchain.
# CRYPTO_STATIC_LIB stays as an optional override for a pre-built libcrypto.a.
set(CRYPTO_STUB_C ${CMAKE_BINARY_DIR}/libcrypto_stub.c)
file(STRINGS ${CMAKE_SOURCE_DIR}/keymint/libcrypto.syms _crypto_syms)
set(_stub_body "")
foreach(_s ${_crypto_syms})
string(APPEND _stub_body "void ${_s}(void){}\n")
endforeach()
file(WRITE ${CRYPTO_STUB_C} "${_stub_body}")
add_library(crypto_stub SHARED ${CRYPTO_STUB_C})
set_target_properties(crypto_stub PROPERTIES OUTPUT_NAME crypto)
target_link_options(crypto_stub PRIVATE -nostdlib -Wl,-soname,libcrypto.so)
set(CRYPTO_LINK crypto_stub)
# The static BoringSSL the keystore interceptor bundles. Only the `crypto` target is
# referenced, so BoringSSL's tools and tests never build (Go stays optional). A
# caller may instead point CRYPTO_STATIC_LIB at a pre-built libcrypto.a.
set(CRYPTO_STATIC_LIB "" CACHE FILEPATH "Pre-built static libcrypto.a to bundle; empty = build BoringSSL internally")
if(CRYPTO_STATIC_LIB)
set(KEYSTORE_CRYPTO ${CRYPTO_STATIC_LIB})
else()
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(${BORINGSSL_DIR} ${CMAKE_BINARY_DIR}/boringssl EXCLUDE_FROM_ALL)
set(KEYSTORE_CRYPTO crypto)
endif()
# --- The injected interceptor library ---------------------------------------
add_library(teesim_keymint SHARED
keymint/keymint_router.cpp keymint/keymint_hook.cpp keymint/keymint_entry.cpp
keymint/crypto_compat.c
common/control.cpp common/json.cpp common/log_context.cpp common/km_names.cpp
common/redact.cpp)
add_dependencies(teesim_keymint rust_ta)
target_include_directories(teesim_keymint PRIVATE
keymint common injector/include rust/teesim-km/include ${LSPLT_JNI}/include)
# libbinder_ndk and liblog come from the NDK sysroot; libcrypto is either the
# runtime stub (shadowed by the host's copy) or a bundled static BoringSSL.
# --whole-archive on the Rust archive forces compiler_builtins' emutls.o in: the NDK's
# static libc++abi (cxa_thread_atexit.cpp.o) calls __emutls_get_address at runtime, and
# emutls.o carries the only definition. Without --whole-archive the weak definition is
# not reliably extracted, leaving an undefined dynamic import that bionic cannot resolve
# when the .so is dlopen'd into a C++ process that has no emutls runtime of its own
# (legacy /system/bin/keystore on Android 10/11). --gc-sections still drops the dead
# code, so only the reachable emutls functions survive in the output.
target_link_libraries(teesim_keymint PRIVATE keymint_aidl lsplt
-Wl,--whole-archive ${RUST_A} -Wl,--no-whole-archive
${CRYPTO_LINK} binder_ndk log)
target_link_options(teesim_keymint PRIVATE
-static-libstdc++
# Binder/log calls newer than the min-SDK are absent from the NDK stub; let them
# stay undefined here and resolve at runtime against keystore2's own libraries.
-Wl,-z,undefs
-Wl,--version-script=${CMAKE_SOURCE_DIR}/keymint/exports.map
-Wl,--gc-sections)
# --- Legacy keystore interceptor (Android 10/11) ----------------------------
# Apps reach the keystore daemon over the C++ libbinder, which is not in the NDK.
# Link stub libbinder/libutils (empty definitions carrying the right C++ ABI and
# SONAMEs) so the device's real libraries provide the implementations at runtime.
# The TA's crypto is bundled here (KEYSTORE_CRYPTO, a static BoringSSL) rather than
# resolved from the host's older libcrypto.
# The platform libbinder exports BBinder's vtable but not its RTTI typeinfo, so
# anything deriving from BBinder must be built without RTTI (and, to match, without
# exceptions) or it fails to load.
set(AOSP ${CMAKE_SOURCE_DIR}/keystore/aosp)
set(BINDER_CXX_FLAGS -fno-rtti -fno-exceptions)
add_library(utils_stub SHARED ${AOSP}/stub_utils.cpp)
set_target_properties(utils_stub PROPERTIES OUTPUT_NAME utils)
target_include_directories(utils_stub PRIVATE ${AOSP}/include)
target_compile_definitions(utils_stub PRIVATE BINDER_DISABLE_NATIVE_HANDLE)
target_compile_options(utils_stub PRIVATE ${BINDER_CXX_FLAGS})
add_library(binder_stub SHARED ${AOSP}/stub_binder.cpp)
set_target_properties(binder_stub PROPERTIES OUTPUT_NAME binder)
target_include_directories(binder_stub PRIVATE ${AOSP}/include)
target_compile_definitions(binder_stub PRIVATE BINDER_DISABLE_NATIVE_HANDLE)
target_compile_options(binder_stub PRIVATE ${BINDER_CXX_FLAGS})
target_link_libraries(binder_stub PRIVATE utils_stub)
add_library(teesim_keystore SHARED
keystore/binder_interceptor.cpp keystore/keystore_router.cpp keystore/keystore_entry.cpp
common/control.cpp common/json.cpp common/log_context.cpp common/km_names.cpp
common/redact.cpp)
add_dependencies(teesim_keystore rust_ta)
target_include_directories(teesim_keystore PRIVATE
keystore common ${AOSP}/include injector/include ${LSPLT_JNI}/include
rust/teesim-km/include ${BORINGSSL_DIR}/include)
target_compile_definitions(teesim_keystore PRIVATE BINDER_DISABLE_NATIVE_HANDLE)
target_compile_options(teesim_keystore PRIVATE ${BINDER_CXX_FLAGS})
target_link_libraries(teesim_keystore PRIVATE
binder_stub utils_stub lsplt log
-Wl,--whole-archive ${RUST_A} -Wl,--no-whole-archive # see the comment on teesim_keymint
${KEYSTORE_CRYPTO})
target_link_options(teesim_keystore PRIVATE
-static-libstdc++
-Wl,-z,undefs
-Wl,--version-script=${CMAKE_SOURCE_DIR}/keymint/exports.map
-Wl,--gc-sections)