kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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