1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3# perf inject --aslr test 4 5set -e 6set -o pipefail 7 8shelldir=$(dirname "$0") 9# shellcheck source=lib/perf_has_symbol.sh 10. "${shelldir}"/lib/perf_has_symbol.sh 11 12sym="noploop" 13 14skip_test_missing_symbol ${sym} 15 16# Create global temp directory 17temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX) 18 19prog="perf test -w noploop" 20[ "$(uname -m)" = "s390x" ] && prog="$prog 3" 21err=0 22kprog="dd if=/dev/urandom of=/dev/null bs=1M count=50" 23 24cleanup() { 25 local exit_code=${1:-$?} 26 trap - EXIT TERM INT 27 if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then 28 echo "Test failed! Preserving temp directory: ${temp_dir}" 29 return 30 fi 31 # Check if temp_dir is set and looks sane before removing 32 if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then 33 rm -rf "${temp_dir}" 34 fi 35} 36 37trap_cleanup() { 38 local exit_code=$? 39 echo "Unexpected signal in ${FUNCNAME[1]}" 40 cleanup ${exit_code} 41 exit ${exit_code} 42} 43trap trap_cleanup EXIT TERM INT 44 45get_noploop_addr() { 46 local file=$1 47 perf script -i "$file" | awk ' 48 BEGIN { found=0 } 49 { 50 for (i=1; i<=NF; i++) { 51 if ($i ~ /noploop\+/) { 52 if (!found) { 53 print $(i-1) 54 found=1 55 } 56 } 57 } 58 }' 59} 60 61test_basic_aslr() { 62 echo "Test basic ASLR remapping" 63 local data 64 data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX") 65 local data2 66 data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX") 67 68 perf record -e task-clock:u -o "${data}" ${prog} 69 perf inject -v --aslr -i "${data}" -o "${data2}" 70 71 orig_addr=$(get_noploop_addr "${data}") 72 new_addr=$(get_noploop_addr "${data2}") 73 74 echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr" 75 76 if [ -z "$orig_addr" ]; then 77 echo "Basic ASLR test [Failed - no noploop samples in original file]" 78 err=1 79 elif [ -z "$new_addr" ]; then 80 echo "Basic ASLR test [Failed - could not find remapped address]" 81 err=1 82 elif [ "$orig_addr" = "$new_addr" ]; then 83 echo "Basic ASLR test [Failed - addresses are not remapped]" 84 err=1 85 else 86 echo "Basic ASLR test [Success]" 87 fi 88} 89 90test_pipe_aslr() { 91 echo "Test pipe mode ASLR remapping" 92 local data 93 data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX") 94 local data2 95 data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX") 96 97 # Use tee to save the original pipe data for comparison 98 perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}" 99 100 orig_addr=$(get_noploop_addr "${data}") 101 new_addr=$(get_noploop_addr "${data2}") 102 103 echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr" 104 105 if [ -z "$orig_addr" ]; then 106 echo "Pipe ASLR test [Failed - no noploop samples in original file]" 107 err=1 108 elif [ -z "$new_addr" ]; then 109 echo "Pipe ASLR test [Failed - could not find remapped address]" 110 err=1 111 elif [ "$orig_addr" = "$new_addr" ]; then 112 echo "Pipe ASLR test [Failed - addresses are not remapped]" 113 err=1 114 else 115 echo "Pipe ASLR test [Success]" 116 fi 117} 118 119test_callchain_aslr() { 120 echo "Test Callchain ASLR remapping" 121 local data 122 data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX") 123 local data2 124 data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX") 125 126 perf record -g -e task-clock:u -o "${data}" ${prog} 127 perf inject --aslr -i "${data}" -o "${data2}" 128 129 orig_addr=$(get_noploop_addr "${data}") 130 new_addr=$(get_noploop_addr "${data2}") 131 132 echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr" 133 134 if [ -z "$orig_addr" ]; then 135 echo "Callchain ASLR test [Failed - no noploop samples in original file]" 136 err=1 137 elif [ -z "$new_addr" ]; then 138 if perf evlist -v -i "${data}" | grep -q 'sample_type:.*STACK_USER'; then 139 echo "Dropping stack user sample as possible ASLR leak" 140 echo "Call-graph dwarf not supported with 'perf inject --aslr'" 141 echo "Callchain ASLR test [Skip]" 142 else 143 echo "Callchain ASLR test [Failed - could not find remapped address]" 144 err=1 145 fi 146 elif [ "$orig_addr" = "$new_addr" ]; then 147 echo "Callchain ASLR test [Failed - addresses are not remapped]" 148 err=1 149 else 150 # Extract callchain addresses (indented lines starting with hex addresses) 151 orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}') 152 new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}') 153 154 if [ -z "$orig_callchain" ]; then 155 echo "Callchain ASLR test [Failed - no callchain samples in original file]" 156 err=1 157 elif [ -z "$new_callchain" ]; then 158 echo "Callchain ASLR test [Failed - callchain data was dropped]" 159 err=1 160 elif [ "$orig_callchain" = "$new_callchain" ]; then 161 echo "Callchain ASLR test [Failed - callchain addresses were not remapped]" 162 err=1 163 else 164 echo "Callchain ASLR test [Success]" 165 fi 166 fi 167} 168 169test_report_aslr() { 170 echo "Test perf report consistency" 171 local data 172 data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX") 173 local data2 174 data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX") 175 local data_clean 176 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 177 178 perf record -e task-clock:u -o "${data}" ${prog} 179 # Use -b to inject build-ids and force ordered events processing in both 180 perf inject -b -i "${data}" -o "${data_clean}" 181 perf inject -v -b --aslr -i "${data}" -o "${data2}" 182 183 local report1="${temp_dir}/report1_basic" 184 local report2="${temp_dir}/report2_basic" 185 local report1_clean="${temp_dir}/report1_basic.clean" 186 local report2_clean="${temp_dir}/report2_basic.clean" 187 local diff_file="${temp_dir}/diff_basic" 188 189 perf report -i "${data_clean}" --stdio > "${report1}" 190 perf report -i "${data2}" --stdio > "${report2}" 191 192 # Strip headers and compare lines with percentages 193 grep '%' "${report1}" | grep -v '^#' | \ 194 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true 195 grep '%' "${report2}" | grep -v '^#' | \ 196 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true 197 198 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 199 200 if [ ! -s "${report1_clean}" ]; then 201 echo "Report ASLR test [Failed - no samples captured]" 202 err=1 203 elif [ -s "${diff_file}" ]; then 204 echo "Report ASLR test [Failed - reports differ]" 205 echo "Showing first 20 lines of diff:" 206 head -n 20 "${diff_file}" 207 err=1 208 else 209 echo "Report ASLR test [Success]" 210 fi 211} 212 213test_pipe_report_aslr() { 214 echo "Test pipe mode perf report consistency" 215 local data 216 data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX") 217 local data2 218 data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX") 219 local data_clean 220 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 221 222 # Use tee to save the original pipe data, then process it with inject -b 223 perf record -e task-clock:u -o - ${prog} | \ 224 tee "${data}" | \ 225 perf inject -b --aslr -o "${data2}" 226 perf inject -b -i "${data}" -o "${data_clean}" 227 228 local report1="${temp_dir}/report1_pipe" 229 local report2="${temp_dir}/report2_pipe" 230 local report1_clean="${temp_dir}/report1_pipe.clean" 231 local report2_clean="${temp_dir}/report2_pipe.clean" 232 local diff_file="${temp_dir}/diff_pipe" 233 234 perf report -i "${data_clean}" --stdio > "${report1}" 235 perf report -i "${data2}" --stdio > "${report2}" 236 237 # Strip headers and compare lines with percentages 238 grep '%' "${report1}" | grep -v '^#' | \ 239 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true 240 grep '%' "${report2}" | grep -v '^#' | \ 241 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true 242 243 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 244 245 if [ ! -s "${report1_clean}" ]; then 246 echo "Pipe Report ASLR test [Failed - no samples captured]" 247 err=1 248 elif [ -s "${diff_file}" ]; then 249 echo "Pipe Report ASLR test [Failed - reports differ]" 250 echo "Showing first 20 lines of diff:" 251 head -n 20 "${diff_file}" 252 err=1 253 else 254 echo "Pipe Report ASLR test [Success]" 255 fi 256} 257 258test_pipe_out_report_aslr() { 259 echo "Test pipe output mode perf report consistency" 260 local data 261 data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX") 262 local data_clean 263 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 264 265 perf record -e task-clock:u -o "${data}" ${prog} 266 perf inject -b -i "${data}" -o "${data_clean}" 267 268 local report1="${temp_dir}/report1_pipe_out" 269 local report2="${temp_dir}/report2_pipe_out" 270 local report1_clean="${temp_dir}/report1_pipe_out.clean" 271 local report2_clean="${temp_dir}/report2_pipe_out.clean" 272 local diff_file="${temp_dir}/diff_pipe_out" 273 274 perf report -i "${data_clean}" --stdio > "${report1}" 275 perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}" 276 277 # Strip headers and compare lines with percentages 278 grep '%' "${report1}" | grep -v '^#' | \ 279 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true 280 grep '%' "${report2}" | grep -v '^#' | \ 281 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true 282 283 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 284 285 if [ ! -s "${report1_clean}" ]; then 286 echo "Pipe Output Report ASLR test [Failed - no samples captured]" 287 err=1 288 elif [ -s "${diff_file}" ]; then 289 echo "Pipe Output Report ASLR test [Failed - reports differ]" 290 echo "Showing first 20 lines of diff:" 291 head -n 20 "${diff_file}" 292 err=1 293 else 294 echo "Pipe Output Report ASLR test [Success]" 295 fi 296} 297 298test_dropped_samples() { 299 echo "Test dropped samples (phys-data)" 300 local data 301 data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX") 302 local data2 303 data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX") 304 305 # Check if --phys-data is supported by recording a short run 306 if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then 307 echo "Skipping dropped samples test as --phys-data is not supported" 308 return 309 fi 310 311 perf record -e task-clock:u --phys-data -o "${data}" ${prog} 312 perf inject --aslr -i "${data}" -o "${data2}" 313 314 # Verify that the original file actually contained samples! 315 orig_samples=$(perf script -i "${data}" | wc -l) 316 if [ "$orig_samples" -eq 0 ]; then 317 echo "Dropped samples test [Failed - no samples in original file]" 318 err=1 319 else 320 # Verify that samples are dropped. 321 samples_count=$(perf script -i "${data2}" | wc -l) 322 323 if [ "$samples_count" -gt 0 ]; then 324 echo "Dropped samples test [Failed - samples were not dropped]" 325 err=1 326 else 327 echo "Dropped samples test [Success]" 328 fi 329 fi 330} 331 332test_kernel_aslr() { 333 echo "Test kernel ASLR remapping" 334 local kdata 335 kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX") 336 local kdata2 337 kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX") 338 local log_file 339 log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX") 340 341 # Try to record kernel samples 342 if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then 343 echo "Skipping kernel ASLR test as recording failed (maybe no permissions)" 344 return 345 fi 346 347 # Check for warning about kernel map restriction 348 if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then 349 echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)" 350 return 351 fi 352 353 perf inject -v --aslr -i "${kdata}" -o "${kdata2}" 354 355 # Check if kernel addresses are remapped. 356 # Find the field that ends with :k: (the event name) and take the next field! 357 orig_addr=$(perf script -i "${kdata}" | awk ' 358 BEGIN { found=0 } 359 { 360 for (i=1; i<NF; i++) { 361 if ($i ~ /:[k]+:?$/) { 362 if (!found) { 363 print $(i+1) 364 found=1 365 } 366 } 367 } 368 }') 369 new_addr=$(perf script -i "${kdata2}" | awk ' 370 BEGIN { found=0 } 371 { 372 for (i=1; i<NF; i++) { 373 if ($i ~ /:[k]+:?$/) { 374 if (!found) { 375 print $(i+1) 376 found=1 377 } 378 } 379 } 380 }') 381 382 echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr" 383 384 if [ -z "$orig_addr" ]; then 385 echo "Kernel ASLR test [Failed - no kernel samples in original file]" 386 err=1 387 elif [ -z "$new_addr" ]; then 388 echo "Kernel ASLR test [Failed - could not find remapped address]" 389 err=1 390 elif [ "$orig_addr" = "$new_addr" ]; then 391 echo "Kernel ASLR test [Failed - addresses are not remapped]" 392 err=1 393 else 394 echo "Kernel ASLR test [Success]" 395 fi 396} 397 398test_kernel_report_aslr() { 399 echo "Test kernel perf report consistency" 400 local kdata 401 kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX") 402 local kdata2 403 kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX") 404 local data_clean 405 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 406 local log_file 407 log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX") 408 409 # Try to record kernel samples 410 if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then 411 echo "Skipping kernel report test as recording failed (maybe no permissions)" 412 return 413 fi 414 415 # Check for warning about kernel map restriction 416 if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then 417 echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)" 418 return 419 fi 420 421 # Use -b to inject build-ids and force ordered events processing in both 422 perf inject -b -i "${kdata}" -o "${data_clean}" 423 perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}" 424 425 local report1="${temp_dir}/report_kernel1" 426 local report2="${temp_dir}/report_kernel2" 427 local report1_clean="${temp_dir}/report_kernel1.clean" 428 local report2_clean="${temp_dir}/report_kernel2.clean" 429 430 perf report -i "${data_clean}" --stdio > "${report1}" 431 perf report -i "${kdata2}" --stdio > "${report2}" 432 433 # Strip headers and compare lines with percentages 434 grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true 435 grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true 436 437 # Normalize kernel DSOs and addresses in clean reports 438 # This allows kernel modules to be either a module or kernel.kallsyms 439 local report1_norm="${temp_dir}/report_kernel1.norm" 440 local report2_norm="${temp_dir}/report_kernel2.norm" 441 local diff_file="${temp_dir}/diff_kernel" 442 443 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \ 444 awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \ 445 sort > "${report1_norm}" || true 446 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \ 447 awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \ 448 sort > "${report2_norm}" || true 449 450 diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true 451 452 if [ ! -s "${report1_norm}" ]; then 453 echo "Kernel Report ASLR test [Failed - no samples captured]" 454 err=1 455 elif [ -s "${diff_file}" ]; then 456 echo "Kernel Report ASLR test [Failed - reports differ]" 457 echo "Showing first 20 lines of diff:" 458 head -n 20 "${diff_file}" 459 err=1 460 else 461 echo "Kernel Report ASLR test [Success]" 462 fi 463} 464 465test_regs_stripping() { 466 echo "Test user register stripping" 467 local rdata="${temp_dir}/perf.data.regs" 468 local rdata2="${temp_dir}/perf.data.regs.injected" 469 local rdata_clean="${temp_dir}/perf.data.regs.clean" 470 471 if ! perf record -e cycles:u --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then 472 echo "Skipping user registers test as recording failed (unsupported flag/platform)" 473 return 474 fi 475 476 perf inject -b -i "${rdata}" -o "${rdata_clean}" 477 perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}" 478 479 local report1="${temp_dir}/report_regs1" 480 local report2="${temp_dir}/report_regs2" 481 local report1_clean="${temp_dir}/report_regs1.clean" 482 local report2_clean="${temp_dir}/report_regs2.clean" 483 local diff_file="${temp_dir}/diff_regs" 484 485 perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true 486 perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true 487 488 grep '%' "${report1}" | grep -v '^#' | \ 489 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \ 490 sort > "${report1_clean}" || true 491 grep '%' "${report2}" | grep -v '^#' | \ 492 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \ 493 sort > "${report2_clean}" || true 494 495 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 496 497 if [ ! -s "${report1_clean}" ]; then 498 echo "User registers stripping test [Failed - profile trace starved/empty]" 499 err=1 500 return 501 elif [ -s "${diff_file}" ]; then 502 echo "User registers stripping test [Failed - report parsing differs]" 503 echo "Showing first 20 lines of diff:" 504 head -n 20 "${diff_file}" 505 err=1 506 return 507 fi 508 509 local script_dump="${temp_dir}/script_regs_dump" 510 perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true 511 if grep -q "user regs:" "${script_dump}"; then 512 echo "User registers stripping test [Failed - register dumps still present]" 513 err=1 514 else 515 echo "User registers stripping test [Success]" 516 fi 517} 518 519test_basic_aslr 520test_pipe_aslr 521test_callchain_aslr 522test_report_aslr 523test_pipe_report_aslr 524test_pipe_out_report_aslr 525test_dropped_samples 526case "$(uname -m)" in 527 aarch64*|arm*) 528 echo "Skipping kernel ASLR tests on ARM" 529 ;; 530 *) 531 test_kernel_aslr 532 test_kernel_report_aslr 533 ;; 534esac 535 536test_regs_stripping 537 538cleanup ${err} 539exit $err 540