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 echo "Callchain ASLR test [Failed - could not find remapped address]" 139 err=1 140 elif [ "$orig_addr" = "$new_addr" ]; then 141 echo "Callchain ASLR test [Failed - addresses are not remapped]" 142 err=1 143 else 144 # Extract callchain addresses (indented lines starting with hex addresses) 145 orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}') 146 new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}') 147 148 if [ -z "$orig_callchain" ]; then 149 echo "Callchain ASLR test [Failed - no callchain samples in original file]" 150 err=1 151 elif [ -z "$new_callchain" ]; then 152 echo "Callchain ASLR test [Failed - callchain data was dropped]" 153 err=1 154 elif [ "$orig_callchain" = "$new_callchain" ]; then 155 echo "Callchain ASLR test [Failed - callchain addresses were not remapped]" 156 err=1 157 else 158 echo "Callchain ASLR test [Success]" 159 fi 160 fi 161} 162 163test_report_aslr() { 164 echo "Test perf report consistency" 165 local data 166 data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX") 167 local data2 168 data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX") 169 local data_clean 170 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 171 172 perf record -e task-clock:u -o "${data}" ${prog} 173 # Use -b to inject build-ids and force ordered events processing in both 174 perf inject -b -i "${data}" -o "${data_clean}" 175 perf inject -v -b --aslr -i "${data}" -o "${data2}" 176 177 local report1="${temp_dir}/report1_basic" 178 local report2="${temp_dir}/report2_basic" 179 local report1_clean="${temp_dir}/report1_basic.clean" 180 local report2_clean="${temp_dir}/report2_basic.clean" 181 local diff_file="${temp_dir}/diff_basic" 182 183 perf report -i "${data_clean}" --stdio > "${report1}" 184 perf report -i "${data2}" --stdio > "${report2}" 185 186 # Strip headers and compare lines with percentages 187 grep '%' "${report1}" | grep -v '^#' | \ 188 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true 189 grep '%' "${report2}" | grep -v '^#' | \ 190 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true 191 192 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 193 194 if [ ! -s "${report1_clean}" ]; then 195 echo "Report ASLR test [Failed - no samples captured]" 196 err=1 197 elif [ -s "${diff_file}" ]; then 198 echo "Report ASLR test [Failed - reports differ]" 199 echo "Showing first 20 lines of diff:" 200 head -n 20 "${diff_file}" 201 err=1 202 else 203 echo "Report ASLR test [Success]" 204 fi 205} 206 207test_pipe_report_aslr() { 208 echo "Test pipe mode perf report consistency" 209 local data 210 data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX") 211 local data2 212 data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX") 213 local data_clean 214 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 215 216 # Use tee to save the original pipe data, then process it with inject -b 217 perf record -e task-clock:u -o - ${prog} | \ 218 tee "${data}" | \ 219 perf inject -b --aslr -o "${data2}" 220 perf inject -b -i "${data}" -o "${data_clean}" 221 222 local report1="${temp_dir}/report1_pipe" 223 local report2="${temp_dir}/report2_pipe" 224 local report1_clean="${temp_dir}/report1_pipe.clean" 225 local report2_clean="${temp_dir}/report2_pipe.clean" 226 local diff_file="${temp_dir}/diff_pipe" 227 228 perf report -i "${data_clean}" --stdio > "${report1}" 229 perf report -i "${data2}" --stdio > "${report2}" 230 231 # Strip headers and compare lines with percentages 232 grep '%' "${report1}" | grep -v '^#' | \ 233 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true 234 grep '%' "${report2}" | grep -v '^#' | \ 235 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true 236 237 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 238 239 if [ ! -s "${report1_clean}" ]; then 240 echo "Pipe Report ASLR test [Failed - no samples captured]" 241 err=1 242 elif [ -s "${diff_file}" ]; then 243 echo "Pipe Report ASLR test [Failed - reports differ]" 244 echo "Showing first 20 lines of diff:" 245 head -n 20 "${diff_file}" 246 err=1 247 else 248 echo "Pipe Report ASLR test [Success]" 249 fi 250} 251 252test_pipe_out_report_aslr() { 253 echo "Test pipe output mode perf report consistency" 254 local data 255 data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX") 256 local data_clean 257 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 258 259 perf record -e task-clock:u -o "${data}" ${prog} 260 perf inject -b -i "${data}" -o "${data_clean}" 261 262 local report1="${temp_dir}/report1_pipe_out" 263 local report2="${temp_dir}/report2_pipe_out" 264 local report1_clean="${temp_dir}/report1_pipe_out.clean" 265 local report2_clean="${temp_dir}/report2_pipe_out.clean" 266 local diff_file="${temp_dir}/diff_pipe_out" 267 268 perf report -i "${data_clean}" --stdio > "${report1}" 269 perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}" 270 271 # Strip headers and compare lines with percentages 272 grep '%' "${report1}" | grep -v '^#' | \ 273 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true 274 grep '%' "${report2}" | grep -v '^#' | \ 275 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true 276 277 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 278 279 if [ ! -s "${report1_clean}" ]; then 280 echo "Pipe Output Report ASLR test [Failed - no samples captured]" 281 err=1 282 elif [ -s "${diff_file}" ]; then 283 echo "Pipe Output Report ASLR test [Failed - reports differ]" 284 echo "Showing first 20 lines of diff:" 285 head -n 20 "${diff_file}" 286 err=1 287 else 288 echo "Pipe Output Report ASLR test [Success]" 289 fi 290} 291 292test_dropped_samples() { 293 echo "Test dropped samples (phys-data)" 294 local data 295 data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX") 296 local data2 297 data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX") 298 299 # Check if --phys-data is supported by recording a short run 300 if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then 301 echo "Skipping dropped samples test as --phys-data is not supported" 302 return 303 fi 304 305 perf record -e task-clock:u --phys-data -o "${data}" ${prog} 306 perf inject --aslr -i "${data}" -o "${data2}" 307 308 # Verify that the original file actually contained samples! 309 orig_samples=$(perf script -i "${data}" | wc -l) 310 if [ "$orig_samples" -eq 0 ]; then 311 echo "Dropped samples test [Failed - no samples in original file]" 312 err=1 313 else 314 # Verify that samples are dropped. 315 samples_count=$(perf script -i "${data2}" | wc -l) 316 317 if [ "$samples_count" -gt 0 ]; then 318 echo "Dropped samples test [Failed - samples were not dropped]" 319 err=1 320 else 321 echo "Dropped samples test [Success]" 322 fi 323 fi 324} 325 326test_kernel_aslr() { 327 echo "Test kernel ASLR remapping" 328 local kdata 329 kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX") 330 local kdata2 331 kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX") 332 local log_file 333 log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX") 334 335 # Try to record kernel samples 336 if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then 337 echo "Skipping kernel ASLR test as recording failed (maybe no permissions)" 338 return 339 fi 340 341 # Check for warning about kernel map restriction 342 if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then 343 echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)" 344 return 345 fi 346 347 perf inject -v --aslr -i "${kdata}" -o "${kdata2}" 348 349 # Check if kernel addresses are remapped. 350 # Find the field that ends with :k: (the event name) and take the next field! 351 orig_addr=$(perf script -i "${kdata}" | awk ' 352 BEGIN { found=0 } 353 { 354 for (i=1; i<NF; i++) { 355 if ($i ~ /:[k]+:?$/) { 356 if (!found) { 357 print $(i+1) 358 found=1 359 } 360 } 361 } 362 }') 363 new_addr=$(perf script -i "${kdata2}" | awk ' 364 BEGIN { found=0 } 365 { 366 for (i=1; i<NF; i++) { 367 if ($i ~ /:[k]+:?$/) { 368 if (!found) { 369 print $(i+1) 370 found=1 371 } 372 } 373 } 374 }') 375 376 echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr" 377 378 if [ -z "$orig_addr" ]; then 379 echo "Kernel ASLR test [Failed - no kernel samples in original file]" 380 err=1 381 elif [ -z "$new_addr" ]; then 382 echo "Kernel ASLR test [Failed - could not find remapped address]" 383 err=1 384 elif [ "$orig_addr" = "$new_addr" ]; then 385 echo "Kernel ASLR test [Failed - addresses are not remapped]" 386 err=1 387 else 388 echo "Kernel ASLR test [Success]" 389 fi 390} 391 392test_kernel_report_aslr() { 393 echo "Test kernel perf report consistency" 394 local kdata 395 kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX") 396 local kdata2 397 kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX") 398 local data_clean 399 data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") 400 local log_file 401 log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX") 402 403 # Try to record kernel samples 404 if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then 405 echo "Skipping kernel report test as recording failed (maybe no permissions)" 406 return 407 fi 408 409 # Check for warning about kernel map restriction 410 if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then 411 echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)" 412 return 413 fi 414 415 # Use -b to inject build-ids and force ordered events processing in both 416 perf inject -b -i "${kdata}" -o "${data_clean}" 417 perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}" 418 419 local report1="${temp_dir}/report_kernel1" 420 local report2="${temp_dir}/report_kernel2" 421 local report1_clean="${temp_dir}/report_kernel1.clean" 422 local report2_clean="${temp_dir}/report_kernel2.clean" 423 424 perf report -i "${data_clean}" --stdio > "${report1}" 425 perf report -i "${kdata2}" --stdio > "${report2}" 426 427 # Strip headers and compare lines with percentages 428 grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true 429 grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true 430 431 # Normalize kernel DSOs and addresses in clean reports 432 # This allows kernel modules to be either a module or kernel.kallsyms 433 local report1_norm="${temp_dir}/report_kernel1.norm" 434 local report2_norm="${temp_dir}/report_kernel2.norm" 435 local diff_file="${temp_dir}/diff_kernel" 436 437 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \ 438 awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \ 439 sort > "${report1_norm}" || true 440 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \ 441 awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \ 442 sort > "${report2_norm}" || true 443 444 diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true 445 446 if [ ! -s "${report1_norm}" ]; then 447 echo "Kernel Report ASLR test [Failed - no samples captured]" 448 err=1 449 elif [ -s "${diff_file}" ]; then 450 echo "Kernel Report ASLR test [Failed - reports differ]" 451 echo "Showing first 20 lines of diff:" 452 head -n 20 "${diff_file}" 453 err=1 454 else 455 echo "Kernel Report ASLR test [Success]" 456 fi 457} 458 459test_regs_stripping() { 460 echo "Test user register stripping" 461 local rdata="${temp_dir}/perf.data.regs" 462 local rdata2="${temp_dir}/perf.data.regs.injected" 463 local rdata_clean="${temp_dir}/perf.data.regs.clean" 464 465 if ! perf record -e cycles:u --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then 466 echo "Skipping user registers test as recording failed (unsupported flag/platform)" 467 return 468 fi 469 470 perf inject -b -i "${rdata}" -o "${rdata_clean}" 471 perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}" 472 473 local report1="${temp_dir}/report_regs1" 474 local report2="${temp_dir}/report_regs2" 475 local report1_clean="${temp_dir}/report_regs1.clean" 476 local report2_clean="${temp_dir}/report_regs2.clean" 477 local diff_file="${temp_dir}/diff_regs" 478 479 perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true 480 perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true 481 482 grep '%' "${report1}" | grep -v '^#' | \ 483 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \ 484 sort > "${report1_clean}" || true 485 grep '%' "${report2}" | grep -v '^#' | \ 486 grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \ 487 sort > "${report2_clean}" || true 488 489 diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true 490 491 if [ ! -s "${report1_clean}" ]; then 492 echo "User registers stripping test [Failed - profile trace starved/empty]" 493 err=1 494 return 495 elif [ -s "${diff_file}" ]; then 496 echo "User registers stripping test [Failed - report parsing differs]" 497 echo "Showing first 20 lines of diff:" 498 head -n 20 "${diff_file}" 499 err=1 500 return 501 fi 502 503 local script_dump="${temp_dir}/script_regs_dump" 504 perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true 505 if grep -q "user regs:" "${script_dump}"; then 506 echo "User registers stripping test [Failed - register dumps still present]" 507 err=1 508 else 509 echo "User registers stripping test [Success]" 510 fi 511} 512 513test_basic_aslr 514test_pipe_aslr 515test_callchain_aslr 516test_report_aslr 517test_pipe_report_aslr 518test_pipe_out_report_aslr 519test_dropped_samples 520case "$(uname -m)" in 521 aarch64*|arm*) 522 echo "Skipping kernel ASLR tests on ARM" 523 ;; 524 *) 525 test_kernel_aslr 526 test_kernel_report_aslr 527 ;; 528esac 529 530test_regs_stripping 531 532cleanup ${err} 533exit $err 534