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