linked.sh (12572B)
1 #!/bin/sh 2 # Linked-image strip acceptance: loader metadata is an exact oracle, executable 3 # modes survive transactional -o rewrites, native images remain runnable, and 4 # signed Mach-O inputs require an explicit signature-removal opt-in. 5 6 set -u 7 8 script_dir=$(cd "$(dirname "$0")" && pwd) 9 repo_root=$(cd "$script_dir/../.." && pwd) 10 KIT_KIT_DIR="$repo_root/test/lib" 11 . "$repo_root/test/lib/kit_sh_kit.sh" 12 13 KIT=${KIT:-"$repo_root/build/kit"} 14 export KIT 15 kit_require_kit strip-linked 16 17 kit_workdir strip-linked 18 work=$KIT_WORK 19 kit_report_init 20 21 loader_dump() { # INPUT OUTPUT 22 "$KIT" objdump -p -R "$1" 2> "$2.err" | 23 awk '/^ __DWARF[[:space:]]/ { getline; next } { print }' | 24 sed -e 's|^.*:\t\(.*private headers\)$|<image>:\t\1|' \ 25 -e 's|^.*:\t\(file format.*\)$|<image>:\t\1|' > "$2" 26 } 27 28 dynamic_dump() { # INPUT OUTPUT 29 "$KIT" objdump -T "$1" 2> "$2.err" | 30 sed -e 's|^.*:\t\(file format.*\)$|<image>:\t\1|' > "$2" 31 } 32 33 assert_no_debug() { # NAME INPUT 34 and_name=$1 35 and_input=$2 36 "$KIT" objdump -h "$and_input" > "$work/$and_name.sections" 2>&1 37 if grep -E '(^|[, ])(__DWARF|\.debug_|\.zdebug_)' \ 38 "$work/$and_name.sections" >/dev/null 2>&1; then 39 not_ok "$and_name" "$work/$and_name.sections" 40 else 41 ok "$and_name" 42 fi 43 } 44 45 strip_shape() { # NAME INPUT [REMOVE_SIGNATURE] [PRESERVE_ALL_NLIST] 46 ss_name=$1 47 ss_input=$2 48 ss_signature=${3:-0} 49 ss_preserve_all_nlist=${4:-1} 50 ss_sigflag= 51 [ "$ss_signature" -eq 0 ] || ss_sigflag=--remove-signature 52 53 chmod 0751 "$ss_input" 54 loader_dump "$ss_input" "$work/$ss_name.before" 55 dynamic_dump "$ss_input" "$work/$ss_name.before.dynamic" 56 57 # shellcheck disable=SC2086 -- optional single flag is intentional. 58 run_ok "$ss_name-strip-debug" "$KIT" strip --strip-debug $ss_sigflag \ 59 -o "$work/$ss_name.debug" "$ss_input" 60 check_mode "$ss_name-debug-mode" "$work/$ss_name.debug" 751 61 assert_no_debug "$ss_name-debug-sections" "$work/$ss_name.debug" 62 loader_dump "$work/$ss_name.debug" "$work/$ss_name.debug.loader" 63 same_file "$ss_name-debug-loader" "$work/$ss_name.before" \ 64 "$work/$ss_name.debug.loader" 65 dynamic_dump "$work/$ss_name.debug" "$work/$ss_name.debug.dynamic" 66 same_file "$ss_name-debug-dynamic" "$work/$ss_name.before.dynamic" \ 67 "$work/$ss_name.debug.dynamic" 68 69 # shellcheck disable=SC2086 -- optional single flag is intentional. 70 run_ok "$ss_name-strip-all" "$KIT" strip --strip-all $ss_sigflag \ 71 -o "$work/$ss_name.all" "$ss_input" 72 check_mode "$ss_name-all-mode" "$work/$ss_name.all" 751 73 assert_no_debug "$ss_name-all-sections" "$work/$ss_name.all" 74 loader_dump "$work/$ss_name.all" "$work/$ss_name.all.loader" 75 same_file "$ss_name-all-loader" "$work/$ss_name.before" \ 76 "$work/$ss_name.all.loader" 77 if [ "$ss_preserve_all_nlist" -ne 0 ]; then 78 dynamic_dump "$work/$ss_name.all" "$work/$ss_name.all.dynamic" 79 same_file "$ss_name-all-dynamic" "$work/$ss_name.before.dynamic" \ 80 "$work/$ss_name.all.dynamic" 81 fi 82 } 83 84 # Cross-format, cross-architecture real linked images. These do not need a 85 # hosted sysroot and therefore run on every build host. 86 cat > "$work/x64-start.c" <<'EOF' 87 __attribute__((noreturn)) void _start(void) { 88 register long nr __asm__("rax") = 231; 89 register long code __asm__("rdi") = 42; 90 __asm__ volatile("syscall" : : "r"(nr), "r"(code) : "memory"); 91 __builtin_unreachable(); 92 } 93 EOF 94 95 run_ok elf-static-build "$KIT" cc -target x86_64-linux-gnu -g \ 96 -ffreestanding -fno-stack-protector -fno-PIC -fno-pie -nostdlib -static \ 97 -Wl,-e,_start "$work/x64-start.c" -o "$work/elf-static" 98 if [ -f "$work/elf-static" ]; then 99 strip_shape elf-static "$work/elf-static" 100 fi 101 102 run_ok elf-pie-build "$KIT" cc -target x86_64-linux-gnu -g \ 103 -ffreestanding -fno-stack-protector -fPIE -pie -nostdlib \ 104 -Wl,-e,_start "$work/x64-start.c" -o "$work/elf-pie" 105 if [ -f "$work/elf-pie" ]; then 106 strip_shape elf-pie "$work/elf-pie" 107 fi 108 109 cat > "$work/elf-shared.c" <<'EOF' 110 extern int imported_value; 111 _Thread_local int tls_value = 7; 112 int exported_value(void) { return imported_value + tls_value; } 113 EOF 114 run_ok elf-shared-build "$KIT" cc -target x86_64-linux-gnu -g \ 115 -ffreestanding -fno-stack-protector -fPIC -shared -nostdlib \ 116 "$work/elf-shared.c" -o "$work/elf-shared.so" 117 if [ -f "$work/elf-shared.so" ]; then 118 strip_shape elf-shared "$work/elf-shared.so" 119 fi 120 121 cat > "$work/pe-start.c" <<'EOF' 122 void _start(void) { for (;;) {} } 123 int exported_value(void) { return 7; } 124 EOF 125 for pe_arch in x86_64 aarch64; do 126 run_ok "pe-$pe_arch-build" "$KIT" cc -target "$pe_arch-windows" -g \ 127 -ffreestanding -fno-stack-protector -nostdlib -Wl,-e,_start \ 128 "$work/pe-start.c" -o "$work/pe-$pe_arch.exe" 129 if [ -f "$work/pe-$pe_arch.exe" ]; then 130 strip_shape "pe-$pe_arch" "$work/pe-$pe_arch.exe" 131 fi 132 done 133 134 host_os=$(uname -s 2>/dev/null || printf unknown) 135 host_arch=$(uname -m 2>/dev/null || printf unknown) 136 137 if [ "$host_os" = Darwin ]; then 138 # Kit's native output is a dynamically linked PIE carrying an ad-hoc code 139 # signature. Default failure must leave both input and a pre-existing -o 140 # destination byte-for-byte unchanged. 141 printf 'int helper(void) { return 0; } int main(void) { return helper(); }\n' \ 142 > "$work/native-app.c" 143 run_ok macho-native-build "$KIT" cc -g "$work/native-app.c" \ 144 -o "$work/macho-native" 145 if [ -f "$work/macho-native" ]; then 146 run_ok macho-native-run-before "$work/macho-native" 147 cp "$work/macho-native" "$work/macho-native.input-snapshot" 148 printf 'preexisting output\n' > "$work/macho-signed-destination" 149 cp "$work/macho-signed-destination" "$work/macho-signed-destination.want" 150 "$KIT" strip --strip-debug -o "$work/macho-signed-destination" \ 151 "$work/macho-native" > "$work/macho-signed-default.out" \ 152 2> "$work/macho-signed-default.err" 153 macho_signed_rc=$? 154 if [ "$macho_signed_rc" -eq 1 ]; then 155 ok macho-signed-default-status 156 else 157 printf 'wanted 1, got %s\n' "$macho_signed_rc" \ 158 > "$work/macho-signed-default.diag" 159 not_ok macho-signed-default-status "$work/macho-signed-default.diag" 160 fi 161 contains macho-signed-default-guidance "$work/macho-signed-default.err" \ 162 "remove-signature" 163 same_file macho-signed-input-unchanged \ 164 "$work/macho-native.input-snapshot" "$work/macho-native" 165 same_file macho-signed-output-unchanged \ 166 "$work/macho-signed-destination.want" \ 167 "$work/macho-signed-destination" 168 169 # MH_EXECUTE's ordinary external nlist is removable when its export trie 170 # owns loader visibility; the dylib case below requires an exact dynamic 171 # symbol view as well. 172 strip_shape macho-native "$work/macho-native" 1 0 173 run_ok macho-native-debug-resign codesign -f -s - \ 174 "$work/macho-native.debug" 175 run_ok macho-native-all-resign codesign -f -s - \ 176 "$work/macho-native.all" 177 run_ok macho-native-debug-run "$work/macho-native.debug" 178 run_ok macho-native-all-run "$work/macho-native.all" 179 fi 180 181 # A platform-linked dylib supplies an independent producer/oracle and proves 182 # exports, imports, unwind data, and install-name metadata remain usable. 183 sdk=$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || printf '') 184 if [ -n "$sdk" ] && command -v clang >/dev/null 2>&1; then 185 printf 'int linked_answer(void) { return 42; }\n' > "$work/dylib.c" 186 printf 'int linked_answer(void); int main(void) { return linked_answer() == 42 ? 0 : 1; }\n' \ 187 > "$work/dylib-main.c" 188 run_ok macho-dylib-build env SDKROOT="$sdk" clang -isysroot "$sdk" -g \ 189 -dynamiclib "$work/dylib.c" -install_name @rpath/libanswer.dylib \ 190 -o "$work/libanswer.dylib" 191 run_ok macho-dylib-client-build env SDKROOT="$sdk" clang -isysroot "$sdk" \ 192 "$work/dylib-main.c" -L"$work" -lanswer \ 193 -Wl,-rpath,@loader_path -o "$work/dylib-client" 194 run_ok macho-dylib-run-before "$work/dylib-client" 195 if [ -f "$work/libanswer.dylib" ]; then 196 strip_shape macho-dylib "$work/libanswer.dylib" 1 197 run_ok macho-dylib-debug-resign codesign -f -s - \ 198 "$work/macho-dylib.debug" 199 run_ok macho-dylib-all-resign codesign -f -s - \ 200 "$work/macho-dylib.all" 201 cp "$work/libanswer.dylib" "$work/libanswer.original" 202 cp "$work/macho-dylib.debug" "$work/libanswer.dylib" 203 run_ok macho-dylib-debug-run "$work/dylib-client" 204 cp "$work/macho-dylib.all" "$work/libanswer.dylib" 205 run_ok macho-dylib-all-run "$work/dylib-client" 206 cp "$work/libanswer.original" "$work/libanswer.dylib" 207 fi 208 else 209 skip_test macho-dylib-runtime "native SDK/clang unavailable" 210 fi 211 elif [ "$host_os" = Linux ]; then 212 # Native hosted output covers a runnable dynamic PIE. 213 printf 'int main(void) { return 0; }\n' > "$work/linux-native.c" 214 run_ok linux-dynamic-build "$KIT" cc -g "$work/linux-native.c" -lc \ 215 -o "$work/linux-dynamic" 216 if [ -f "$work/linux-dynamic" ]; then 217 strip_shape linux-dynamic "$work/linux-dynamic" 218 run_ok linux-dynamic-run-before "$work/linux-dynamic" 219 run_ok linux-dynamic-debug-run "$work/linux-dynamic.debug" 220 run_ok linux-dynamic-all-run "$work/linux-dynamic.all" 221 fi 222 223 # Exercise a native DSO through the platform loader before and after both 224 # strip modes. Keep the SONAME stable while swapping the rewritten variants 225 # underneath one client, so a successful call proves the export and dynamic 226 # loader metadata remain usable rather than merely present. 227 printf 'int linked_answer(void) { return 42; }\n' > "$work/linux-shared.c" 228 printf 'int linked_answer(void); int main(void) { return linked_answer() == 42 ? 0 : 1; }\n' \ 229 > "$work/linux-shared-main.c" 230 run_ok linux-shared-build "$KIT" cc -g -fPIC -shared \ 231 -Wl,-soname,libanswer.so "$work/linux-shared.c" \ 232 -o "$work/libanswer.so" 233 run_ok linux-shared-client-build "$KIT" cc -g \ 234 "$work/linux-shared-main.c" -L"$work" -lanswer \ 235 '-Wl,-rpath,$ORIGIN' -lc -o "$work/linux-shared-client" 236 if [ -f "$work/libanswer.so" ] && [ -f "$work/linux-shared-client" ]; then 237 run_ok linux-shared-run-before "$work/linux-shared-client" 238 strip_shape linux-shared "$work/libanswer.so" 239 cp "$work/libanswer.so" "$work/libanswer.original" 240 cp "$work/linux-shared.debug" "$work/libanswer.so" 241 run_ok linux-shared-debug-run "$work/linux-shared-client" 242 cp "$work/linux-shared.all" "$work/libanswer.so" 243 run_ok linux-shared-all-run "$work/linux-shared-client" 244 cp "$work/libanswer.original" "$work/libanswer.so" 245 fi 246 247 # Execute a genuinely static freestanding artifact on supported native 248 # Linux architectures; the cross-format structural cases above remain 249 # unconditional. 250 case "$host_arch" in 251 x86_64|amd64) 252 native_static_target=x86_64-linux-gnu 253 cp "$work/x64-start.c" "$work/native-static.c" 254 ;; 255 aarch64|arm64) 256 native_static_target=aarch64-linux-gnu 257 cat > "$work/native-static.c" <<'EOF' 258 __attribute__((noreturn)) void _start(void) { 259 register long nr __asm__("x8") = 94; 260 register long code __asm__("x0") = 42; 261 __asm__ volatile("svc #0" : : "r"(nr), "r"(code) : "memory"); 262 __builtin_unreachable(); 263 } 264 EOF 265 ;; 266 riscv64) 267 native_static_target=riscv64-linux-gnu 268 cat > "$work/native-static.c" <<'EOF' 269 __attribute__((noreturn)) void _start(void) { 270 register long nr __asm__("a7") = 94; 271 register long code __asm__("a0") = 42; 272 __asm__ volatile("ecall" : : "r"(nr), "r"(code) : "memory"); 273 __builtin_unreachable(); 274 } 275 EOF 276 ;; 277 *) native_static_target= ;; 278 esac 279 if [ -n "$native_static_target" ]; then 280 run_ok linux-static-build "$KIT" cc -target "$native_static_target" -g \ 281 -ffreestanding -fno-stack-protector -fno-PIC -fno-pie -nostdlib \ 282 -static -Wl,-e,_start "$work/native-static.c" \ 283 -o "$work/linux-static" 284 if [ -f "$work/linux-static" ]; then 285 strip_shape linux-static "$work/linux-static" 286 "$work/linux-static" >/dev/null 2>&1; static_before_rc=$? 287 "$work/linux-static.debug" >/dev/null 2>&1; static_debug_rc=$? 288 "$work/linux-static.all" >/dev/null 2>&1; static_all_rc=$? 289 if [ "$static_before_rc" -eq 42 ]; then ok linux-static-run-before; 290 else not_ok linux-static-run-before; fi 291 if [ "$static_debug_rc" -eq 42 ]; then ok linux-static-debug-run; 292 else not_ok linux-static-debug-run; fi 293 if [ "$static_all_rc" -eq 42 ]; then ok linux-static-all-run; 294 else not_ok linux-static-all-run; fi 295 fi 296 else 297 skip_test linux-static-runtime "unsupported native architecture $host_arch" 298 fi 299 fi 300 301 kit_summary strip-linked 302 kit_exit