xref: /linux/tools/perf/tests/shell/inject_aslr.sh (revision 83684c4e4d62cb02b2e4d0d18963d1035439278e)
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