kit

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

stack_protector.sh (10058B)


      1 #!/bin/sh
      2 # Focused driver/CG coverage for the native stack protector.  Selection tests
      3 # inspect undefined ABI symbols in relocatable objects; runtime tests mutate
      4 # the process guard after the function-entry snapshot and exercise both return
      5 # arms at every supported optimization spelling.
      6 
      7 set -u
      8 
      9 script_dir=$(cd "$(dirname "$0")" && pwd)
     10 repo_root=$(cd "$script_dir/../.." && pwd)
     11 fixtures="$script_dir/fixtures"
     12 KIT="${KIT:-$repo_root/build/kit}"
     13 
     14 if [ ! -x "$KIT" ]; then
     15   echo "stack-protector: kit binary not found at $KIT" >&2
     16   exit 2
     17 fi
     18 
     19 work=$(mktemp -d "${TMPDIR:-/tmp}/kit-stack-protector.XXXXXX")
     20 trap 'rm -rf "$work"' EXIT
     21 
     22 KIT_KIT_DIR="$repo_root/test/lib"
     23 . "$repo_root/test/lib/kit_sh_kit.sh"
     24 kit_report_init
     25 
     26 has_symbol() {
     27   name=$1
     28   file=$2
     29   needle=$3
     30   if "$KIT" nm "$file" > "$work/$name.nm" 2> "$work/$name.nm.err" &&
     31      grep -F "$needle" "$work/$name.nm" >/dev/null 2>&1; then
     32     ok "$name"
     33   else
     34     {
     35       printf 'missing symbol substring: %s\n' "$needle"
     36       sed 's/^/nm: /' "$work/$name.nm" 2>/dev/null
     37       sed 's/^/err: /' "$work/$name.nm.err" 2>/dev/null
     38     } > "$work/$name.diag"
     39     not_ok "$name" "$work/$name.diag"
     40   fi
     41 }
     42 
     43 lacks_symbol() {
     44   name=$1
     45   file=$2
     46   needle=$3
     47   if "$KIT" nm "$file" > "$work/$name.nm" 2> "$work/$name.nm.err" &&
     48      ! grep -F "$needle" "$work/$name.nm" >/dev/null 2>&1; then
     49     ok "$name"
     50   else
     51     {
     52       printf 'unexpected symbol substring: %s\n' "$needle"
     53       sed 's/^/nm: /' "$work/$name.nm" 2>/dev/null
     54       sed 's/^/err: /' "$work/$name.nm.err" 2>/dev/null
     55     } > "$work/$name.diag"
     56     not_ok "$name" "$work/$name.diag"
     57   fi
     58 }
     59 
     60 # Conventional frontend selection.  Basic protects character arrays of at
     61 # least eight bytes; strong additionally protects any array or address-taken
     62 # local; all protects even a function without local storage.
     63 run_ok select-basic-plain "$KIT" cc -fstack-protector -c \
     64   "$fixtures/stack_plain.c" -o "$work/basic-plain.o"
     65 lacks_symbol select-basic-plain-symbol "$work/basic-plain.o" stack_chk
     66 
     67 run_ok select-basic-char4 "$KIT" cc -fstack-protector -c \
     68   "$fixtures/stack_char4.c" -o "$work/basic-char4.o"
     69 lacks_symbol select-basic-char4-symbol "$work/basic-char4.o" stack_chk
     70 
     71 run_ok select-basic-char8 "$KIT" cc -fstack-protector -c \
     72   "$fixtures/stack_char8.c" -o "$work/basic-char8.o"
     73 has_symbol select-basic-char8-guard "$work/basic-char8.o" stack_chk_guard
     74 has_symbol select-basic-char8-fail "$work/basic-char8.o" stack_chk_fail
     75 
     76 run_ok select-basic-int-array "$KIT" cc -fstack-protector -c \
     77   "$fixtures/stack_int_array.c" -o "$work/basic-int.o"
     78 lacks_symbol select-basic-int-array-symbol "$work/basic-int.o" stack_chk
     79 
     80 run_ok select-strong-int-array "$KIT" cc -fstack-protector-strong -c \
     81   "$fixtures/stack_int_array.c" -o "$work/strong-int.o"
     82 has_symbol select-strong-int-array-guard "$work/strong-int.o" stack_chk_guard
     83 has_symbol select-strong-int-array-fail "$work/strong-int.o" stack_chk_fail
     84 
     85 run_ok select-strong-address "$KIT" cc -fstack-protector-strong -c \
     86   "$fixtures/stack_address.c" -o "$work/strong-address.o"
     87 has_symbol select-strong-address-guard "$work/strong-address.o" stack_chk_guard
     88 has_symbol select-strong-address-fail "$work/strong-address.o" stack_chk_fail
     89 
     90 run_ok select-all-plain "$KIT" cc -fstack-protector-all -c \
     91   "$fixtures/stack_plain.c" -o "$work/all-plain.o"
     92 has_symbol select-all-plain-guard "$work/all-plain.o" stack_chk_guard
     93 has_symbol select-all-plain-fail "$work/all-plain.o" stack_chk_fail
     94 
     95 run_ok select-last-option-wins "$KIT" cc -fstack-protector-all \
     96   -fno-stack-protector -c "$fixtures/stack_char8.c" \
     97   -o "$work/disabled-char8.o"
     98 lacks_symbol select-last-option-wins-symbol "$work/disabled-char8.o" stack_chk
     99 
    100 run_fail select-unknown-mode "$KIT" cc -fstack-protector-explicit \
    101   -c "$fixtures/stack_plain.c" -o "$work/unknown.o"
    102 contains select-unknown-mode-diag "$work/select-unknown-mode.err" \
    103   "unsupported stack protector mode"
    104 
    105 # All three native backends must retain ABI guard/failure references at O0 and
    106 # through the optimizer (-O2 is the supported alias for -O1).
    107 for target in aarch64-none-elf x86_64-none-elf riscv64-none-elf; do
    108   for opt in 0 1 2; do
    109     tag=$(printf '%s-O%s' "$target" "$opt" | tr _ -)
    110     obj="$work/$tag.o"
    111     run_ok "cross-$tag" "$KIT" cc -target "$target" -ffreestanding \
    112       -O"$opt" -fstack-protector -c "$fixtures/stack_char8.c" -o "$obj"
    113     has_symbol "cross-$tag-guard" "$obj" stack_chk_guard
    114     has_symbol "cross-$tag-fail" "$obj" stack_chk_fail
    115   done
    116 done
    117 
    118 # On glibc AArch64/RISC-V, __stack_chk_guard is exported by the ELF
    119 # interpreter rather than libc. Exercise that linker lane hermetically with a
    120 # tiny DSO that provides only the failure hook: the guard must remain a dynamic
    121 # object import and must not add ld-linux to DT_NEEDED.
    122 for row in 'aa64 aarch64-linux-gnu' 'rv64 riscv64-linux-gnu'; do
    123   set -- $row
    124   short=$1
    125   target=$2
    126   fail_obj="$work/loader-$short-fail.o"
    127   fail_dso="$work/loader-$short-libstackfail.so"
    128   protected_obj="$work/loader-$short-protected.o"
    129   image="$work/loader-$short-image"
    130   run_ok "loader-$short-fail-object" "$KIT" cc -target "$target" \
    131     -ffreestanding -fPIC -c "$fixtures/stack_fail_only.c" -o "$fail_obj"
    132   run_ok "loader-$short-fail-dso" "$KIT" ld -target "$target" -shared \
    133     -soname libstackfail.so -o "$fail_dso" "$fail_obj"
    134   run_ok "loader-$short-protected-object" "$KIT" cc -target "$target" \
    135     -ffreestanding -O1 -fstack-protector -c "$fixtures/stack_char8.c" \
    136     -o "$protected_obj"
    137   run_ok "loader-$short-link" "$KIT" ld -target "$target" -pie \
    138     -e stack_char8 -o "$image" "$protected_obj" "$fail_dso"
    139   if "$KIT" objdump -p -T -R "$image" > "$work/loader-$short.dump" \
    140        2> "$work/loader-$short.dump.err" &&
    141      grep -F ' U ' "$work/loader-$short.dump" |
    142        grep -F '__stack_chk_guard' >/dev/null 2>&1 &&
    143      grep -F 'NEEDED       libstackfail.so' "$work/loader-$short.dump" \
    144        >/dev/null 2>&1 &&
    145      grep -F '__stack_chk_guard' "$work/loader-$short.dump" |
    146        grep -E 'GLOB_DAT|R_RISCV_64' >/dev/null 2>&1 &&
    147      ! grep -F 'NEEDED       ld-linux' "$work/loader-$short.dump" \
    148        >/dev/null 2>&1; then
    149     ok "loader-$short-guard-import"
    150   else
    151     {
    152       printf 'expected interpreter-owned dynamic guard import\n'
    153       sed 's/^/dump: /' "$work/loader-$short.dump" 2>/dev/null
    154       sed 's/^/err: /' "$work/loader-$short.dump.err" 2>/dev/null
    155     } > "$work/loader-$short.diag"
    156     not_ok "loader-$short-guard-import" "$work/loader-$short.diag"
    157   fi
    158 done
    159 
    160 # The supported Windows SDK is LLVM-MinGW, whose CRT supplies the GNU
    161 # stack-protector guard and failure hook in libmingwex.
    162 for target in aarch64-windows x86_64-windows; do
    163   tag=$(printf '%s' "$target" | tr _ -)
    164   obj="$work/$tag.o"
    165   run_ok "windows-$tag" "$KIT" cc -target "$target" -ffreestanding \
    166     -O1 -fstack-protector-all -c "$fixtures/stack_plain.c" -o "$obj"
    167   has_symbol "windows-$tag-guard" "$obj" stack_chk_guard
    168   has_symbol "windows-$tag-fail" "$obj" stack_chk_fail
    169   lacks_symbol "windows-$tag-no-msvc-cookie" "$obj" security_cookie
    170 done
    171 
    172 # Linux/Android x86-64 reserve the guard at fs:0x28 rather than exporting a
    173 # __stack_chk_guard object. The object must carry only the failure reference;
    174 # disassembly verifies that both entry and return checks use the segment read.
    175 for target in x86_64-linux-gnu x86_64-linux-android; do
    176   for opt in 0 1 2; do
    177     tag=$(printf '%s-O%s' "$target" "$opt" | tr _ -)
    178     obj="$work/$tag.o"
    179     run_ok "tls-$tag" "$KIT" cc -target "$target" -ffreestanding \
    180       -O"$opt" -fstack-protector-all -c "$fixtures/stack_plain.c" -o "$obj"
    181     lacks_symbol "tls-$tag-no-guard-symbol" "$obj" stack_chk_guard
    182     has_symbol "tls-$tag-fail" "$obj" stack_chk_fail
    183     # Kit's current x64 disassembler prints a segment override as a standalone
    184     # `.byte 0x64` immediately before the decoded mov; count those prefixes.
    185     if "$KIT" objdump -d "$obj" > "$work/$tag.dis" 2> "$work/$tag.dis.err" &&
    186        [ "$(grep -c '\.byte 0x64' "$work/$tag.dis" 2>/dev/null || true)" -ge 2 ]; then
    187       ok "tls-$tag-fs-guard-loads"
    188     else
    189       {
    190         printf 'expected two fs-segment guard loads\n'
    191         sed 's/^/dis: /' "$work/$tag.dis" 2>/dev/null
    192         sed 's/^/err: /' "$work/$tag.dis.err" 2>/dev/null
    193       } > "$work/$tag.dis.diag"
    194       not_ok "tls-$tag-fs-guard-loads" "$work/$tag.dis.diag"
    195     fi
    196   done
    197 done
    198 
    199 # A hosted executable with an unchanged guard returns normally.  Mutating the
    200 # guard after the entry snapshot must reach __stack_chk_fail on both explicit
    201 # return arms at O0, O1, and O2.
    202 for opt in 0 1 2; do
    203   for path in 0 1 2; do
    204     tag=runtime-O$opt-path$path
    205     run_ok "$tag-build-ok" "$KIT" cc -O"$opt" -fstack-protector \
    206       -DSTACK_PATH="$path" "$fixtures/stack_runtime.c" \
    207       "$fixtures/stack_support_ok.c" -o "$work/$tag-ok"
    208     run_ok "$tag-run-ok" "$work/$tag-ok"
    209 
    210     run_ok "$tag-build-bad" "$KIT" cc -O"$opt" -fstack-protector \
    211       -DSTACK_PATH="$path" "$fixtures/stack_runtime.c" \
    212       "$fixtures/stack_support_bad.c" -o "$work/$tag-bad"
    213     run_fail "$tag-run-bad" "$work/$tag-bad"
    214   done
    215 
    216   # A separate-TU write one byte beyond an eight-byte buffer lands on the
    217   # entry snapshot itself. Require the failure hook's exact status, proving
    218   # this is a real frame canary rather than only a symbol/check scaffold.
    219   overflow=runtime-O$opt-overflow
    220   run_ok "$overflow-build" "$KIT" cc -O"$opt" -fstack-protector \
    221     "$fixtures/stack_overflow.c" "$fixtures/stack_overflow_write.c" \
    222     "$fixtures/stack_support_ok.c" -o "$work/$overflow"
    223   if "$work/$overflow" > "$work/$overflow.out" 2> "$work/$overflow.err"; then
    224     overflow_rc=0
    225   else
    226     overflow_rc=$?
    227   fi
    228   if [ "$overflow_rc" -eq 99 ]; then
    229     ok "$overflow-run"
    230   else
    231     {
    232       printf 'expected stack failure status 99, got %s\n' "$overflow_rc"
    233       sed 's/^/out: /' "$work/$overflow.out" 2>/dev/null
    234       sed 's/^/err: /' "$work/$overflow.err" 2>/dev/null
    235     } > "$work/$overflow.diag"
    236     not_ok "$overflow-run" "$work/$overflow.diag"
    237   fi
    238 done
    239 
    240 kit_summary stack-protector
    241 kit_exit