tools/perf/builtin-inject.c | 79 +- tools/perf/tests/shell/inject_aslr.sh | 518 ++++++++++ tools/perf/util/Build | 1 + tools/perf/util/aslr.c | 1269 +++++++++++++++++++++++++ tools/perf/util/aslr.h | 41 + tools/perf/util/machine.c | 32 +- tools/perf/util/maps.c | 80 ++ tools/perf/util/maps.h | 3 + tools/perf/util/symbol-elf.c | 41 +- tools/perf/util/symbol.c | 17 +- 10 files changed, 2048 insertions(+), 33 deletions(-) create mode 100755 tools/perf/tests/shell/inject_aslr.sh create mode 100644 tools/perf/util/aslr.c create mode 100644 tools/perf/util/aslr.h
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space) and 'orig_last_end' (the end address of the last
processed original mapping).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- If the new mapping is contiguous to the previous one in the original
address space (start == orig_last_end), we place it contiguously in
the remapped space. This is critical to preserve the contiguity of
mappings for downstream merging (e.g. symbols split by HugeTLB, or
anonymous .bss segments adjacent to initialized data).
- If not contiguous, we insert a 1-page gap (using page_size) from the
previous maximum allocated address to prevent accidental merging of
unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf test: Add inject ASLR test
perf aslr: Strip sample registers
tools/perf/builtin-inject.c | 79 +-
tools/perf/tests/shell/inject_aslr.sh | 518 ++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1269 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 +
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 80 ++
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
10 files changed, 2048 insertions(+), 33 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space) and 'orig_last_end' (the end address of the last
processed original mapping).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- If the new mapping is contiguous to the previous one in the original
address space (start == orig_last_end), we place it contiguously in
the remapped space. This is critical to preserve the contiguity of
mappings for downstream merging (e.g. symbols split by HugeTLB, or
anonymous .bss segments adjacent to initialized data).
- If not contiguous, we insert a 1-page gap (using page_size) from the
previous maximum allocated address to prevent accidental merging of
unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps() to correctly
accumulate valid maps (skipping NULL entries after failures) and safely return
the exact populated count, resolving out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing by passing
evsel->needs_swap instead of false to __evsel__parse_sample in aslr.c, ensuring
correct 32-bit field byte unswapping for packed fields. Refactored
evsel__parse_sample to take a needs_swap argument via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions to capture
and propagate the correct pipeline failure status code instead of unconditionally
returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf test: Add inject ASLR test
perf aslr: Strip sample registers
tools/perf/builtin-inject.c | 79 +-
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1262 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2095 insertions(+), 65 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space) and 'orig_last_end' (the end address of the last
processed original mapping).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- If the new mapping is contiguous to the previous one in the original
address space (start == orig_last_end), we place it contiguously in
the remapped space. This is critical to preserve the contiguity of
mappings for downstream merging (e.g. symbols split by HugeTLB, or
anonymous .bss segments adjacent to initialized data).
- If not contiguous, we insert a 1-page gap (using page_size) from the
previous maximum allocated address to prevent accidental merging of
unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v11:
- Patch 1: Fixed struct dso name accessor in maps.c by using dso__name() instead of ->name.
- Patch 2: Fixed hash function in aslr.c to hash the underlying dso pointer using RC_CHK_ACCESS to support reference count checking.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps() to correctly
accumulate valid maps (skipping NULL entries after failures) and safely return
the exact populated count, resolving out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing by passing
evsel->needs_swap instead of false to __evsel__parse_sample in aslr.c, ensuring
correct 32-bit field byte unswapping for packed fields. Refactored
evsel__parse_sample to take a needs_swap argument via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions to capture
and propagate the correct pipeline failure status code instead of unconditionally
returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf test: Add inject ASLR test
perf aslr: Strip sample registers
tools/perf/builtin-inject.c | 79 +-
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1262 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2095 insertions(+), 65 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space) and 'orig_last_end' (the end address of the last
processed original mapping).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- If the new mapping is contiguous to the previous one in the original
address space (start == orig_last_end), we place it contiguously in
the remapped space. This is critical to preserve the contiguity of
mappings for downstream merging (e.g. symbols split by HugeTLB, or
anonymous .bss segments adjacent to initialized data).
- If not contiguous, we insert a 1-page gap (using page_size) from the
previous maximum allocated address to prevent accidental merging of
unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v12:
- Patch 2: Fixed potential NULL pointer dereference in
remap_addresses__hash() when handling unmapped memory events (key->dso
is NULL) under REFCNT_CHECKING.
- Patch 2: Dynamically detect machine architecture bitness via
perf_env__kernel_is_64_bit() to select appropriate kernel_space_start
boundaries, avoiding 64-bit address injection on 32-bit platforms.
Changes since v11:
- Patch 1: Fixed struct dso name accessor in maps.c by using
dso__name() instead of ->name.
- Patch 2: Fixed hash function in aslr.c to hash the underlying
dso pointer using RC_CHK_ACCESS to support reference count checking.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps()
to correctly accumulate valid maps (skipping NULL entries after
failures) and safely return the exact populated count, resolving
out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing
by passing evsel->needs_swap instead of false to __evsel__parse_sample
in aslr.c, ensuring correct 32-bit field byte unswapping for packed
fields. Refactored evsel__parse_sample to take a needs_swap argument
via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions
to capture and propagate the correct pipeline failure status code
instead of unconditionally returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf test: Add inject ASLR test
perf aslr: Strip sample registers
tools/perf/builtin-inject.c | 79 +-
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1268 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2101 insertions(+), 65 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space) and 'orig_last_end' (the end address of the last
processed original mapping).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- If the new mapping is contiguous to the previous one in the original
address space (start == orig_last_end), we place it contiguously in
the remapped space. This is critical to preserve the contiguity of
mappings for downstream merging (e.g. symbols split by HugeTLB, or
anonymous .bss segments adjacent to initialized data).
- If not contiguous, we insert a 1-page gap (using page_size) from the
previous maximum allocated address to prevent accidental merging of
unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v13:
- Patch 2: Added a NULL check for env before calling
perf_env__kernel_is_64_bit(env) to prevent potential segfaults if the
recorded environment has no headers.
- Patch 5: Fixed sample_size and id_pos going out of sync during
aslr_tool__strip_evlist() and aslr_tool__restore_evlist(). Instead of
using evsel__reset_sample_bit(), which was acting as a no-op due to
early bit clearing and corrupted sample_size, the tool now directly
updates sample_type and recomputes sample_size/id_pos dynamically.
Added orig_sample_size to aslr_evsel_priv to correctly restore the
state.
Changes since v12:
- Patch 2: Fixed potential NULL pointer dereference in
remap_addresses__hash() when handling unmapped memory events (key->dso
is NULL) under REFCNT_CHECKING.
- Patch 2: Dynamically detect machine architecture bitness via
perf_env__kernel_is_64_bit() to select appropriate kernel_space_start
boundaries, avoiding 64-bit address injection on 32-bit platforms.
Changes since v11:
- Patch 1: Fixed struct dso name accessor in maps.c by using
dso__name() instead of ->name.
- Patch 2: Fixed hash function in aslr.c to hash the underlying
dso pointer using RC_CHK_ACCESS to support reference count checking.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps()
to correctly accumulate valid maps (skipping NULL entries after
failures) and safely return the exact populated count, resolving
out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing
by passing evsel->needs_swap instead of false to __evsel__parse_sample
in aslr.c, ensuring correct 32-bit field byte unswapping for packed
fields. Refactored evsel__parse_sample to take a needs_swap argument
via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions
to capture and propagate the correct pipeline failure status code
instead of unconditionally returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf test: Add inject ASLR test
perf aslr: Strip sample registers
tools/perf/builtin-inject.c | 79 +-
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1272 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2105 insertions(+), 65 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- We use thread__find_map to look up the mapping immediately preceding
the new one in the original address space (at start - 1). If the preceding
mapping was also remapped, we place the new mapping contiguously after it in
the remapped space. This preserves contiguity of split mappings (e.g., symbols
split by HugeTLB, or anonymous .bss segments adjacent to initialized data).
- If no contiguous mapping is found, we insert a 1-page gap from the
highest allocated address (remapped_max) to prevent accidental merging of
unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v14:
- Patch 2: Removed unnecessary vertical whitespace in builtin-inject.c.
- Patch 2: Added comments explaining why pgoff is assigned for
anonymous memory maps to prevent ASLR leaks.
- Patch 2: Removed orig_last_end tracking and refactored contiguous mapping
detection to use thread__find_map(..., start - 1, ...) based on Gabriel's
feedback.
- Patch 2: Scrub kprobe/uprobe event config1 and config2 fields to prevent
address leaks.
- Patch 2: Overwrite pgoff with the remapped start address for anonymous
mappings (detected via is_anon_memory and is_no_dso_memory).
- Patch 3: Fix C90 mixed declaration error for orig_needs_swap.
- Patch 3: Temporarily disable evsel->needs_swap during the secondary
evsel__parse_sample() call to prevent branch stack double-swapping bugs.
Changes since v13:
- Patch 2: Added a NULL check for env before calling
perf_env__kernel_is_64_bit(env) to prevent potential segfaults if the
recorded environment has no headers.
- Patch 5: Fixed sample_size and id_pos going out of sync during
aslr_tool__strip_evlist() and aslr_tool__restore_evlist(). Instead of
using evsel__reset_sample_bit(), which was acting as a no-op due to
early bit clearing and corrupted sample_size, the tool now directly
updates sample_type and recomputes sample_size/id_pos dynamically.
Added orig_sample_size to aslr_evsel_priv to correctly restore the
state.
Changes since v12:
- Patch 2: Fixed potential NULL pointer dereference in
remap_addresses__hash() when handling unmapped memory events (key->dso
is NULL) under REFCNT_CHECKING.
- Patch 2: Dynamically detect machine architecture bitness via
perf_env__kernel_is_64_bit() to select appropriate kernel_space_start
boundaries, avoiding 64-bit address injection on 32-bit platforms.
Changes since v11:
- Patch 1: Fixed struct dso name accessor in maps.c by using
dso__name() instead of ->name.
- Patch 2: Fixed hash function in aslr.c to hash the underlying
dso pointer using RC_CHK_ACCESS to support reference count checking.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps()
to correctly accumulate valid maps (skipping NULL entries after
failures) and safely return the exact populated count, resolving
out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing
by passing evsel->needs_swap instead of false to __evsel__parse_sample
in aslr.c, ensuring correct 32-bit field byte unswapping for packed
fields. Refactored evsel__parse_sample to take a needs_swap argument
via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions
to capture and propagate the correct pipeline failure status code
instead of unconditionally returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf test: Add inject ASLR test
perf aslr: Strip sample registers
tools/perf/builtin-inject.c | 96 +-
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1299 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2149 insertions(+), 65 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- We use thread__find_map to look up the mapping immediately preceding
the new one in the original address space (at start - 1). If the preceding
mapping was also remapped, we place the new mapping contiguously after it in
the remapped space. This preserves contiguity of split mappings (e.g., symbols
split by HugeTLB, or anonymous .bss segments adjacent to initialized data).
- If no contiguous mapping is found, we insert a 1-page gap from the
highest allocated address (remapped_max) to prevent accidental merging of
unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v15:
- Patch 2: Added bounds checking for event->header.size before writing
to breakpoint fields to avoid heap buffer overflow on older ABI events.
- Patch 2: Fixed asymmetric calculation bug in aslr_tool__findnew_mapping()
where pgoff for anonymous kernel memory was not properly subtracted upon
insertion, causing the lookup addition to overflow.
- Patch 2: Added detailed comments documenting the symmetric lookup and
insertion math for unmapped and mapped memory blocks.
- Patch 5: Add missing kprobe and uprobe scrubbing of config1 and config2
during aslr_tool__strip_evlist() to strictly conform with repipe constraints.
Changes since v14:
- Patch 2: Removed unnecessary vertical whitespace in builtin-inject.c.
- Patch 2: Added comments explaining why pgoff is assigned for
anonymous memory maps to prevent ASLR leaks.
- Patch 2: Removed orig_last_end tracking and refactored contiguous mapping
detection to use thread__find_map(..., start - 1, ...) based on Gabriel's
feedback.
- Patch 2: Scrub kprobe/uprobe event config1 and config2 fields to prevent
address leaks.
- Patch 2: Overwrite pgoff with the remapped start address for anonymous
mappings (detected via is_anon_memory and is_no_dso_memory).
- Patch 3: Fix C90 mixed declaration error for orig_needs_swap.
- Patch 3: Temporarily disable evsel->needs_swap during the secondary
evsel__parse_sample() call to prevent branch stack double-swapping bugs.
Changes since v13:
- Patch 2: Added a NULL check for env before calling
perf_env__kernel_is_64_bit(env) to prevent potential segfaults if the
recorded environment has no headers.
- Patch 5: Fixed sample_size and id_pos going out of sync during
aslr_tool__strip_evlist() and aslr_tool__restore_evlist(). Instead of
using evsel__reset_sample_bit(), which was acting as a no-op due to
early bit clearing and corrupted sample_size, the tool now directly
updates sample_type and recomputes sample_size/id_pos dynamically.
Added orig_sample_size to aslr_evsel_priv to correctly restore the
state.
Changes since v12:
- Patch 2: Fixed potential NULL pointer dereference in
remap_addresses__hash() when handling unmapped memory events (key->dso
is NULL) under REFCNT_CHECKING.
- Patch 2: Dynamically detect machine architecture bitness via
perf_env__kernel_is_64_bit() to select appropriate kernel_space_start
boundaries, avoiding 64-bit address injection on 32-bit platforms.
Changes since v11:
- Patch 1: Fixed struct dso name accessor in maps.c by using
dso__name() instead of ->name.
- Patch 2: Fixed hash function in aslr.c to hash the underlying
dso pointer using RC_CHK_ACCESS to support reference count checking.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps()
to correctly accumulate valid maps (skipping NULL entries after
failures) and safely return the exact populated count, resolving
out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing
by passing evsel->needs_swap instead of false to __evsel__parse_sample
in aslr.c, ensuring correct 32-bit field byte unswapping for packed
fields. Refactored evsel__parse_sample to take a needs_swap argument
via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions
to capture and propagate the correct pipeline failure status code
instead of unconditionally returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf test: Add inject ASLR test
perf aslr: Strip sample registers
tools/perf/builtin-inject.c | 104 +-
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1322 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2180 insertions(+), 65 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- We use thread__find_map to look up the mapping immediately preceding
the new one in the original address space (at start - 1). If
the preceding
mapping was also remapped, we place the new mapping
contiguously after it in the remapped space. This preserves
contiguity of split mappings (e.g., symbols split by HugeTLB,
or anonymous .bss segments adjacent to initialized data).
- If no contiguous mapping is found, we insert a 1-page gap from
the highest allocated address (remapped_max) to prevent accidental
merging of unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v16:
- Patch 2: Refactored inline ASLR stripping logic out of builtin-inject.c
and into dedicated helpers (aslr_tool__strip_attr_event and
aslr_tool__strip_evlist) in aslr.c to better separate concerns.
- Patch 2: Fixed guest machine allocation memory leak in
aslr_tool__delete() where machines__exit() explicitly skipped freeing
the guest processes tree.
- Patch 3: Fixed bounds-check violations during cross-endian parsing inside
aslr_tool__process_sample() by correctly applying bswap_64() to raw
offsets, iteration counts, sizes, and addresses prior to logical
evaluation when orig_needs_swap is active.
- Patch 4: Fixed pipe mode parser misalignment bug by safely fetching
needs_swap from the initialized evsel rather than blindly intercepting
HEADER_ATTR events prior to session parsing.
- Patch 4: Resolved checkpatch.pl line length warnings in the bswap_64
endianness swapping logic.
- Patch Series: Reordered the final two patches. "perf aslr: Strip
sample registers" is now Patch 4, and "perf test: Add inject ASLR
test" is now Patch 5. This ensures the register stripping logic
is fully introduced before the comprehensive shell tests validate it,
preventing bisectability test failures and easing merge conflicts.
- Patch 5: Fixed "User registers stripping test" starvation when run as
root by explicitly using '-e cycles:u' during recording, preventing
the ring buffer from overflowing with kernel samples.
Changes since v15:
- Patch 2: Added bounds checking for event->header.size before writing
to breakpoint fields to avoid heap buffer overflow on older ABI events.
- Patch 2: Fixed asymmetric calculation bug in aslr_tool__findnew_mapping()
where pgoff for anonymous kernel memory was not properly subtracted upon
insertion, causing the lookup addition to overflow.
- Patch 2: Added detailed comments documenting the symmetric lookup and
insertion math for unmapped and mapped memory blocks.
- Patch 5: Add missing kprobe and uprobe scrubbing of config1 and
config2 during aslr_tool__strip_evlist() to strictly conform with
repipe constraints.
Changes since v14:
- Patch 2: Removed unnecessary vertical whitespace in builtin-inject.c.
- Patch 2: Added comments explaining why pgoff is assigned for
anonymous memory maps to prevent ASLR leaks.
- Patch 2: Removed orig_last_end tracking and refactored contiguous mapping
detection to use thread__find_map(..., start - 1, ...) based on Gabriel's
feedback.
- Patch 2: Scrub kprobe/uprobe event config1 and config2 fields to prevent
address leaks.
- Patch 2: Overwrite pgoff with the remapped start address for anonymous
mappings (detected via is_anon_memory and is_no_dso_memory).
- Patch 3: Fix C90 mixed declaration error for orig_needs_swap.
- Patch 3: Temporarily disable evsel->needs_swap during the secondary
evsel__parse_sample() call to prevent branch stack double-swapping bugs.
Changes since v13:
- Patch 2: Added a NULL check for env before calling
perf_env__kernel_is_64_bit(env) to prevent potential segfaults if the
recorded environment has no headers.
- Patch 5: Fixed sample_size and id_pos going out of sync during
aslr_tool__strip_evlist() and aslr_tool__restore_evlist(). Instead of
using evsel__reset_sample_bit(), which was acting as a no-op due to
early bit clearing and corrupted sample_size, the tool now directly
updates sample_type and recomputes sample_size/id_pos dynamically.
Added orig_sample_size to aslr_evsel_priv to correctly restore the
state.
Changes since v12:
- Patch 2: Fixed potential NULL pointer dereference in
remap_addresses__hash() when handling unmapped memory events (key->dso
is NULL) under REFCNT_CHECKING.
- Patch 2: Dynamically detect machine architecture bitness via
perf_env__kernel_is_64_bit() to select appropriate kernel_space_start
boundaries, avoiding 64-bit address injection on 32-bit platforms.
Changes since v11:
- Patch 1: Fixed struct dso name accessor in maps.c by using
dso__name() instead of ->name.
- Patch 2: Fixed hash function in aslr.c to hash the underlying
dso pointer using RC_CHK_ACCESS to support reference count checking.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps()
to correctly accumulate valid maps (skipping NULL entries after
failures) and safely return the exact populated count, resolving
out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing
by passing evsel->needs_swap instead of false to __evsel__parse_sample
in aslr.c, ensuring correct 32-bit field byte unswapping for packed
fields. Refactored evsel__parse_sample to take a needs_swap argument
via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions
to capture and propagate the correct pipeline failure status code
instead of unconditionally returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf aslr: Strip sample registers
perf test: Add inject ASLR test
tools/perf/builtin-inject.c | 50 +-
tools/perf/tests/shell/inject_aslr.sh | 519 +++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1394 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 44 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2202 insertions(+), 64 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
This patch series introduces the new 'perf inject --aslr' feature to
remap virtual memory addresses or drop physical memory event leaks
when profile record data is shared between machines. Bundled with this
feature is a bug fix inside the core map tracking tool that hardens
perf session analysis against concurrent lookup data races.
Detailed Mechanism of MMAP Mapping and ASLR virtual Address Allocation:
The ASLR tool virtualizes the address space of the recorded processes by
intercepting MMAP and MMAP2 events to build a consistent translation
database, which is subsequently used to rewrite sample addresses.
It maintains two primary lookup databases using hash maps:
1. 'remap_addresses': Maps an original mapping key to its new remapped
base address. The key uses topological invariant coordinates:
(machine, dso, invariant). The invariant is computed as (start - pgoff)
for DSO-backed mappings. This invariant remains constant even when
perf's internal overlap-resolution splits a VMA into fragmented
pieces, ensuring split maps resolve consistently back to the same
remapped base.
2. 'top_addresses': Tracks the allocation state per process (machine, pid).
It maintains 'remapped_max' (the highest allocated address in the
virtualized space).
For each MMAP/MMAP2 event:
- We look up the DSO and invariant key in 'remap_addresses'. If found, we
reuse the translation, preserving the offset within the mapping.
- If not found, we allocate a new remapped address space:
- We use thread__find_map to look up the mapping immediately preceding
the new one in the original address space (at start - 1). If
the preceding
mapping was also remapped, we place the new mapping
contiguously after it in the remapped space. This preserves
contiguity of split mappings (e.g., symbols split by HugeTLB,
or anonymous .bss segments adjacent to initialized data).
- If no contiguous mapping is found, we insert a 1-page gap from
the highest allocated address (remapped_max) to prevent accidental
merging of unrelated VMAs.
- The event's start address (and pgoff for kernel maps) is rewritten,
and the event is delegated to the output writer.
To remain strictly conservative and guarantee security, the tool
scrubs breakpoint addresses (bp_addr) from all synthesized stream
headers, completely drops PERF_RECORD_TEXT_POKE events to prevent
absolute immediate pointer operands leaks, and drops unsupported
complex payloads (such as user register stacks, raw tracepoints, and
hardware AUX tracing frames).
Verification is reinforced with shell test ('inject_aslr.sh').
Prerequisite Bug Fix (Patch 1). During development, a core map
indexing issue was identified and resolved to prevent concurrent
lookup data races during session analysis.
Changes since v17:
- Patch 2: Reordered ksymbol deletion logic to ensure
`perf_event__process_ksymbol` deletes the map *after* the
`aslr_tool__findnew_mapping` translates the unregister offsets.
- Patch 2: Changed `aslr_tool__delete` to cleanly handle guest machine
deletion memory leaks.
- Patch 2: Resolved read-only segfaults on memory-mapped perf.data
headers during attribute stripping by using deep copies in
`perf_event__repipe_attr`.
- Patch 2: Fixed user space remap invariant logic to include
`(start - map__start(al.map))` preventing negative overflows on module
offset boundaries.
- Patch 3: Removed duplicate `bswap_64` payload byte-swapping inside the
array logic, allowing the host endianness macros `COPY_U64()` to
handle it dynamically.
- Patch 3: Fixed LBR branch sample starvation by explicitly reading branch
counters instead of dropping the entire sample.
- Patch 5: Fixed test flakiness by grepping out physical hex addresses
`0x[0-9a-f]{8,}` instead of matching exact address strings.
- Patch 5: Parameterized temp reports and updated test to scale with
`/dev/urandom` continuous random reads.
- Patch Series: Added Signed-off-by tags uniformly and Assisted-by tags to
track assistance.
Changes since v16:
- Patch 2: Refactored inline ASLR stripping logic out of builtin-inject.c
and into dedicated helpers (aslr_tool__strip_attr_event and
aslr_tool__strip_evlist) in aslr.c to better separate concerns.
- Patch 2: Fixed guest machine allocation memory leak in
aslr_tool__delete() where machines__exit() explicitly skipped freeing
the guest processes tree.
- Patch 3: Fixed bounds-check violations during cross-endian parsing inside
aslr_tool__process_sample() by correctly applying bswap_64() to raw
offsets, iteration counts, sizes, and addresses prior to logical
evaluation when orig_needs_swap is active.
- Patch 4: Fixed pipe mode parser misalignment bug by safely fetching
needs_swap from the initialized evsel rather than blindly intercepting
HEADER_ATTR events prior to session parsing.
- Patch 4: Resolved checkpatch.pl line length warnings in the bswap_64
endianness swapping logic.
- Patch Series: Reordered the final two patches. "perf aslr: Strip
sample registers" is now Patch 4, and "perf test: Add inject ASLR
test" is now Patch 5. This ensures the register stripping logic
is fully introduced before the comprehensive shell tests validate it,
preventing bisectability test failures and easing merge conflicts.
- Patch 5: Fixed "User registers stripping test" starvation when run as
root by explicitly using '-e cycles:u' during recording, preventing
the ring buffer from overflowing with kernel samples.
Changes since v15:
- Patch 2: Added bounds checking for event->header.size before writing
to breakpoint fields to avoid heap buffer overflow on older ABI events.
- Patch 2: Fixed asymmetric calculation bug in aslr_tool__findnew_mapping()
where pgoff for anonymous kernel memory was not properly subtracted upon
insertion, causing the lookup addition to overflow.
- Patch 2: Added detailed comments documenting the symmetric lookup and
insertion math for unmapped and mapped memory blocks.
- Patch 5: Add missing kprobe and uprobe scrubbing of config1 and
config2 during aslr_tool__strip_evlist() to strictly conform with
repipe constraints.
Changes since v14:
- Patch 2: Removed unnecessary vertical whitespace in builtin-inject.c.
- Patch 2: Added comments explaining why pgoff is assigned for
anonymous memory maps to prevent ASLR leaks.
- Patch 2: Removed orig_last_end tracking and refactored contiguous mapping
detection to use thread__find_map(..., start - 1, ...) based on Gabriel's
feedback.
- Patch 2: Scrub kprobe/uprobe event config1 and config2 fields to prevent
address leaks.
- Patch 2: Overwrite pgoff with the remapped start address for anonymous
mappings (detected via is_anon_memory and is_no_dso_memory).
- Patch 3: Fix C90 mixed declaration error for orig_needs_swap.
- Patch 3: Temporarily disable evsel->needs_swap during the secondary
evsel__parse_sample() call to prevent branch stack double-swapping bugs.
Changes since v13:
- Patch 2: Added a NULL check for env before calling
perf_env__kernel_is_64_bit(env) to prevent potential segfaults if the
recorded environment has no headers.
- Patch 5: Fixed sample_size and id_pos going out of sync during
aslr_tool__strip_evlist() and aslr_tool__restore_evlist(). Instead of
using evsel__reset_sample_bit(), which was acting as a no-op due to
early bit clearing and corrupted sample_size, the tool now directly
updates sample_type and recomputes sample_size/id_pos dynamically.
Added orig_sample_size to aslr_evsel_priv to correctly restore the
state.
Changes since v12:
- Patch 2: Fixed potential NULL pointer dereference in
remap_addresses__hash() when handling unmapped memory events (key->dso
is NULL) under REFCNT_CHECKING.
- Patch 2: Dynamically detect machine architecture bitness via
perf_env__kernel_is_64_bit() to select appropriate kernel_space_start
boundaries, avoiding 64-bit address injection on 32-bit platforms.
Changes since v11:
- Patch 1: Fixed struct dso name accessor in maps.c by using
dso__name() instead of ->name.
- Patch 2: Fixed hash function in aslr.c to hash the underlying
dso pointer using RC_CHK_ACCESS to support reference count checking.
Changes since v10:
- Patch 1: Added explicit tracking array logic in maps__load_maps()
to correctly accumulate valid maps (skipping NULL entries after
failures) and safely return the exact populated count, resolving
out-of-bounds pointer iteration panics.
- Patch 3: Fixed endianness bug during cross-endian sample parsing
by passing evsel->needs_swap instead of false to __evsel__parse_sample
in aslr.c, ensuring correct 32-bit field byte unswapping for packed
fields. Refactored evsel__parse_sample to take a needs_swap argument
via __evsel__parse_sample.
- Patch 4: Fixed inject_aslr.sh exit code handling in trap functions
to capture and propagate the correct pipeline failure status code
instead of unconditionally returning success or failing the test.
Changes since v9:
- Patch 1: Added `-ENOMEM` error check inside
`maps__find_symbol_by_name()` and return `NULL` early. Added map
sorting state invalidation on early return in `maps__load_maps()`.
- Patch 2: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__findnew_mapping()`. Added
`pr_warning_once` warning when raw auxtrace data is dropped.
- Patch 3: Fixed encapsulation by using `thread__maps()` and
`thread__pid()` accessors in `aslr_tool__remap_address()`. Wrapped
`evsel__parse_sample()` to temporarily disable `needs_swap` to avoid
branch stack endianness corruption on cross-endian files. Fixed ISO
C90 warning for declaration-after-statement for `orig_needs_swap`.
- Patch 4: Fixed duplicate cleanup by explicitly removing trap
handlers (`trap - EXIT TERM INT`) inside the `cleanup()` function.
- Patch 5: Fixed heap corruption by adding size bounds checking before
writing to `sample_regs_user` and `sample_regs_intr` fields. Added
missing register mask clearing logic for the `itrace` synthesis path
of `perf_event__repipe_attr()`.
Ian Rogers (5):
perf maps: Add maps__mutate_mapping
perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking
perf inject/aslr: Implement sample address remapping
perf aslr: Strip sample registers
perf test: Add inject ASLR test
tools/perf/builtin-inject.c | 81 +-
tools/perf/tests/shell/inject_aslr.sh | 519 +++++++++
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 1398 +++++++++++++++++++++++++
tools/perf/util/aslr.h | 44 +
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
tools/perf/util/machine.c | 32 +-
tools/perf/util/maps.c | 149 ++-
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 +-
tools/perf/util/symbol.c | 17 +-
12 files changed, 2230 insertions(+), 71 deletions(-)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..b1b8efe42149 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/builtin-inject.c | 61 ++-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 825 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 ++
4 files changed, 919 insertions(+), 9 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..8bb37095e2de 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -24,6 +25,7 @@
#include "util/string2.h"
#include "util/symbol.h"
#include "util/synthetic-events.h"
+#include "util/pmus.h"
#include "util/thread.h"
#include "util/namespaces.h"
#include "util/unwind.h"
@@ -124,6 +126,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -234,20 +237,36 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
u64 *ids;
int ret;
+ union perf_event *aslr_event = NULL;
+
ret = perf_event__process_attr(tool, event, pevlist);
if (ret)
return ret;
+ if (inject->aslr) {
+ aslr_event = malloc(event->header.size);
+ if (!aslr_event)
+ return -ENOMEM;
+ memcpy(aslr_event, event, event->header.size);
+ aslr_tool__strip_attr_event(aslr_event, pevlist);
+ event = aslr_event;
+ }
+
/* If the output isn't a pipe then the attributes will be written as part of the header. */
- if (!inject->output.is_pipe)
- return 0;
+ if (!inject->output.is_pipe) {
+ ret = 0;
+ goto out;
+ }
- if (!inject->itrace_synth_opts.set)
- return perf_event__repipe_synth(tool, event);
+ if (!inject->itrace_synth_opts.set) {
+ ret = perf_event__repipe_synth(tool, event);
+ goto out;
+ }
if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZE_VER0) {
pr_err("Attribute event size %u is too small\n", event->header.size);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
/*
@@ -263,7 +282,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
raw_attr_size > event->header.size - sizeof(event->header))) {
pr_err("Attribute event size %u is too small for attr.size %u\n",
event->header.size, raw_attr_size);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
memset(&attr, 0, sizeof(attr));
@@ -281,8 +301,11 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
attr.branch_sample_type |= PERF_SAMPLE_BRANCH_HW_INDEX;
}
- return perf_event__synthesize_attr(tool, &attr, (u32)n_ids, ids,
+ ret = perf_event__synthesize_attr(tool, &attr, (u32)n_ids, ids,
perf_event__repipe_synth_cb);
+out:
+ free(aslr_event);
+ return ret;
}
static int perf_event__repipe_event_update(const struct perf_tool *tool,
@@ -2594,7 +2617,6 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
-
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2726,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2735,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2756,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2854,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2922,6 +2961,8 @@ int cmd_inject(int argc, const char **argv)
goto out_delete;
ret = __cmd_inject(&inject);
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
guest_session__exit(&inject.guest_session);
@@ -2929,6 +2970,8 @@ int cmd_inject(int argc, const char **argv)
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..e45f68c60493
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,825 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "evlist.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+#include "pmus.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start_64 = 0xffff800010000000ULL;
+static const u64 kernel_space_start_32 = 0x80000000ULL;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+ void *dso_ptr = key->dso ? RC_CHK_ACCESS(key->dso) : NULL;
+
+ return (size_t)key->machine ^ (size_t)dso_ptr ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct machine *session_machine,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ /*
+ * The cached value is the base of the invariant. We add the
+ * offset into the VMA (start - map__start), plus the map's
+ * pgoff, to get the precise virtual address within this chunk.
+ */
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ /*
+ * For unmapped memory (e.g. kernel anonymous), the cached value
+ * was stored offset by pgoff. Adding pgoff yields the true remap_addr.
+ */
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ struct addr_location prev_al;
+ bool is_contiguous = false;
+
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ addr_location__init(&prev_al);
+ if (thread__find_map(aslr_thread, cpumode, start - 1, &prev_al)) {
+ if (map__end(prev_al.map) == start)
+ is_contiguous = true;
+ }
+ addr_location__exit(&prev_al);
+
+ if (is_contiguous) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+ struct perf_env *env = session_machine ? session_machine->env : NULL;
+ bool is_64 = env ? perf_env__kernel_is_64_bit(env) : (sizeof(void *) == 8);
+ u64 kernel_start_addr = is_64 ? kernel_space_start_64 : kernel_space_start_32;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_start_addr : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ /*
+ * Subtract pgoff from the base virtual address so that
+ * when the lookup path adds pgoff back, it perfectly
+ * cancels out and returns remap_addr.
+ */
+ *new_remap_val = remap_addr - pgoff;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? (start - map__start(al.map)) + map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, machine, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap.filename) || is_no_dso_memory(event->mmap.filename))
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, machine, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap2.filename) || is_no_dso_memory(event->mmap2.filename))
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start before process_ksymbol potentially deletes the map */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, machine, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err) {
+ thread__put(thread);
+ return err;
+ }
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist)
+{
+ struct evsel *evsel;
+ bool needs_swap = false;
+
+ if (pevlist && *pevlist) {
+ evsel = evlist__last(*pevlist);
+ if (evsel)
+ needs_swap = evsel->needs_swap;
+ }
+
+ if (event->header.size >= (offsetof(struct perf_record_header_attr,
+ attr.sample_type) + sizeof(u64))) {
+ u64 st = event->attr.attr.sample_type;
+
+ if (needs_swap)
+ st = bswap_64(st);
+
+ st &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (needs_swap)
+ st = bswap_64(st);
+
+ event->attr.attr.sample_type = st;
+ }
+
+ if (event->header.size >= (offsetof(struct perf_record_header_attr,
+ attr.type) + sizeof(u32))) {
+ u32 type = event->attr.attr.type;
+
+ if (needs_swap)
+ type = bswap_32(type);
+
+ if (type == PERF_TYPE_BREAKPOINT &&
+ event->header.size >= (offsetof(struct perf_record_header_attr,
+ attr.bp_addr) + sizeof(u64))) {
+ event->attr.attr.bp_addr = 0;
+ } else if (type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu;
+
+ pmu = perf_pmus__find_by_type(type);
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr,
+ attr.config1) + sizeof(u64)))
+ event->attr.attr.config1 = 0;
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr,
+ attr.config2) + sizeof(u64)))
+ event->attr.attr.config2 = 0;
+ }
+ }
+ }
+}
+
+void aslr_tool__strip_evlist(struct perf_tool *tool __maybe_unused,
+ struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
+
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ evsel->core.attr.config1 = 0;
+ evsel->core.attr.config2 = 0;
+ }
+ }
+ }
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+ struct rb_node *nd;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+
+ while ((nd = rb_first_cached(&aslr->machines.guests)) != NULL) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ rb_erase_cached(nd, &aslr->machines.guests);
+ machine__delete(machine);
+ }
+
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..2b82f711bc67
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+struct evlist;
+union perf_event;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist);
+void aslr_tool__strip_evlist(struct perf_tool *tool, struct evlist *evlist);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/util/aslr.c | 463 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 470 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index e45f68c60493..03944677ddfc 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -20,6 +20,7 @@
#include <linux/zalloc.h>
#include <inttypes.h>
#include <unistd.h>
+#include <byteswap.h>
/**
* struct remap_addresses_key - Key for mapping original addresses to remapped ones.
@@ -112,6 +113,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -602,13 +657,413 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+ bool orig_needs_swap;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ orig_needs_swap = evsel->needs_swap;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
+
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ u64 remapped; \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ remapped = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ if (orig_needs_swap) \
+ remapped = bswap_64(remapped); \
+ out_array[j++] = remapped; \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ nr = in_array[i];
+ COPY_U64();
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ nr = in_array[i];
+ COPY_U64();
- return delegate->sample(delegate, event, sample, machine);
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = orig_needs_swap ? bswap_64(addr) : addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ addr = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ if (orig_needs_swap)
+ addr = bswap_64(addr);
+ out_array[j++] = addr;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ nr = in_array[i];
+ COPY_U64();
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ u64 from = in_array[i++];
+ u64 to = in_array[i++];
+
+ if (orig_needs_swap) {
+ from = bswap_64(from);
+ to = bswap_64(to);
+ }
+
+ from = aslr_tool__remap_address(aslr, thread, sample->cpumode, from);
+ to = aslr_tool__remap_address(aslr, thread, sample->cpumode, to);
+
+ if (orig_needs_swap) {
+ from = bswap_64(from);
+ to = bswap_64(to);
+ }
+
+ out_array[j++] = from;
+ out_array[j++] = to;
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++)
+ COPY_U64();
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ size = in_array[i];
+ COPY_U64();
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = __evsel__parse_sample(evsel, new_event, &new_sample, orig_needs_swap);
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Extend the ASLR tool stripping helpers to drop register dump payloads
by masking out the relevant perf_event_attr fields (sample_regs_user,
sample_regs_intr) when the delegated tool is handling the data.
struct aslr_evsel_priv maintains the original perf_event_attr values
and is looked up via the evsel_orig_attrs hashmap so that sample sizes
can be properly parsed even when bits are stripped from the pipeline.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/builtin-inject.c | 28 +++-
tools/perf/util/aslr.c | 292 +++++++++++++++++++++++++-----------
tools/perf/util/aslr.h | 9 +-
3 files changed, 236 insertions(+), 93 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 8bb37095e2de..6d6cce4765a7 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -248,7 +248,7 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (!aslr_event)
return -ENOMEM;
memcpy(aslr_event, event, event->header.size);
- aslr_tool__strip_attr_event(aslr_event, pevlist);
+ aslr_tool__strip_attr_event(aslr_event, *pevlist);
event = aslr_event;
}
@@ -297,6 +297,7 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
attr.branch_sample_type |= PERF_SAMPLE_BRANCH_HW_INDEX;
@@ -2617,6 +2618,10 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
+
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2875,6 +2880,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2893,10 +2910,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2961,8 +2985,6 @@ int cmd_inject(int argc, const char **argv)
goto out_delete;
ret = __cmd_inject(&inject);
- if (inject.aslr)
- aslr_tool__strip_evlist(tool, inject.session->evlist);
guest_session__exit(&inject.guest_session);
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 03944677ddfc..912efd111bb3 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -18,6 +18,7 @@
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
#include <byteswap.h>
@@ -46,6 +47,23 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+ int orig_sample_size;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
};
@@ -60,6 +78,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -123,6 +146,8 @@ static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
u64 *remapped_invariant_ptr = NULL;
u64 remap_addr = 0;
u8 effective_cpumode = cpumode;
+ struct dso *dso;
+ const char *dso_name;
if (!aslr_thread)
return 0; /* No thread. */
@@ -148,9 +173,15 @@ static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
}
}
+ dso = map__dso(al.map);
+ dso_name = dso ? dso__long_name(dso) : NULL;
+
key.machine = maps__machine(thread__maps(aslr_thread));
- key.dso = map__dso(al.map);
- key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ key.invariant = map__start(al.map);
key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
kernel_pid : thread__pid(aslr_thread);
@@ -662,6 +693,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -674,6 +706,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
bool orig_needs_swap;
del_tool = container_of(tool, struct delegate_tool, tool);
@@ -686,7 +722,24 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+ sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -739,25 +792,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -790,7 +843,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -858,7 +911,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = addr;
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -877,7 +930,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -928,19 +981,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
COPY_U64();
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -970,39 +1029,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -1042,11 +1107,20 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
-
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
perf_sample__init(&new_sample, /*all=*/ true);
ret = __evsel__parse_sample(evsel, new_event, &new_sample, orig_needs_swap);
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -1055,6 +1129,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1108,43 +1188,30 @@ static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __mayb
return 0;
}
-
-void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist)
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *evlist)
{
- struct evsel *evsel;
- bool needs_swap = false;
-
- if (pevlist && *pevlist) {
- evsel = evlist__last(*pevlist);
- if (evsel)
- needs_swap = evsel->needs_swap;
- }
+ if (!evlist)
+ return;
if (event->header.size >= (offsetof(struct perf_record_header_attr,
attr.sample_type) + sizeof(u64))) {
- u64 st = event->attr.attr.sample_type;
-
- if (needs_swap)
- st = bswap_64(st);
-
- st &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (needs_swap)
- st = bswap_64(st);
-
- event->attr.attr.sample_type = st;
+ event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr, attr.sample_regs_user) + sizeof(u64)))
+ event->attr.attr.sample_regs_user = 0;
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr, attr.sample_regs_intr) + sizeof(u64)))
+ event->attr.attr.sample_regs_intr = 0;
}
if (event->header.size >= (offsetof(struct perf_record_header_attr,
attr.type) + sizeof(u32))) {
u32 type = event->attr.attr.type;
- if (needs_swap)
- type = bswap_32(type);
-
if (type == PERF_TYPE_BREAKPOINT &&
event->header.size >= (offsetof(struct perf_record_header_attr,
- attr.bp_addr) + sizeof(u64))) {
+ attr.bp_addr) + sizeof(u64))) {
event->attr.attr.bp_addr = 0;
} else if (type >= PERF_TYPE_MAX) {
struct perf_pmu *pmu;
@@ -1165,28 +1232,6 @@ void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlis
}
}
-void aslr_tool__strip_evlist(struct perf_tool *tool __maybe_unused,
- struct evlist *evlist)
-{
- struct evsel *evsel;
-
- evlist__for_each_entry(evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
- struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
-
- if (pmu && (!strcmp(pmu->name, "kprobe") ||
- !strcmp(pmu->name, "uprobe"))) {
- evsel->core.attr.config1 = 0;
- evsel->core.attr.config2 = 0;
- }
- }
- }
-}
-
static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
{
delegate_tool__init(&aslr->tool, delegate);
@@ -1200,6 +1245,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1262,9 +1310,13 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
@@ -1278,3 +1330,69 @@ void aslr_tool__delete(struct perf_tool *tool)
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+ priv->orig_sample_size = evsel->sample_size;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+ evsel->sample_size = __evsel__sample_size(evsel->core.attr.sample_type);
+ evsel__calc_id_pos(evsel);
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT) {
+ evsel->core.attr.bp_addr = 0;
+ } else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
+
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ evsel->core.attr.config1 = 0;
+ evsel->core.attr.config2 = 0;
+ }
+ }
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ evsel->sample_size = priv->orig_sample_size;
+ evsel__calc_id_pos(evsel);
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index 2b82f711bc67..522e31c8e2c0 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -34,8 +34,11 @@ struct evlist;
union perf_event;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
-void aslr_tool__delete(struct perf_tool *aslr);
-void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist);
-void aslr_tool__strip_evlist(struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__delete(struct perf_tool *tool);
+
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *evlist);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into.
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload to guarantee continuous timer interrupts sampling flow
inside kernel privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions
to catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Note on kernel DSO normalization in the test script:
The test script deliberately normalizes all kernel DSOs to a generic
[kernel] tag before diffing, as obfuscating physical kernel addresses
forces perf report to occasionally shift samples between individual
modules and [kernel.kallsyms] due to the lack of valid host module
boundary maps.
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++++++++++++++++++
1 file changed, 519 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..2e469f83675e
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,519 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/urandom of=/dev/null bs=1M count=50"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1_basic"
+ local report2="${temp_dir}/report2_basic"
+ local report1_clean="${temp_dir}/report1_basic.clean"
+ local report2_clean="${temp_dir}/report2_basic.clean"
+ local diff_file="${temp_dir}/diff_basic"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1_pipe"
+ local report2="${temp_dir}/report2_pipe"
+ local report1_clean="${temp_dir}/report1_pipe.clean"
+ local report2_clean="${temp_dir}/report2_pipe.clean"
+ local diff_file="${temp_dir}/diff_pipe"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1_pipe_out"
+ local report2="${temp_dir}/report2_pipe_out"
+ local report1_clean="${temp_dir}/report1_pipe_out.clean"
+ local report2_clean="${temp_dir}/report2_pipe_out.clean"
+ local diff_file="${temp_dir}/diff_pipe_out"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record -e cycles:u --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "user regs:" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+test_regs_stripping
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..b1b8efe42149 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/builtin-inject.c | 29 +-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 822 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 41 ++
4 files changed, 891 insertions(+), 2 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..6cc9c6dbf608 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -24,6 +25,7 @@
#include "util/string2.h"
#include "util/symbol.h"
#include "util/synthetic-events.h"
+#include "util/pmus.h"
#include "util/thread.h"
#include "util/namespaces.h"
#include "util/unwind.h"
@@ -124,6 +126,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -276,6 +279,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+ if (inject->aslr)
+ aslr_tool__strip_attr_event(event, pevlist);
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2594,7 +2599,6 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
-
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2708,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2717,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2738,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2836,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2922,6 +2943,8 @@ int cmd_inject(int argc, const char **argv)
goto out_delete;
ret = __cmd_inject(&inject);
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
guest_session__exit(&inject.guest_session);
@@ -2929,6 +2952,8 @@ int cmd_inject(int argc, const char **argv)
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..2c5fafbe5d84
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,822 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "evlist.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+#include "pmus.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start_64 = 0xffff800010000000ULL;
+static const u64 kernel_space_start_32 = 0x80000000ULL;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+ void *dso_ptr = key->dso ? RC_CHK_ACCESS(key->dso) : NULL;
+
+ return (size_t)key->machine ^ (size_t)dso_ptr ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct machine *session_machine,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ /*
+ * The cached value is the base of the invariant. We add the
+ * offset into the VMA (start - map__start), plus the map's
+ * pgoff, to get the precise virtual address within this chunk.
+ */
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ /*
+ * For unmapped memory (e.g. kernel anonymous), the cached value
+ * was stored offset by pgoff. Adding pgoff yields the true remap_addr.
+ */
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ struct addr_location prev_al;
+ bool is_contiguous = false;
+
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ addr_location__init(&prev_al);
+ if (thread__find_map(aslr_thread, cpumode, start - 1, &prev_al)) {
+ if (map__end(prev_al.map) == start)
+ is_contiguous = true;
+ }
+ addr_location__exit(&prev_al);
+
+ if (is_contiguous) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+ struct perf_env *env = session_machine ? session_machine->env : NULL;
+ bool is_64 = env ? perf_env__kernel_is_64_bit(env) : (sizeof(void *) == 8);
+ u64 kernel_start_addr = is_64 ? kernel_space_start_64 : kernel_space_start_32;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_start_addr : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ /*
+ * Subtract pgoff from the base virtual address so that
+ * when the lookup path adds pgoff back, it perfectly
+ * cancels out and returns remap_addr.
+ */
+ *new_remap_val = remap_addr - pgoff;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, machine, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap.filename) || is_no_dso_memory(event->mmap.filename))
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, machine, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap2.filename) || is_no_dso_memory(event->mmap2.filename))
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, machine, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist)
+{
+ struct evsel *evsel;
+ bool needs_swap = false;
+
+ if (pevlist && *pevlist) {
+ evsel = evlist__last(*pevlist);
+ if (evsel)
+ needs_swap = evsel->needs_swap;
+ }
+
+ if (event->header.size >= (offsetof(struct perf_record_header_attr,
+ attr.sample_type) + sizeof(u64))) {
+ u64 st = event->attr.attr.sample_type;
+
+ if (needs_swap)
+ st = bswap_64(st);
+
+ st &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (needs_swap)
+ st = bswap_64(st);
+
+ event->attr.attr.sample_type = st;
+ }
+
+ if (event->header.size >= (offsetof(struct perf_record_header_attr,
+ attr.type) + sizeof(u32))) {
+ u32 type = event->attr.attr.type;
+
+ if (needs_swap)
+ type = bswap_32(type);
+
+ if (type == PERF_TYPE_BREAKPOINT &&
+ event->header.size >= (offsetof(struct perf_record_header_attr,
+ attr.bp_addr) + sizeof(u64))) {
+ event->attr.attr.bp_addr = 0;
+ } else if (type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu;
+
+ pmu = perf_pmus__find_by_type(type);
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr,
+ attr.config1) + sizeof(u64)))
+ event->attr.attr.config1 = 0;
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr,
+ attr.config2) + sizeof(u64)))
+ event->attr.attr.config2 = 0;
+ }
+ }
+ }
+}
+
+void aslr_tool__strip_evlist(struct perf_tool *tool __maybe_unused,
+ struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
+
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ evsel->core.attr.config1 = 0;
+ evsel->core.attr.config2 = 0;
+ }
+ }
+ }
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+ struct rb_node *nd;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+
+ while ((nd = rb_first_cached(&aslr->machines.guests)) != NULL) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ rb_erase_cached(nd, &aslr->machines.guests);
+ machine__delete(machine);
+ }
+
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..2b82f711bc67
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+struct evlist;
+union perf_event;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist);
+void aslr_tool__strip_evlist(struct perf_tool *tool, struct evlist *evlist);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/util/aslr.c | 478 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 485 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 2c5fafbe5d84..8980599b0158 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -20,6 +20,7 @@
#include <linux/zalloc.h>
#include <inttypes.h>
#include <unistd.h>
+#include <byteswap.h>
/**
* struct remap_addresses_key - Key for mapping original addresses to remapped ones.
@@ -112,6 +113,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -599,13 +654,428 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+ bool orig_needs_swap;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ orig_needs_swap = evsel->needs_swap;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
+
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ u64 remapped; \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ remapped = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ if (orig_needs_swap) \
+ remapped = bswap_64(remapped); \
+ out_array[j++] = remapped; \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ nr = in_array[i];
+ if (orig_needs_swap)
+ nr = bswap_64(nr);
+ out_array[j++] = in_array[i++];
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ nr = in_array[i];
+ if (orig_needs_swap)
+ nr = bswap_64(nr);
+ out_array[j++] = in_array[i++];
+
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (orig_needs_swap)
+ addr = bswap_64(addr);
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = orig_needs_swap ? bswap_64(addr) : addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ addr = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ if (orig_needs_swap)
+ addr = bswap_64(addr);
+ out_array[j++] = addr;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ nr = in_array[i];
+ if (orig_needs_swap)
+ nr = bswap_64(nr);
+ out_array[j++] = in_array[i++];
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ u64 from = in_array[i++];
+ u64 to = in_array[i++];
+
+ if (orig_needs_swap) {
+ from = bswap_64(from);
+ to = bswap_64(to);
+ }
+
+ from = aslr_tool__remap_address(aslr, thread, sample->cpumode, from);
+ to = aslr_tool__remap_address(aslr, thread, sample->cpumode, to);
+
+ if (orig_needs_swap) {
+ from = bswap_64(from);
+ to = bswap_64(to);
+ }
+
+ out_array[j++] = from;
+ out_array[j++] = to;
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
- return delegate->sample(delegate, event, sample, machine);
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ size = in_array[i];
+ if (orig_needs_swap)
+ size = bswap_64(size);
+ out_array[j++] = in_array[i++];
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = __evsel__parse_sample(evsel, new_event, &new_sample, orig_needs_swap);
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Extend the ASLR tool stripping helpers to drop register dump payloads
by masking out the relevant perf_event_attr fields (sample_regs_user,
sample_regs_intr) when the delegated tool is handling the data.
struct aslr_evsel_priv maintains the original perf_event_attr values
and is looked up via the evsel_orig_attrs hashmap so that sample sizes
can be properly parsed even when bits are stripped from the pipeline.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/builtin-inject.c | 31 +++-
tools/perf/util/aslr.c | 298 ++++++++++++++++++++++++++----------
tools/perf/util/aslr.h | 9 +-
3 files changed, 248 insertions(+), 90 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 6cc9c6dbf608..abb2228bc5bd 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -241,6 +241,9 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (ret)
return ret;
+ if (inject->aslr)
+ aslr_tool__strip_attr_event(event, *pevlist);
+
/* If the output isn't a pipe then the attributes will be written as part of the header. */
if (!inject->output.is_pipe)
return 0;
@@ -279,8 +282,7 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
- if (inject->aslr)
- aslr_tool__strip_attr_event(event, pevlist);
+
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2599,6 +2601,10 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
+
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2857,6 +2863,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2875,10 +2893,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2943,8 +2968,6 @@ int cmd_inject(int argc, const char **argv)
goto out_delete;
ret = __cmd_inject(&inject);
- if (inject.aslr)
- aslr_tool__strip_evlist(tool, inject.session->evlist);
guest_session__exit(&inject.guest_session);
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 8980599b0158..bf6bb2715357 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -18,6 +18,7 @@
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
#include <byteswap.h>
@@ -46,6 +47,23 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+ int orig_sample_size;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
};
@@ -60,6 +78,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -123,6 +146,8 @@ static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
u64 *remapped_invariant_ptr = NULL;
u64 remap_addr = 0;
u8 effective_cpumode = cpumode;
+ struct dso *dso;
+ const char *dso_name;
if (!aslr_thread)
return 0; /* No thread. */
@@ -148,9 +173,15 @@ static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
}
}
+ dso = map__dso(al.map);
+ dso_name = dso ? dso__long_name(dso) : NULL;
+
key.machine = maps__machine(thread__maps(aslr_thread));
- key.dso = map__dso(al.map);
- key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ key.invariant = map__start(al.map);
key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
kernel_pid : thread__pid(aslr_thread);
@@ -659,6 +690,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -671,6 +703,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
bool orig_needs_swap;
del_tool = container_of(tool, struct delegate_tool, tool);
@@ -683,7 +719,24 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+ sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -736,25 +789,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -789,7 +842,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -861,7 +914,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = addr;
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -880,7 +933,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -938,19 +991,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
goto out_put;
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -982,39 +1041,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -1054,11 +1119,20 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
-
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
perf_sample__init(&new_sample, /*all=*/ true);
ret = __evsel__parse_sample(evsel, new_event, &new_sample, orig_needs_swap);
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -1067,6 +1141,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1120,43 +1200,44 @@ static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __mayb
return 0;
}
-
-void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist)
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *evlist)
{
struct evsel *evsel;
bool needs_swap = false;
- if (pevlist && *pevlist) {
- evsel = evlist__last(*pevlist);
- if (evsel)
- needs_swap = evsel->needs_swap;
- }
+ if (!evlist)
+ return;
+
+ evsel = evlist__last(evlist);
+ if (evsel)
+ needs_swap = evsel->needs_swap;
if (event->header.size >= (offsetof(struct perf_record_header_attr,
attr.sample_type) + sizeof(u64))) {
- u64 st = event->attr.attr.sample_type;
-
- if (needs_swap)
- st = bswap_64(st);
-
- st &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (needs_swap) {
+ u64 st = bswap_64(event->attr.attr.sample_type);
- if (needs_swap)
- st = bswap_64(st);
+ st &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ event->attr.attr.sample_type = bswap_64(st);
+ } else {
+ event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ }
- event->attr.attr.sample_type = st;
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr, attr.sample_regs_user) + sizeof(u64)))
+ event->attr.attr.sample_regs_user = 0;
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr, attr.sample_regs_intr) + sizeof(u64)))
+ event->attr.attr.sample_regs_intr = 0;
}
if (event->header.size >= (offsetof(struct perf_record_header_attr,
attr.type) + sizeof(u32))) {
- u32 type = event->attr.attr.type;
-
- if (needs_swap)
- type = bswap_32(type);
+ u32 type = needs_swap ? bswap_32(event->attr.attr.type) : event->attr.attr.type;
if (type == PERF_TYPE_BREAKPOINT &&
event->header.size >= (offsetof(struct perf_record_header_attr,
- attr.bp_addr) + sizeof(u64))) {
+ attr.bp_addr) + sizeof(u64))) {
event->attr.attr.bp_addr = 0;
} else if (type >= PERF_TYPE_MAX) {
struct perf_pmu *pmu;
@@ -1177,28 +1258,6 @@ void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlis
}
}
-void aslr_tool__strip_evlist(struct perf_tool *tool __maybe_unused,
- struct evlist *evlist)
-{
- struct evsel *evsel;
-
- evlist__for_each_entry(evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
- struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
-
- if (pmu && (!strcmp(pmu->name, "kprobe") ||
- !strcmp(pmu->name, "uprobe"))) {
- evsel->core.attr.config1 = 0;
- evsel->core.attr.config2 = 0;
- }
- }
- }
-}
-
static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
{
delegate_tool__init(&aslr->tool, delegate);
@@ -1212,6 +1271,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1274,9 +1336,13 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
@@ -1290,3 +1356,69 @@ void aslr_tool__delete(struct perf_tool *tool)
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+ priv->orig_sample_size = evsel->sample_size;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+ evsel->sample_size = __evsel__sample_size(evsel->core.attr.sample_type);
+ evsel__calc_id_pos(evsel);
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT) {
+ evsel->core.attr.bp_addr = 0;
+ } else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
+
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ evsel->core.attr.config1 = 0;
+ evsel->core.attr.config2 = 0;
+ }
+ }
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ evsel->sample_size = priv->orig_sample_size;
+ evsel__calc_id_pos(evsel);
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index 2b82f711bc67..522e31c8e2c0 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -34,8 +34,11 @@ struct evlist;
union perf_event;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
-void aslr_tool__delete(struct perf_tool *aslr);
-void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **pevlist);
-void aslr_tool__strip_evlist(struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__delete(struct perf_tool *tool);
+
+void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *evlist);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into.
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload to guarantee continuous timer interrupts sampling flow
inside kernel privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions
to catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Note on kernel DSO normalization in the test script:
The test script deliberately normalizes all kernel DSOs to a generic
[kernel] tag before diffing, as obfuscating physical kernel addresses
forces perf report to occasionally shift samples between individual
modules and [kernel.kallsyms] due to the lack of valid host module
boundary maps.
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++++++++++++++++++
1 file changed, 519 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..db5497508259
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,519 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/zero of=/dev/null bs=1M count=500"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record -e cycles:u --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "PERF_SAMPLE_REGS_USER" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+test_regs_stripping
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..b1b8efe42149 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
---
tools/perf/builtin-inject.c | 84 ++++-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 732 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 37 ++
4 files changed, 851 insertions(+), 3 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..00a54d1c7e41 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -24,6 +25,7 @@
#include "util/string2.h"
#include "util/symbol.h"
#include "util/synthetic-events.h"
+#include "util/pmus.h"
#include "util/thread.h"
#include "util/namespaces.h"
#include "util/unwind.h"
@@ -124,6 +126,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -242,8 +245,43 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (!inject->output.is_pipe)
return 0;
- if (!inject->itrace_synth_opts.set)
+ if (!inject->itrace_synth_opts.set) {
+ if (inject->aslr) {
+ union perf_event *stripped_event = malloc(event->header.size);
+ int err;
+
+ if (!stripped_event)
+ return -ENOMEM;
+ memcpy(stripped_event, event, event->header.size);
+ stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT &&
+ event->header.size >= (offsetof(struct perf_record_header_attr,
+ attr.bp_addr) + sizeof(u64))) {
+ stripped_event->attr.attr.bp_addr = 0;
+ } else if (stripped_event->attr.attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu;
+
+ pmu = perf_pmus__find_by_type(stripped_event->attr.attr.type);
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr,
+ attr.config1) + sizeof(u64)))
+ stripped_event->attr.attr.config1 = 0;
+ if (event->header.size >=
+ (offsetof(struct perf_record_header_attr,
+ attr.config2) + sizeof(u64)))
+ stripped_event->attr.attr.config2 = 0;
+ }
+ }
+
+ err = perf_event__repipe_synth(tool, stripped_event);
+ free(stripped_event);
+ return err;
+ }
return perf_event__repipe_synth(tool, event);
+ }
if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZE_VER0) {
pr_err("Attribute event size %u is too small\n", event->header.size);
@@ -276,6 +314,17 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+ if (inject->aslr) {
+ attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu = perf_pmus__find_by_type(attr.type);
+
+ if (pmu && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe"))) {
+ attr.config1 = 0;
+ attr.config2 = 0;
+ }
+ }
+ }
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2594,7 +2643,6 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
-
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2752,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2761,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2782,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2880,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2923,12 +2988,25 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+ }
+
guest_session__exit(&inject.guest_session);
out_delete:
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..084158014dc7
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,732 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start_64 = 0xffff800010000000ULL;
+static const u64 kernel_space_start_32 = 0x80000000ULL;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+ void *dso_ptr = key->dso ? RC_CHK_ACCESS(key->dso) : NULL;
+
+ return (size_t)key->machine ^ (size_t)dso_ptr ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ /*
+ * The cached value is the base of the invariant. We add the
+ * offset into the VMA (start - map__start), plus the map's
+ * pgoff, to get the precise virtual address within this chunk.
+ */
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ /*
+ * For unmapped memory (e.g. kernel anonymous), the cached value
+ * was stored offset by pgoff. Adding pgoff yields the true remap_addr.
+ */
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ struct addr_location prev_al;
+ bool is_contiguous = false;
+
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ addr_location__init(&prev_al);
+ if (thread__find_map(aslr_thread, cpumode, start - 1, &prev_al)) {
+ if (map__end(prev_al.map) == start)
+ is_contiguous = true;
+ }
+ addr_location__exit(&prev_al);
+
+ if (is_contiguous) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+ struct machine *machine = remap_key.machine;
+ struct perf_env *env = machine ? machine->env : NULL;
+ bool is_64 = env ? perf_env__kernel_is_64_bit(env) : (sizeof(void *) == 8);
+ u64 kernel_start_addr = is_64 ? kernel_space_start_64 : kernel_space_start_32;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_start_addr : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ /*
+ * Subtract pgoff from the base virtual address so that
+ * when the lookup path adds pgoff back, it perfectly
+ * cancels out and returns remap_addr.
+ */
+ *new_remap_val = remap_addr - pgoff;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap.filename) || is_no_dso_memory(event->mmap.filename))
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap2.filename) || is_no_dso_memory(event->mmap2.filename))
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..a9b90bf29540
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 454 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 461 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 084158014dc7..7afa5a0dac2f 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -110,6 +110,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -597,13 +651,405 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+ bool orig_needs_swap;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
+
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* from */
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* to */
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ orig_needs_swap = evsel->needs_swap;
- return delegate->sample(delegate, event, sample, machine);
+ evsel->needs_swap = false;
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+ evsel->needs_swap = orig_needs_swap;
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into .
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload
to guarantee continuous timer interrupts sampling flow inside kernel
privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions to
catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 464 ++++++++++++++++++++++++++
1 file changed, 464 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..d8ded16ba905
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,464 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/zero of=/dev/null bs=1M count=500"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
Refactor the ASLR tool to strip out only the register dump payload
by masking out the relevant perf_event_attr fields when the delegated
tool is handling the data. struct aslr_evsel_priv maintains the
original perf_event_attr values and is looked up via the evsel_orig_attrs
hashmap.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-inject.c | 46 ++++--
tools/perf/tests/shell/inject_aslr.sh | 55 +++++++
tools/perf/util/aslr.c | 224 +++++++++++++++++++++-----
tools/perf/util/aslr.h | 4 +
4 files changed, 277 insertions(+), 52 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 00a54d1c7e41..c852ade3c4e3 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -254,6 +254,12 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
return -ENOMEM;
memcpy(stripped_event, event, event->header.size);
stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_user) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_user = 0;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_intr) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_intr = 0;
if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT &&
event->header.size >= (offsetof(struct perf_record_header_attr,
@@ -316,7 +322,9 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.sample_type &= ~PERF_SAMPLE_AUX;
if (inject->aslr) {
attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
- if (attr.type >= PERF_TYPE_MAX) {
+ if (attr.type == PERF_TYPE_BREAKPOINT) {
+ attr.bp_addr = 0;
+ } else if (attr.type >= PERF_TYPE_MAX) {
struct perf_pmu *pmu = perf_pmus__find_by_type(attr.type);
if (pmu && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe"))) {
@@ -324,6 +332,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.config2 = 0;
}
}
+ attr.sample_regs_user = 0;
+ attr.sample_regs_intr = 0;
}
if (inject->itrace_synth_opts.add_last_branch) {
@@ -2643,6 +2653,10 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
+
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2901,6 +2915,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2919,10 +2945,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2988,17 +3021,6 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
- if (inject.aslr) {
- struct evsel *evsel;
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- }
- }
-
guest_session__exit(&inject.guest_session);
out_delete:
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
index d8ded16ba905..21d306a0ff2f 100755
--- a/tools/perf/tests/shell/inject_aslr.sh
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -450,6 +450,60 @@ test_kernel_report_aslr() {
fi
}
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "PERF_SAMPLE_REGS_USER" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
test_basic_aslr
test_pipe_aslr
test_callchain_aslr
@@ -459,6 +513,7 @@ test_pipe_out_report_aslr
test_dropped_samples
test_kernel_aslr
test_kernel_report_aslr
+test_regs_stripping
cleanup ${err}
exit $err
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 7afa5a0dac2f..bac41bff1b2a 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -5,6 +5,7 @@
#include "debug.h"
#include "event.h"
#include "evsel.h"
+#include "evlist.h"
#include "machine.h"
#include "map.h"
#include "thread.h"
@@ -12,10 +13,11 @@
#include "session.h"
#include "data.h"
#include "dso.h"
-
+#include "pmus.h"
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
@@ -43,6 +45,23 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+ int orig_sample_size;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
};
@@ -57,6 +76,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -656,6 +680,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -668,6 +693,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
bool orig_needs_swap;
del_tool = container_of(tool, struct delegate_tool, tool);
@@ -678,7 +707,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -727,25 +772,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -779,7 +824,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -845,7 +890,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -864,7 +909,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -909,19 +954,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
goto out_put;
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -952,39 +1003,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -1024,15 +1081,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
-
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
orig_needs_swap = evsel->needs_swap;
-
evsel->needs_swap = false;
perf_sample__init(&new_sample, /*all=*/ true);
ret = evsel__parse_sample(evsel, new_event, &new_sample);
evsel->needs_swap = orig_needs_swap;
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -1041,6 +1106,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1107,6 +1178,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1168,11 +1242,81 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+ priv->orig_sample_size = evsel->sample_size;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+ evsel->sample_size = __evsel__sample_size(evsel->core.attr.sample_type);
+ evsel__calc_id_pos(evsel);
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT) {
+ evsel->core.attr.bp_addr = 0;
+ } else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
+
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ evsel->core.attr.config1 = 0;
+ evsel->core.attr.config2 = 0;
+ }
+ }
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ evsel->sample_size = priv->orig_sample_size;
+ evsel__calc_id_pos(evsel);
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index a9b90bf29540..4c2cffc0e500 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -30,8 +30,12 @@
struct perf_tool;
struct evsel;
+struct evlist;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
void aslr_tool__delete(struct perf_tool *aslr);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..b1b8efe42149 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
---
tools/perf/builtin-inject.c | 76 +++-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 718 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 37 ++
4 files changed, 829 insertions(+), 3 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..6ac6e6fb3b47 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -24,6 +25,7 @@
#include "util/string2.h"
#include "util/symbol.h"
#include "util/synthetic-events.h"
+#include "util/pmus.h"
#include "util/thread.h"
#include "util/namespaces.h"
#include "util/unwind.h"
@@ -124,6 +126,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -242,8 +245,35 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (!inject->output.is_pipe)
return 0;
- if (!inject->itrace_synth_opts.set)
+ if (!inject->itrace_synth_opts.set) {
+ if (inject->aslr) {
+ union perf_event *stripped_event = malloc(event->header.size);
+ int err;
+
+ if (!stripped_event)
+ return -ENOMEM;
+ memcpy(stripped_event, event, event->header.size);
+ stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT) {
+ stripped_event->attr.attr.bp_addr = 0;
+ } else if (stripped_event->attr.attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu;
+
+ pmu = perf_pmus__find_by_type(stripped_event->attr.attr.type);
+ if (pmu && (!strcmp(pmu->name, "kprobe") ||
+ !strcmp(pmu->name, "uprobe"))) {
+ stripped_event->attr.attr.config1 = 0;
+ stripped_event->attr.attr.config2 = 0;
+ }
+ }
+
+ err = perf_event__repipe_synth(tool, stripped_event);
+ free(stripped_event);
+ return err;
+ }
return perf_event__repipe_synth(tool, event);
+ }
if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZE_VER0) {
pr_err("Attribute event size %u is too small\n", event->header.size);
@@ -276,6 +306,17 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+ if (inject->aslr) {
+ attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (attr.type >= PERF_TYPE_MAX) {
+ struct perf_pmu *pmu = perf_pmus__find_by_type(attr.type);
+
+ if (pmu && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe"))) {
+ attr.config1 = 0;
+ attr.config2 = 0;
+ }
+ }
+ }
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2594,7 +2635,6 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
-
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2744,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2753,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2774,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2872,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2923,12 +2980,25 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+ }
+
guest_session__exit(&inject.guest_session);
out_delete:
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..16537e0e1bbb
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,718 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start_64 = 0xffff800010000000ULL;
+static const u64 kernel_space_start_32 = 0x80000000ULL;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+ void *dso_ptr = key->dso ? RC_CHK_ACCESS(key->dso) : NULL;
+
+ return (size_t)key->machine ^ (size_t)dso_ptr ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ struct addr_location prev_al;
+ bool is_contiguous = false;
+
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ addr_location__init(&prev_al);
+ if (thread__find_map(aslr_thread, cpumode, start - 1, &prev_al)) {
+ if (map__end(prev_al.map) == start)
+ is_contiguous = true;
+ }
+ addr_location__exit(&prev_al);
+
+ if (is_contiguous) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+ struct machine *machine = remap_key.machine;
+ struct perf_env *env = machine ? machine->env : NULL;
+ bool is_64 = env ? perf_env__kernel_is_64_bit(env) : (sizeof(void *) == 8);
+ u64 kernel_start_addr = is_64 ? kernel_space_start_64 : kernel_space_start_32;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_start_addr : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ *new_remap_val = remap_addr;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap.filename) || is_no_dso_memory(event->mmap.filename))
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ /*
+ * For anonymous memory (and kernel maps), the kernel populates the
+ * event's pgoff field with the original un-obfuscated virtual address
+ * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT).
+ * We must overwrite pgoff with the new remapped byte address to prevent
+ * leaking the original ASLR layout.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ is_anon_memory(event->mmap2.filename) || is_no_dso_memory(event->mmap2.filename))
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..a9b90bf29540
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 454 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 461 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 16537e0e1bbb..7bd646a5c2fb 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -110,6 +110,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -583,13 +637,405 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+ bool orig_needs_swap;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
+
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* from */
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* to */
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ orig_needs_swap = evsel->needs_swap;
- return delegate->sample(delegate, event, sample, machine);
+ evsel->needs_swap = false;
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+ evsel->needs_swap = orig_needs_swap;
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into .
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload
to guarantee continuous timer interrupts sampling flow inside kernel
privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions to
catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 464 ++++++++++++++++++++++++++
1 file changed, 464 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..d8ded16ba905
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,464 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/zero of=/dev/null bs=1M count=500"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
Refactor the ASLR tool to strip out only the register dump payload
by masking out the relevant perf_event_attr fields when the delegated
tool is handling the data. struct aslr_evsel_priv maintains the
original perf_event_attr values and is looked up via the evsel_orig_attrs
hashmap.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-inject.c | 46 ++++--
tools/perf/tests/shell/inject_aslr.sh | 55 +++++++
tools/perf/util/aslr.c | 213 +++++++++++++++++++++-----
tools/perf/util/aslr.h | 4 +
4 files changed, 267 insertions(+), 51 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 6ac6e6fb3b47..96b90af8264c 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -254,6 +254,12 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
return -ENOMEM;
memcpy(stripped_event, event, event->header.size);
stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_user) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_user = 0;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_intr) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_intr = 0;
if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT) {
stripped_event->attr.attr.bp_addr = 0;
@@ -308,7 +314,9 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.sample_type &= ~PERF_SAMPLE_AUX;
if (inject->aslr) {
attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
- if (attr.type >= PERF_TYPE_MAX) {
+ if (attr.type == PERF_TYPE_BREAKPOINT) {
+ attr.bp_addr = 0;
+ } else if (attr.type >= PERF_TYPE_MAX) {
struct perf_pmu *pmu = perf_pmus__find_by_type(attr.type);
if (pmu && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe"))) {
@@ -316,6 +324,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.config2 = 0;
}
}
+ attr.sample_regs_user = 0;
+ attr.sample_regs_intr = 0;
}
if (inject->itrace_synth_opts.add_last_branch) {
@@ -2635,6 +2645,10 @@ static int __cmd_inject(struct perf_inject *inject)
evsel->core.attr.exclude_callchain_user = 0;
}
}
+
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2893,6 +2907,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2911,10 +2937,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2980,17 +3013,6 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
- if (inject.aslr) {
- struct evsel *evsel;
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- }
- }
-
guest_session__exit(&inject.guest_session);
out_delete:
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
index d8ded16ba905..21d306a0ff2f 100755
--- a/tools/perf/tests/shell/inject_aslr.sh
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -450,6 +450,60 @@ test_kernel_report_aslr() {
fi
}
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "PERF_SAMPLE_REGS_USER" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
test_basic_aslr
test_pipe_aslr
test_callchain_aslr
@@ -459,6 +513,7 @@ test_pipe_out_report_aslr
test_dropped_samples
test_kernel_aslr
test_kernel_report_aslr
+test_regs_stripping
cleanup ${err}
exit $err
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 7bd646a5c2fb..99a6d7318174 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -5,6 +5,7 @@
#include "debug.h"
#include "event.h"
#include "evsel.h"
+#include "evlist.h"
#include "machine.h"
#include "map.h"
#include "thread.h"
@@ -16,6 +17,7 @@
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
@@ -43,6 +45,23 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+ int orig_sample_size;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
};
@@ -57,6 +76,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -642,6 +666,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -654,6 +679,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
bool orig_needs_swap;
del_tool = container_of(tool, struct delegate_tool, tool);
@@ -664,7 +693,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -713,25 +758,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -765,7 +810,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -831,7 +876,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -850,7 +895,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -895,19 +940,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
goto out_put;
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -938,39 +989,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -1010,15 +1067,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
-
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
orig_needs_swap = evsel->needs_swap;
-
evsel->needs_swap = false;
perf_sample__init(&new_sample, /*all=*/ true);
ret = evsel__parse_sample(evsel, new_event, &new_sample);
evsel->needs_swap = orig_needs_swap;
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -1027,6 +1092,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1093,6 +1164,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1154,11 +1228,72 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+ priv->orig_sample_size = evsel->sample_size;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+ evsel->sample_size = __evsel__sample_size(evsel->core.attr.sample_type);
+ evsel__calc_id_pos(evsel);
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ evsel->sample_size = priv->orig_sample_size;
+ evsel__calc_id_pos(evsel);
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index a9b90bf29540..4c2cffc0e500 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -30,8 +30,12 @@
struct perf_tool;
struct evsel;
+struct evlist;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
void aslr_tool__delete(struct perf_tool *aslr);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..b1b8efe42149 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
---
tools/perf/builtin-inject.c | 57 ++-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 695 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 37 ++
4 files changed, 788 insertions(+), 2 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..65c7eccccf4d 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -124,6 +125,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -242,8 +244,25 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (!inject->output.is_pipe)
return 0;
- if (!inject->itrace_synth_opts.set)
+ if (!inject->itrace_synth_opts.set) {
+ if (inject->aslr) {
+ union perf_event *stripped_event = malloc(event->header.size);
+ int err;
+
+ if (!stripped_event)
+ return -ENOMEM;
+ memcpy(stripped_event, event, event->header.size);
+ stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
+ stripped_event->attr.attr.bp_addr = 0;
+
+ err = perf_event__repipe_synth(tool, stripped_event);
+ free(stripped_event);
+ return err;
+ }
return perf_event__repipe_synth(tool, event);
+ }
if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZE_VER0) {
pr_err("Attribute event size %u is too small\n", event->header.size);
@@ -276,6 +295,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+ if (inject->aslr)
+ attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2595,6 +2616,8 @@ static int __cmd_inject(struct perf_inject *inject)
}
}
+
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2727,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2736,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2757,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2855,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2923,12 +2963,25 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+ }
+
guest_session__exit(&inject.guest_session);
out_delete:
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..4b4b00b4d52d
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,695 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+ u64 orig_last_end;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start_64 = 0xffff800010000000ULL;
+static const u64 kernel_space_start_32 = 0x80000000ULL;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+ void *dso_ptr = key->dso ? RC_CHK_ACCESS(key->dso) : NULL;
+
+ return (size_t)key->machine ^ (size_t)dso_ptr ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ if (start == top->orig_last_end) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ top->orig_last_end = start + len;
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+ struct machine *machine = remap_key.machine;
+ struct perf_env *env = machine ? machine->env : NULL;
+ bool is_64 = env ? perf_env__kernel_is_64_bit(env) : (sizeof(void *) == 8);
+ u64 kernel_start_addr = is_64 ? kernel_space_start_64 : kernel_space_start_32;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_start_addr : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ top_val->orig_last_end = start + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ *new_remap_val = remap_addr;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..a9b90bf29540
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 449 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 456 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 4b4b00b4d52d..ebae4617b158 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -111,6 +111,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -560,13 +614,400 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
- return delegate->sample(delegate, event, sample, machine);
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* from */
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* to */
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into .
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload
to guarantee continuous timer interrupts sampling flow inside kernel
privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions to
catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 464 ++++++++++++++++++++++++++
1 file changed, 464 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..d8ded16ba905
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,464 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/zero of=/dev/null bs=1M count=500"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
Refactor the ASLR tool to strip out only the register dump payload
by masking out the relevant perf_event_attr fields when the delegated
tool is handling the data. struct aslr_evsel_priv maintains the
original perf_event_attr values and is looked up via the evsel_orig_attrs
hashmap.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-inject.c | 46 ++++--
tools/perf/tests/shell/inject_aslr.sh | 55 +++++++
tools/perf/util/aslr.c | 212 +++++++++++++++++++++-----
tools/perf/util/aslr.h | 4 +
4 files changed, 267 insertions(+), 50 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 65c7eccccf4d..de315bb334b3 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -253,6 +253,12 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
return -ENOMEM;
memcpy(stripped_event, event, event->header.size);
stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_user) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_user = 0;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_intr) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_intr = 0;
if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
stripped_event->attr.attr.bp_addr = 0;
@@ -295,8 +301,13 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
- if (inject->aslr)
+ if (inject->aslr) {
attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (attr.type == PERF_TYPE_BREAKPOINT)
+ attr.bp_addr = 0;
+ attr.sample_regs_user = 0;
+ attr.sample_regs_intr = 0;
+ }
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2618,6 +2629,9 @@ static int __cmd_inject(struct perf_inject *inject)
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2876,6 +2890,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2894,10 +2920,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2963,17 +2996,6 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
- if (inject.aslr) {
- struct evsel *evsel;
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- }
- }
-
guest_session__exit(&inject.guest_session);
out_delete:
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
index d8ded16ba905..21d306a0ff2f 100755
--- a/tools/perf/tests/shell/inject_aslr.sh
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -450,6 +450,60 @@ test_kernel_report_aslr() {
fi
}
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "PERF_SAMPLE_REGS_USER" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
test_basic_aslr
test_pipe_aslr
test_callchain_aslr
@@ -459,6 +513,7 @@ test_pipe_out_report_aslr
test_dropped_samples
test_kernel_aslr
test_kernel_report_aslr
+test_regs_stripping
cleanup ${err}
exit $err
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index ebae4617b158..995159d56ff5 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -5,6 +5,7 @@
#include "debug.h"
#include "event.h"
#include "evsel.h"
+#include "evlist.h"
#include "machine.h"
#include "map.h"
#include "thread.h"
@@ -16,6 +17,7 @@
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
@@ -43,6 +45,23 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+ int orig_sample_size;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
u64 orig_last_end;
@@ -58,6 +77,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -619,6 +643,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -631,7 +656,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
-
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
del_tool = container_of(tool, struct delegate_tool, tool);
aslr = container_of(del_tool, struct aslr_tool, tool);
delegate = aslr->tool.delegate;
@@ -640,7 +668,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -689,25 +733,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -741,7 +785,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -807,7 +851,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -826,7 +870,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -871,19 +915,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
goto out_put;
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -914,39 +964,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -987,10 +1043,20 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
perf_sample__init(&new_sample, /*all=*/ true);
ret = evsel__parse_sample(evsel, new_event, &new_sample);
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -999,6 +1065,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1065,6 +1137,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1126,11 +1201,72 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+ priv->orig_sample_size = evsel->sample_size;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+ evsel->sample_size = __evsel__sample_size(evsel->core.attr.sample_type);
+ evsel__calc_id_pos(evsel);
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ evsel->sample_size = priv->orig_sample_size;
+ evsel__calc_id_pos(evsel);
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index a9b90bf29540..4c2cffc0e500 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -30,8 +30,12 @@
struct perf_tool;
struct evsel;
+struct evlist;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
void aslr_tool__delete(struct perf_tool *aslr);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..b1b8efe42149 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
---
tools/perf/builtin-inject.c | 57 ++-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 695 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 37 ++
4 files changed, 788 insertions(+), 2 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..65c7eccccf4d 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -124,6 +125,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -242,8 +244,25 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (!inject->output.is_pipe)
return 0;
- if (!inject->itrace_synth_opts.set)
+ if (!inject->itrace_synth_opts.set) {
+ if (inject->aslr) {
+ union perf_event *stripped_event = malloc(event->header.size);
+ int err;
+
+ if (!stripped_event)
+ return -ENOMEM;
+ memcpy(stripped_event, event, event->header.size);
+ stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
+ stripped_event->attr.attr.bp_addr = 0;
+
+ err = perf_event__repipe_synth(tool, stripped_event);
+ free(stripped_event);
+ return err;
+ }
return perf_event__repipe_synth(tool, event);
+ }
if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZE_VER0) {
pr_err("Attribute event size %u is too small\n", event->header.size);
@@ -276,6 +295,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+ if (inject->aslr)
+ attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2595,6 +2616,8 @@ static int __cmd_inject(struct perf_inject *inject)
}
}
+
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2727,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2736,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2757,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2855,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2923,12 +2963,25 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+ }
+
guest_session__exit(&inject.guest_session);
out_delete:
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..5a002dcecb8f
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,695 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+ u64 orig_last_end;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start_64 = 0xffff800010000000ULL;
+static const u64 kernel_space_start_32 = 0x80000000ULL;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+ void *dso_ptr = key->dso ? RC_CHK_ACCESS(key->dso) : NULL;
+
+ return (size_t)key->machine ^ (size_t)dso_ptr ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ if (start == top->orig_last_end) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ top->orig_last_end = start + len;
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+ struct machine *machine = remap_key.machine;
+ struct perf_env *env = machine ? machine->env : NULL;
+ u64 kernel_start_addr = perf_env__kernel_is_64_bit(env) ?
+ kernel_space_start_64 : kernel_space_start_32;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_start_addr : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ top_val->orig_last_end = start + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ *new_remap_val = remap_addr;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..a9b90bf29540
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 449 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 456 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 5a002dcecb8f..c62ae5bcc124 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -111,6 +111,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -560,13 +614,400 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
- return delegate->sample(delegate, event, sample, machine);
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* from */
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* to */
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into .
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload
to guarantee continuous timer interrupts sampling flow inside kernel
privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions to
catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 464 ++++++++++++++++++++++++++
1 file changed, 464 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..d8ded16ba905
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,464 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/zero of=/dev/null bs=1M count=500"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
Refactor the ASLR tool to strip out only the register dump payload
by masking out the relevant perf_event_attr fields when the delegated
tool is handling the data. struct aslr_evsel_priv maintains the
original perf_event_attr values and is looked up via the evsel_orig_attrs
hashmap.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-inject.c | 46 ++++--
tools/perf/tests/shell/inject_aslr.sh | 55 +++++++
tools/perf/util/aslr.c | 208 +++++++++++++++++++++-----
tools/perf/util/aslr.h | 4 +
4 files changed, 263 insertions(+), 50 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 65c7eccccf4d..de315bb334b3 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -253,6 +253,12 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
return -ENOMEM;
memcpy(stripped_event, event, event->header.size);
stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_user) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_user = 0;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_intr) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_intr = 0;
if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
stripped_event->attr.attr.bp_addr = 0;
@@ -295,8 +301,13 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
- if (inject->aslr)
+ if (inject->aslr) {
attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (attr.type == PERF_TYPE_BREAKPOINT)
+ attr.bp_addr = 0;
+ attr.sample_regs_user = 0;
+ attr.sample_regs_intr = 0;
+ }
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2618,6 +2629,9 @@ static int __cmd_inject(struct perf_inject *inject)
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2876,6 +2890,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2894,10 +2920,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2963,17 +2996,6 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
- if (inject.aslr) {
- struct evsel *evsel;
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- }
- }
-
guest_session__exit(&inject.guest_session);
out_delete:
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
index d8ded16ba905..21d306a0ff2f 100755
--- a/tools/perf/tests/shell/inject_aslr.sh
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -450,6 +450,60 @@ test_kernel_report_aslr() {
fi
}
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "PERF_SAMPLE_REGS_USER" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
test_basic_aslr
test_pipe_aslr
test_callchain_aslr
@@ -459,6 +513,7 @@ test_pipe_out_report_aslr
test_dropped_samples
test_kernel_aslr
test_kernel_report_aslr
+test_regs_stripping
cleanup ${err}
exit $err
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index c62ae5bcc124..19fca84a7405 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -5,6 +5,7 @@
#include "debug.h"
#include "event.h"
#include "evsel.h"
+#include "evlist.h"
#include "machine.h"
#include "map.h"
#include "thread.h"
@@ -16,6 +17,7 @@
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
@@ -43,6 +45,22 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
u64 orig_last_end;
@@ -58,6 +76,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -619,6 +642,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -631,7 +655,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
-
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
del_tool = container_of(tool, struct delegate_tool, tool);
aslr = container_of(del_tool, struct aslr_tool, tool);
delegate = aslr->tool.delegate;
@@ -640,7 +667,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -689,25 +732,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -741,7 +784,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -807,7 +850,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -826,7 +869,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -871,19 +914,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
goto out_put;
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -914,39 +963,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -987,10 +1042,20 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
perf_sample__init(&new_sample, /*all=*/ true);
ret = evsel__parse_sample(evsel, new_event, &new_sample);
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -999,6 +1064,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1065,6 +1136,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1126,11 +1200,69 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel__reset_sample_bit(evsel, REGS_USER);
+ evsel__reset_sample_bit(evsel, REGS_INTR);
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index a9b90bf29540..4c2cffc0e500 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -30,8 +30,12 @@
struct perf_tool;
struct evsel;
+struct evlist;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
void aslr_tool__delete(struct perf_tool *aslr);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..b1b8efe42149 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
---
tools/perf/builtin-inject.c | 57 ++-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 689 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 37 ++
4 files changed, 782 insertions(+), 2 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..65c7eccccf4d 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -124,6 +125,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -242,8 +244,25 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (!inject->output.is_pipe)
return 0;
- if (!inject->itrace_synth_opts.set)
+ if (!inject->itrace_synth_opts.set) {
+ if (inject->aslr) {
+ union perf_event *stripped_event = malloc(event->header.size);
+ int err;
+
+ if (!stripped_event)
+ return -ENOMEM;
+ memcpy(stripped_event, event, event->header.size);
+ stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
+ stripped_event->attr.attr.bp_addr = 0;
+
+ err = perf_event__repipe_synth(tool, stripped_event);
+ free(stripped_event);
+ return err;
+ }
return perf_event__repipe_synth(tool, event);
+ }
if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZE_VER0) {
pr_err("Attribute event size %u is too small\n", event->header.size);
@@ -276,6 +295,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+ if (inject->aslr)
+ attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2595,6 +2616,8 @@ static int __cmd_inject(struct perf_inject *inject)
}
}
+
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2727,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2736,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2757,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2855,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2923,12 +2963,25 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+ }
+
guest_session__exit(&inject.guest_session);
out_delete:
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..ac24fda658a5
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,689 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+ u64 orig_last_end;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start = 0xffff800010000000;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+
+ return (size_t)key->machine ^ (size_t)RC_CHK_ACCESS(key->dso) ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ if (start == top->orig_last_end) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ top->orig_last_end = start + len;
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_space_start : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ top_val->orig_last_end = start + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ *new_remap_val = remap_addr;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..a9b90bf29540
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 449 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 456 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index ac24fda658a5..dece5fb43dd8 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -109,6 +109,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -554,13 +608,400 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
- return delegate->sample(delegate, event, sample, machine);
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* from */
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* to */
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into .
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload
to guarantee continuous timer interrupts sampling flow inside kernel
privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions to
catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 464 ++++++++++++++++++++++++++
1 file changed, 464 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..d8ded16ba905
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,464 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/zero of=/dev/null bs=1M count=500"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
Refactor the ASLR tool to strip out only the register dump payload
by masking out the relevant perf_event_attr fields when the delegated
tool is handling the data. struct aslr_evsel_priv maintains the
original perf_event_attr values and is looked up via the evsel_orig_attrs
hashmap.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-inject.c | 46 ++++--
tools/perf/tests/shell/inject_aslr.sh | 55 +++++++
tools/perf/util/aslr.c | 208 +++++++++++++++++++++-----
tools/perf/util/aslr.h | 4 +
4 files changed, 263 insertions(+), 50 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 65c7eccccf4d..de315bb334b3 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -253,6 +253,12 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
return -ENOMEM;
memcpy(stripped_event, event, event->header.size);
stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_user) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_user = 0;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_intr) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_intr = 0;
if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
stripped_event->attr.attr.bp_addr = 0;
@@ -295,8 +301,13 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
- if (inject->aslr)
+ if (inject->aslr) {
attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (attr.type == PERF_TYPE_BREAKPOINT)
+ attr.bp_addr = 0;
+ attr.sample_regs_user = 0;
+ attr.sample_regs_intr = 0;
+ }
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2618,6 +2629,9 @@ static int __cmd_inject(struct perf_inject *inject)
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2876,6 +2890,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2894,10 +2920,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2963,17 +2996,6 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
- if (inject.aslr) {
- struct evsel *evsel;
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- }
- }
-
guest_session__exit(&inject.guest_session);
out_delete:
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
index d8ded16ba905..21d306a0ff2f 100755
--- a/tools/perf/tests/shell/inject_aslr.sh
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -450,6 +450,60 @@ test_kernel_report_aslr() {
fi
}
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "PERF_SAMPLE_REGS_USER" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
test_basic_aslr
test_pipe_aslr
test_callchain_aslr
@@ -459,6 +513,7 @@ test_pipe_out_report_aslr
test_dropped_samples
test_kernel_aslr
test_kernel_report_aslr
+test_regs_stripping
cleanup ${err}
exit $err
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index dece5fb43dd8..14c8589d7453 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -5,6 +5,7 @@
#include "debug.h"
#include "event.h"
#include "evsel.h"
+#include "evlist.h"
#include "machine.h"
#include "map.h"
#include "thread.h"
@@ -16,6 +17,7 @@
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
@@ -43,6 +45,22 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
u64 orig_last_end;
@@ -58,6 +76,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -613,6 +636,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -625,7 +649,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
-
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
del_tool = container_of(tool, struct delegate_tool, tool);
aslr = container_of(del_tool, struct aslr_tool, tool);
delegate = aslr->tool.delegate;
@@ -634,7 +661,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -683,25 +726,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -735,7 +778,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -801,7 +844,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -820,7 +863,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -865,19 +908,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
goto out_put;
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -908,39 +957,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -981,10 +1036,20 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
perf_sample__init(&new_sample, /*all=*/ true);
ret = evsel__parse_sample(evsel, new_event, &new_sample);
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -993,6 +1058,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1059,6 +1130,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1120,11 +1194,69 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel__reset_sample_bit(evsel, REGS_USER);
+ evsel__reset_sample_bit(evsel, REGS_INTR);
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index a9b90bf29540..4c2cffc0e500 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -30,8 +30,12 @@
struct perf_tool;
struct evsel;
+struct evlist;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
void aslr_tool__delete(struct perf_tool *aslr);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
kallsyms image loading (dso__load_kernel_sym,
dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
updates (machine__update_kernel_mmap), the loader directly modifies
live virtual address boundary keys fields on map objects. If these
boundaries are mutated while the map pointer actively resides inside
the parent maps cache array list (kmaps) outside of any lock closure,
an unsafe concurrent window is exposed where parallel worker lookup
threads (e.g., inside perf top) can mistakenly assume the cache
remains sorted based on stale parameters, executing binary search
queries (bsearch) across an unsorted range and triggering lookup
failures.
Fix this by introducing maps__mutate_mapping() that explicitly
acquires the parent maps write semaphore lock, executes an incoming
mutation callback block to perform the field updates under lock
protection, and invalidates the sorted tracking flags prior to
releasing the write lock. This guarantees synchronization invariants,
closing the concurrent lookup race window. The adjacent module
alignment pass inside machine__create_kernel_maps() is safely
preserved as a high-performance lockless pass, as its invocation
lifecycle bounds remain strictly single-threaded by contract during
session initialization construction. To safely support this
unconditional down_write write lock mutator without recursive
read-to-write self-deadlock upgrades during lazy symbol loading, we
introduce a public maps__load_maps() API. It copies map pointers under
a brief read lock and force-loads all modules locklessly outside the
lock. Callers (such as perf inject) must pre-load all kernel symbol
maps up front at startup using maps__load_maps(), completely bypassing
dynamic runtime mutations.
Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/machine.c | 32 +++++---
tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++-------
tools/perf/util/maps.h | 3 +
tools/perf/util/symbol-elf.c | 41 ++++++----
tools/perf/util/symbol.c | 17 +++-
5 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index da1ad58758af..1ea06fde14e0 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
map__set_end(machine->vmlinux_map, ~0ULL);
}
-static int machine__update_kernel_mmap(struct machine *machine,
- u64 start, u64 end)
+struct kernel_mmap_mutation_ctx {
+ u64 start;
+ u64 end;
+};
+
+static int kernel_mmap_mutate_cb(struct map *map, void *data)
{
- struct map *orig, *updated;
- int err;
+ struct kernel_mmap_mutation_ctx *ctx = data;
- orig = machine->vmlinux_map;
- updated = map__get(orig);
+ map__set_start(map, ctx->start);
+ map__set_end(map, ctx->end);
+ if (ctx->start == 0 && ctx->end == 0)
+ map__set_end(map, ~0ULL);
+ return 0;
+}
- machine->vmlinux_map = updated;
- maps__remove(machine__kernel_maps(machine), orig);
- machine__set_kernel_mmap(machine, start, end);
- err = maps__insert(machine__kernel_maps(machine), updated);
- map__put(orig);
+static int machine__update_kernel_mmap(struct machine *machine,
+ u64 start, u64 end)
+{
+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
- return err;
+ return maps__mutate_mapping(machine__kernel_maps(machine),
+ machine->vmlinux_map,
+ kernel_mmap_mutate_cb, &ctx);
}
int machine__create_kernel_maps(struct machine *machine)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index 923935ee21b6..aed72c9f0a50 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map)
#endif
}
+/**
+ * maps__mutate_mapping - Apply write-protected mutations to a map.
+ * @maps: The maps collection containing the map.
+ * @map: The map to mutate.
+ * @mutate_cb: Callback function that performs the actual mutations.
+ * @data: Private data passed to the callback.
+ *
+ * This acquires the write lock on the maps semaphore to safely protect
+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
+ *
+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
+ * maps__load_maps()) under a clean, single-threaded context before entering
+ * multi-threaded event processing loops.
+ */
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data)
+{
+ int err = 0;
+
+ if (maps)
+ down_write(maps__lock(maps));
+
+ err = mutate_cb(map, data);
+
+ if (maps) {
+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
+ }
+
+ if (maps)
+ up_write(maps__lock(maps));
+
+#ifdef HAVE_LIBDW_SUPPORT
+ if (maps)
+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
+#endif
+
+ return err;
+}
+
bool maps__empty(struct maps *maps)
{
bool res;
@@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
return ret;
}
+int maps__load_maps(struct maps *maps)
+{
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ int err = 0;
+
+ if (!maps)
+ return 0;
+
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ if (nr_maps == 0) {
+ up_read(maps__lock(maps));
+ return 0;
+ }
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (!maps_copy) {
+ up_read(maps__lock(maps));
+ return -ENOMEM;
+ }
+ for (unsigned int i = 0; i < nr_maps; i++)
+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
+ up_read(maps__lock(maps));
+
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ if (map__load(maps_copy[i]) < 0) {
+ pr_warning("Failed to load map %s\n", map__dso(maps_copy[i])->name);
+ err = -1;
+ }
+ map__put(maps_copy[i]);
+ }
+ free(maps_copy);
+ return err;
+}
+
void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
{
struct map **maps_by_address;
@@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
return result;
}
-struct maps__find_symbol_by_name_args {
- struct map **mapp;
- const char *name;
- struct symbol *sym;
-};
-
-static int maps__find_symbol_by_name_cb(struct map *map, void *data)
+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
{
- struct maps__find_symbol_by_name_args *args = data;
+ struct map **maps_copy;
+ unsigned int nr_maps;
+ struct symbol *sym = NULL;
- args->sym = map__find_symbol_by_name(map, args->name);
- if (!args->sym)
- return 0;
+ if (!maps)
+ return NULL;
- if (!map__contains_symbol(map, args->sym)) {
- args->sym = NULL;
- return 0;
+ /*
+ * First, ensure all maps are loaded. We pre-load them outside of any
+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
+ */
+ maps__load_maps(maps);
+
+ /*
+ * Create a local snapshot of the maps while holding the read lock.
+ * This prevents deadlocking if iteration triggers further map insertions.
+ */
+ down_read(maps__lock(maps));
+ nr_maps = maps__nr_maps(maps);
+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
+ if (maps_copy) {
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps__maps_by_address(maps)[i];
+
+ maps_copy[i] = map__get(map);
+ }
}
+ up_read(maps__lock(maps));
- if (args->mapp != NULL)
- *args->mapp = map__get(map);
- return 1;
-}
+ if (!maps_copy)
+ return NULL;
-struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
-{
- struct maps__find_symbol_by_name_args args = {
- .mapp = mapp,
- .name = name,
- .sym = NULL,
- };
+ for (unsigned int i = 0; i < nr_maps; i++) {
+ struct map *map = maps_copy[i];
+
+ sym = map__find_symbol_by_name(map, name);
+ if (sym && map__contains_symbol(map, sym)) {
+ if (mapp)
+ *mapp = map__get(map);
+ break;
+ }
+ sym = NULL;
+ }
+
+ for (unsigned int i = 0; i < nr_maps; i++)
+ map__put(maps_copy[i]);
- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
- return args.sym;
+ free(maps_copy);
+ return sym;
}
int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 5b80b199685e..4ec9b7453a3b 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, void *dwfl);
size_t maps__fprintf(struct maps *maps, FILE *fp);
+int maps__load_maps(struct maps *maps);
int maps__insert(struct maps *maps, struct map *map);
void maps__remove(struct maps *maps, struct map *map);
+int maps__mutate_mapping(struct maps *maps, struct map *map,
+ int (*mutate_cb)(struct map *map, void *data), void *data);
struct map *maps__find(struct maps *maps, u64 addr);
struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 186e6d92ac3d..d1e93c0556dd 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap)
void __weak arch__sym_update(struct symbol *s __maybe_unused,
GElf_Sym *sym __maybe_unused) { }
+struct remap_kernel_ctx {
+ u64 sh_addr;
+ u64 sh_size;
+ u64 sh_offset;
+ struct kmap *kmap;
+};
+
+static int remap_kernel_cb(struct map *map, void *data)
+{
+ struct remap_kernel_ctx *ctx = data;
+
+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
+ map__set_end(map, map__start(map) + ctx->sh_size);
+ map__set_pgoff(map, ctx->sh_offset);
+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
+ return 0;
+}
+
static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
GElf_Sym *sym, GElf_Shdr *shdr,
struct maps *kmaps, struct kmap *kmap,
@@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
* map to the kernel dso.
*/
if (*remap_kernel && dso__kernel(dso) && !kmodule) {
+ struct remap_kernel_ctx ctx = {
+ .sh_addr = shdr->sh_addr,
+ .sh_size = shdr->sh_size,
+ .sh_offset = shdr->sh_offset,
+ .kmap = kmap
+ };
+
*remap_kernel = false;
- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
- map__set_end(map, map__start(map) + shdr->sh_size);
- map__set_pgoff(map, shdr->sh_offset);
- map__set_mapping_type(map, MAPPING_TYPE__DSO);
- /* Ensure maps are correctly ordered */
- if (kmaps) {
- int err;
- struct map *tmp = map__get(map);
-
- maps__remove(kmaps, map);
- err = maps__insert(kmaps, map);
- map__put(tmp);
- if (err)
- return err;
- }
+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
}
/*
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 0c46b24ee098..2cc911af8c81 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -48,6 +48,13 @@
#include <symbol/kallsyms.h>
#include <sys/utsname.h>
+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
+{
+ map__fixup_start(map);
+ map__fixup_end(map);
+ return 0;
+}
+
static int dso__load_kernel_sym(struct dso *dso, struct map *map);
static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
@@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
free(kallsyms_allocated_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
@@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
if (err > 0)
pr_debug("Using %s for symbols\n", kallsyms_filename);
if (err > 0 && !dso__is_kcore(dso)) {
+ struct maps *kmaps = map__kmaps(map);
+
dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
dso__set_long_name(dso, machine->mmap_name, false);
- map__fixup_start(map);
- map__fixup_end(map);
+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
}
return err;
--
2.54.0.1032.g2f8565e1d1-goog
If perf.data files are taken from one machine to another they may
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add an aslr option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
This patch introduces the core ASLR remapping tool infrastructure and
implements remapping/tracking for metadata events (MMAP, MMAP2, COMM,
FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without
remapping for now.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
---
tools/perf/builtin-inject.c | 57 ++-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 689 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 37 ++
4 files changed, 782 insertions(+), 2 deletions(-)
create mode 100644 tools/perf/util/aslr.c
create mode 100644 tools/perf/util/aslr.h
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 75ffe31d03fe..65c7eccccf4d 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -8,6 +8,7 @@
*/
#include "builtin.h"
+#include "util/aslr.h"
#include "util/color.h"
#include "util/dso.h"
#include "util/vdso.h"
@@ -124,6 +125,7 @@ struct perf_inject {
bool in_place_update_dry_run;
bool copy_kcore_dir;
bool convert_callchain;
+ bool aslr;
const char *input_name;
struct perf_data output;
u64 bytes_written;
@@ -242,8 +244,25 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
if (!inject->output.is_pipe)
return 0;
- if (!inject->itrace_synth_opts.set)
+ if (!inject->itrace_synth_opts.set) {
+ if (inject->aslr) {
+ union perf_event *stripped_event = malloc(event->header.size);
+ int err;
+
+ if (!stripped_event)
+ return -ENOMEM;
+ memcpy(stripped_event, event, event->header.size);
+ stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
+ stripped_event->attr.attr.bp_addr = 0;
+
+ err = perf_event__repipe_synth(tool, stripped_event);
+ free(stripped_event);
+ return err;
+ }
return perf_event__repipe_synth(tool, event);
+ }
if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZE_VER0) {
pr_err("Attribute event size %u is too small\n", event->header.size);
@@ -276,6 +295,8 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
+ if (inject->aslr)
+ attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2595,6 +2616,8 @@ static int __cmd_inject(struct perf_inject *inject)
}
}
+
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2704,6 +2727,8 @@ int cmd_inject(int argc, const char **argv)
unwind__option),
OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
"Generate callchains using DWARF and drop register/stack data"),
+ OPT_BOOLEAN(0, "aslr", &inject.aslr,
+ "Remap virtual memory addresses similar to ASLR"),
OPT_END()
};
const char * const inject_usage[] = {
@@ -2711,6 +2736,7 @@ int cmd_inject(int argc, const char **argv)
NULL
};
bool ordered_events;
+ struct perf_tool *tool = &inject.tool;
if (!inject.itrace_synth_opts.set) {
/* Disable eager loading of kernel symbols that adds overhead to perf inject. */
@@ -2731,6 +2757,11 @@ int cmd_inject(int argc, const char **argv)
if (argc)
usage_with_options(inject_usage, options);
+ if (inject.aslr && inject.convert_callchain) {
+ pr_err("Error: --aslr and --convert-callchain are mutually exclusive features.\n");
+ return -EINVAL;
+ }
+
if (inject.strip && !inject.itrace_synth_opts.set) {
pr_err("--strip option requires --itrace option\n");
return -1;
@@ -2824,12 +2855,21 @@ int cmd_inject(int argc, const char **argv)
inject.tool.schedstat_domain = perf_event__repipe_op2_synth;
inject.tool.dont_split_sample_group = true;
inject.tool.merge_deferred_callchains = false;
- inject.session = __perf_session__new(&data, &inject.tool,
+ if (inject.aslr) {
+ tool = aslr_tool__new(&inject.tool);
+ if (!tool) {
+ ret = -ENOMEM;
+ goto out_close_output;
+ }
+ }
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
if (IS_ERR(inject.session)) {
ret = PTR_ERR(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
goto out_close_output;
}
@@ -2923,12 +2963,25 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+ }
+
guest_session__exit(&inject.guest_session);
out_delete:
strlist__delete(inject.known_build_ids);
zstd_fini(&(inject.session->zstd_data));
perf_session__delete(inject.session);
+ if (inject.aslr)
+ aslr_tool__delete(tool);
out_close_output:
if (!inject.in_place_update)
perf_data__close(&inject.output);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 4bbc78b1f741..19994e026ae5 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -6,6 +6,7 @@ perf-util-y += arm64-frame-pointer-unwind-support.o
perf-util-y += addr2line.o
perf-util-y += addr_location.o
perf-util-y += annotate.o
+perf-util-y += aslr.o
perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
new file mode 100644
index 000000000000..375a0355f281
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,689 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "aslr.h"
+
+#include "addr_location.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "tool.h"
+#include "session.h"
+#include "data.h"
+#include "dso.h"
+
+#include <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <linux/zalloc.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+/**
+ * struct remap_addresses_key - Key for mapping original addresses to remapped ones.
+ * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the mapping.
+ * @invariant: Unique offset invariant within the VMA (Virtual Memory Area).
+ * Calculated as `start - pgoff`. This value remains constant when
+ * perf's internal `maps__fixup_overlap_and_insert` splits a map into
+ * fragmented VMA pieces due to overlapping events, allowing us to
+ * resolve split maps consistently back to the original VMA.
+ * @pid: Process ID associated with the mapping.
+ */
+struct remap_addresses_key {
+ struct machine *machine;
+ struct dso *dso;
+ u64 invariant;
+ pid_t pid;
+};
+
+struct aslr_mapping {
+ struct list_head node;
+ u64 orig_start;
+ u64 len;
+ u64 remap_start;
+};
+
+struct process_top_address {
+ u64 remapped_max;
+ u64 orig_last_end;
+};
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machines: The machines with the input, not remapped, virtual address layout. */
+ struct machines machines;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+ /** @remap_addresses: mapping from remap_addresses_key to remapped address. */
+ struct hashmap remap_addresses;
+ /** @top_addresses: mapping from process to max remapped address. */
+ struct hashmap top_addresses;
+};
+
+static const pid_t kernel_pid = -1;
+
+/* Start remapping user processes from a small non-zero offset. */
+static const u64 user_space_start = 0x200000;
+static const u64 kernel_space_start = 0xffff800010000000;
+
+static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key = (struct remap_addresses_key *)_key;
+
+ return (size_t)key->machine ^ (size_t)key->dso ^ key->invariant ^ key->pid;
+}
+
+static bool remap_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct remap_addresses_key *key1 = (struct remap_addresses_key *)_key1;
+ struct remap_addresses_key *key2 = (struct remap_addresses_key *)_key2;
+
+ return key1->machine == key2->machine &&
+ RC_CHK_EQUAL(key1->dso, key2->dso) &&
+ key1->invariant == key2->invariant &&
+ key1->pid == key2->pid;
+}
+
+struct top_addresses_key {
+ struct machine *machine;
+ pid_t pid;
+};
+
+static size_t top_addresses__hash(long _key, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key = (struct top_addresses_key *)_key;
+
+ return (size_t)key->machine ^ key->pid;
+}
+
+static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe_unused)
+{
+ struct top_addresses_key *key1 = (struct top_addresses_key *)_key1;
+ struct top_addresses_key *key2 = (struct top_addresses_key *)_key2;
+
+ return key1->machine == key2->machine && key1->pid == key2->pid;
+}
+
+static u64 round_up_to_page_size(u64 addr)
+{
+ return (addr + page_size - 1) & ~((u64)page_size - 1);
+}
+
+struct aslr_machine_priv {
+ bool kernel_maps_loaded;
+};
+
+static int aslr_tool__preload_kernel_maps(struct machine *machine)
+{
+ struct aslr_machine_priv *mpriv = machine->priv;
+
+ if (!mpriv) {
+ mpriv = zalloc(sizeof(*mpriv));
+ if (!mpriv)
+ return -ENOMEM;
+ machine->priv = mpriv;
+ }
+
+ if (!mpriv->kernel_maps_loaded) {
+ struct maps *kmaps = machine__kernel_maps(machine);
+
+ if (kmaps) {
+ int err = maps__load_maps(kmaps);
+
+ if (err < 0) {
+ pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n",
+ machine->pid);
+ return err;
+ }
+ }
+ mpriv->kernel_maps_loaded = true;
+ }
+ return 0;
+}
+
+static void aslr_tool__free_machine_priv(struct machine *machine)
+{
+ free(machine->priv);
+ machine->priv = NULL;
+}
+
+static void aslr_tool__destroy_machines_priv(struct machines *machines)
+{
+ struct rb_node *nd;
+
+ aslr_tool__free_machine_priv(&machines->host);
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ aslr_tool__free_machine_priv(machine);
+ }
+}
+
+static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode, u64 start,
+ u64 len, u64 pgoff)
+{
+ /* Address location for dso lookup. */
+ struct addr_location al;
+ /* Original ASLR address based key for the remap table. */
+ struct remap_addresses_key remap_key;
+ /* The address in the ASLR sanitized address space less pg_off. */
+ u64 *remapped_invariant_ptr;
+ /* Key for the maximum address in a process. */
+ struct top_addresses_key top_addr_key;
+ /* Value in top address table. */
+ struct process_top_address *top = NULL;
+ /* Address in ASLR sanitized address space. */
+ u64 remap_addr;
+ /* Potentially allocated remap table key. */
+ struct remap_addresses_key *new_remap_key = NULL;
+ /*
+ * Potentially allocated remap table key.
+ * TODO: Avoid allocation necessary for perf 32-bit binary support.
+ */
+ u64 *new_remap_val = NULL;
+ int err;
+
+ if (!aslr_thread)
+ return 0;
+
+ /* The key to look up an incoming address to the outgoing value. */
+ addr_location__init(&al);
+ remap_key.machine = maps__machine(thread__maps(aslr_thread));
+ remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+ if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+ struct dso *dso = map__dso(al.map);
+ const char *dso_name = dso ? dso__long_name(dso) : NULL;
+
+ remap_key.dso = dso;
+ if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name))
+ remap_key.invariant = map__start(al.map) - map__pgoff(al.map);
+ else
+ remap_key.invariant = map__start(al.map);
+ } else {
+ remap_key.dso = NULL;
+ remap_key.invariant = start;
+ }
+
+ /* The key to look up top allocated address. */
+ top_addr_key.machine = remap_key.machine;
+ top_addr_key.pid = remap_key.pid;
+
+ if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant_ptr)) {
+ /* Mmap already exists. */
+ u64 calculated_max;
+
+ if (al.map) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (start - map__start(al.map));
+ } else {
+ remap_addr = *remapped_invariant_ptr + pgoff;
+ }
+
+ calculated_max = remap_addr + len;
+
+ /* See if top mapping was expanded. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ if (calculated_max > top->remapped_max)
+ top->remapped_max = calculated_max;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+ }
+ /* No mmap, create an entry from the top address. */
+ if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) {
+ /* Current max allocated mmap address within the process. */
+ remap_addr = top->remapped_max;
+
+ if (start == top->orig_last_end) {
+ /* Contiguous mapping, do not add 1 page gap! */
+ remap_addr = round_up_to_page_size(remap_addr);
+ } else {
+ /* Give 1 page gap from current max page. */
+ remap_addr = round_up_to_page_size(remap_addr);
+ remap_addr += page_size;
+ }
+ top->orig_last_end = start + len;
+ if (remap_addr + len > top->remapped_max)
+ top->remapped_max = remap_addr + len;
+ } else {
+ /* First address of the process, allocate key and first top address. */
+ struct top_addresses_key *tk;
+ struct process_top_address *top_val;
+
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_space_start : user_space_start;
+ remap_addr = round_up_to_page_size(remap_addr);
+
+ tk = malloc(sizeof(*tk));
+ top_val = malloc(sizeof(*top_val));
+ if (!tk || !top_val) {
+ err = -ENOMEM;
+ } else {
+ *tk = top_addr_key;
+ top_val->remapped_max = remap_addr + len;
+ top_val->orig_last_end = start + len;
+ err = hashmap__insert(&aslr->top_addresses, tk, top_val,
+ HASHMAP_ADD, NULL, NULL);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR process top address %m\n");
+ free(tk);
+ free(top_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ }
+ /* Create rmeapping entry. */
+ new_remap_key = malloc(sizeof(*new_remap_key));
+ new_remap_val = malloc(sizeof(u64));
+ if (!new_remap_key || !new_remap_val) {
+ err = -ENOMEM;
+ } else {
+ *new_remap_key = remap_key;
+ new_remap_key->dso = dso__get(remap_key.dso);
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ if (al.map) {
+ *new_remap_val = remap_addr -
+ (start - map__start(al.map)) -
+ map__pgoff(al.map);
+ } else {
+ *new_remap_val = remap_addr;
+ }
+ } else {
+ *new_remap_val = remap_addr - (al.map ? map__pgoff(al.map) : pgoff);
+ }
+ err = hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_val);
+ if (err)
+ dso__put(new_remap_key->dso);
+ }
+ if (err) {
+ errno = -err;
+ pr_err("Failure to add ASLR remapping %m\n");
+ free(new_remap_key);
+ free(new_remap_val);
+ addr_location__exit(&al);
+ return 0;
+ }
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static int aslr_tool__process_mmap(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap.pid, event->mmap.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap.start,
+ event->mmap.len,
+ event->mmap.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap.pgoff = new_event->mmap.start;
+ err = delegate->mmap(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_mmap2(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ u8 cpumode;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_mmap2(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, event->mmap2.pid, event->mmap2.tid);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__findnew_mapping(aslr, thread, cpumode,
+ event->mmap2.start,
+ event->mmap2.len,
+ event->mmap2.pgoff);
+ if (cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ new_event->mmap2.pgoff = new_event->mmap2.start;
+ err = delegate->mmap2(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_comm(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_comm(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->comm(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_fork(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_fork(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->fork(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_exit(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_exit(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ return delegate->exit(delegate, event, sample, machine);
+}
+
+static int aslr_tool__process_text_poke(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ /* Drop in case the instruction encodes an ASLR revealing address. */
+ return 0;
+}
+
+static int aslr_tool__process_ksymbol(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ union perf_event *new_event;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ int err;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+ new_event = (union perf_event *)aslr->event_copy;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ err = perf_event__process_ksymbol(tool, event, sample, aslr_machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(aslr_machine, kernel_pid, 0);
+ if (!thread)
+ return -ENOMEM;
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__findnew_mapping(aslr, thread,
+ PERF_RECORD_MISC_KERNEL,
+ event->ksymbol.addr,
+ event->ksymbol.len,
+ /*pgoff=*/0);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct perf_tool *delegate = aslr->tool.delegate;
+
+ return delegate->sample(delegate, event, sample, machine);
+}
+
+static int skipn(int fd, off_t n)
+{
+ char buf[4096];
+ ssize_t ret;
+
+ while (n > 0) {
+ ret = read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf)));
+ if (ret <= 0)
+ return ret;
+ n -= ret;
+ }
+
+ return 0;
+}
+
+static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.\n");
+ if (perf_data__is_pipe(session->data)) {
+ /* Copy behavior of the stub by reading all pipe data. */
+ int err = skipn(perf_data__fd(session->data), event->auxtrace.size);
+
+ if (err < 0)
+ return err;
+ }
+ return event->auxtrace.size;
+}
+
+static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool __maybe_unused,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused)
+{
+ return 0;
+}
+
+static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+{
+ delegate_tool__init(&aslr->tool, delegate);
+ aslr->tool.tool.ordered_events = true;
+
+ machines__init(&aslr->machines);
+
+ hashmap__init(&aslr->remap_addresses,
+ remap_addresses__hash, remap_addresses__equal,
+ /*ctx=*/NULL);
+ hashmap__init(&aslr->top_addresses,
+ top_addresses__hash, top_addresses__equal,
+ /*ctx=*/NULL);
+
+ aslr->tool.tool.sample = aslr_tool__process_sample;
+ /* read - reads a counter, okay to delegate. */
+ aslr->tool.tool.mmap = aslr_tool__process_mmap;
+ aslr->tool.tool.mmap2 = aslr_tool__process_mmap2;
+ aslr->tool.tool.comm = aslr_tool__process_comm;
+ aslr->tool.tool.fork = aslr_tool__process_fork;
+ aslr->tool.tool.exit = aslr_tool__process_exit;
+ /* namesspaces, cgroup, lost, lost_sample, aux, */
+ /* itrace_start, aux_output_hw_id, context_switch, throttle, unthrottle */
+ /* - no virtual addresses. */
+ aslr->tool.tool.ksymbol = aslr_tool__process_ksymbol;
+ /* bpf - no virtual address. */
+ aslr->tool.tool.text_poke = aslr_tool__process_text_poke;
+ /*
+ * event_update, tracing_data, finished_round, build_id, id_index,
+ * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map,
+ * stat_config, stat, feature, finished_init, bpf_metadata, compressed,
+ * auxtrace - no virtual addresses.
+ */
+ aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
+ aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
+ aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = zalloc(sizeof(*aslr));
+
+ if (!aslr)
+ return NULL;
+
+ aslr_tool__init(aslr, delegate);
+ return &aslr->tool.tool;
+}
+
+void aslr_tool__delete(struct perf_tool *tool)
+{
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ if (!tool)
+ return;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
+
+ if (key)
+ dso__put(key->dso);
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ aslr_tool__destroy_machines_priv(&aslr->machines);
+ machines__destroy_kernel_maps(&aslr->machines);
+ machines__exit(&aslr->machines);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..a9b90bf29540
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+#include <linux/perf_event.h>
+
+#define ASLR_SUPPORTED_SAMPLE_TYPE ( \
+ PERF_SAMPLE_IDENTIFIER | \
+ PERF_SAMPLE_IP | \
+ PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | \
+ PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | \
+ PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_READ | \
+ PERF_SAMPLE_CALLCHAIN | \
+ PERF_SAMPLE_RAW | \
+ PERF_SAMPLE_BRANCH_STACK | \
+ PERF_SAMPLE_STACK_USER | \
+ PERF_SAMPLE_WEIGHT_TYPE | \
+ PERF_SAMPLE_DATA_SRC | \
+ PERF_SAMPLE_TRANSACTION | \
+ PERF_SAMPLE_PHYS_ADDR | \
+ PERF_SAMPLE_CGROUP | \
+ PERF_SAMPLE_DATA_PAGE_SIZE | \
+ PERF_SAMPLE_CODE_PAGE_SIZE | \
+ PERF_SAMPLE_AUX)
+
+struct perf_tool;
+struct evsel;
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
+void aslr_tool__delete(struct perf_tool *aslr);
+
+#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
Add the sample address remapping logic to the ASLR tool. This patch
implements aslr_tool__process_sample, which parses sample events,
remaps IPs, ADDRs, callchains, and branch stacks using the mappings
collected from metadata events, and drops potentially leaking raw,
register, stack, physical address, and aux samples.
Also adds the aslr_tool__remap_address helper function.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 449 +++++++++++++++++++++++++++++++++++++++-
tools/perf/util/evsel.c | 6 +-
tools/perf/util/evsel.h | 10 +-
3 files changed, 456 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 375a0355f281..6d081dfae480 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -109,6 +109,60 @@ static u64 round_up_to_page_size(u64 addr)
return (addr + page_size - 1) & ~((u64)page_size - 1);
}
+static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 addr)
+{
+ struct addr_location al;
+ struct remap_addresses_key key;
+ u64 *remapped_invariant_ptr = NULL;
+ u64 remap_addr = 0;
+ u8 effective_cpumode = cpumode;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&al);
+ if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+ /*
+ * If lookup fails with specified cpumode, try fallback to the other space
+ * to be robust against bad cpumode in samples.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_USER;
+ else if (cpumode == PERF_RECORD_MISC_USER)
+ effective_cpumode = PERF_RECORD_MISC_KERNEL;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_USER;
+ else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
+ effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.machine = maps__machine(thread__maps(aslr_thread));
+ key.dso = map__dso(al.map);
+ key.invariant = map__start(al.map) - map__pgoff(al.map);
+ key.pid = (effective_cpumode == PERF_RECORD_MISC_KERNEL ||
+ effective_cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
+ kernel_pid : thread__pid(aslr_thread);
+
+ if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr)) {
+ remap_addr = *remapped_invariant_ptr + map__pgoff(al.map) +
+ (addr - map__start(al.map));
+ } else {
+ pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%lx) for pid=%d\n",
+ addr, map__start(al.map), map__size(al.map), key.pid);
+ }
+
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
struct aslr_machine_priv {
bool kernel_maps_loaded;
};
@@ -554,13 +608,400 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
- struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
- struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
- struct perf_tool *delegate = aslr->tool.delegate;
+ struct evsel *evsel = sample->evsel;
+ struct delegate_tool *del_tool;
+ struct aslr_tool *aslr;
+ struct perf_tool *delegate;
+ int ret;
+ u64 sample_type;
+ struct thread *thread;
+ struct machine *aslr_machine;
+ __u64 max_i;
+ __u64 max_j;
+ union perf_event *new_event;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode;
+ u64 addr;
+ size_t i;
+ size_t j;
+
+ del_tool = container_of(tool, struct delegate_tool, tool);
+ aslr = container_of(del_tool, struct aslr_tool, tool);
+ delegate = aslr->tool.delegate;
+
+ if (evsel__is_dummy_event(evsel))
+ return delegate->sample(delegate, event, sample, machine);
+
+ ret = -EFAULT;
+ sample_type = evsel->core.attr.sample_type;
+ max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
+ max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
+ new_event = (union perf_event *)aslr->event_copy;
+ cpumode = sample->cpumode;
+ i = 0;
+ j = 0;
+
+ aslr_machine = machines__findnew(&aslr->machines, machine->pid);
+ if (!aslr_machine)
+ return -ENOMEM;
+ if (aslr_tool__preload_kernel_maps(aslr_machine) < 0)
+ return -ENOMEM;
+
+ thread = machine__findnew_thread(aslr_machine, sample->pid, sample->tid);
+
+ if (!thread)
+ return -ENOMEM;
- return delegate->sample(delegate, event, sample, machine);
+ if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64))
+ goto out_put;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+#define CHECK_BOUNDS(required_i, required_j) \
+ (i + (required_i) > max_i || j + (required_j) > max_j)
+
+#define COPY_U64() \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = in_array[i++]; \
+ } while (0)
+
+#define REMAP_U64(addr_field) \
+ do { \
+ if (CHECK_BOUNDS(1, 1)) { \
+ ret = -EFAULT; \
+ goto out_put; \
+ } \
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr_field); \
+ i++; \
+ } while (0)
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_IP)
+ REMAP_U64(sample->ip);
+ if (sample_type & PERF_SAMPLE_TID)
+ COPY_U64(); /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ COPY_U64(); /* time */
+ if (sample_type & PERF_SAMPLE_ADDR)
+ REMAP_U64(sample->addr);
+ if (sample_type & PERF_SAMPLE_ID)
+ COPY_U64(); /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ COPY_U64(); /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ COPY_U64(); /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ COPY_U64(); /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ } else {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COPY_U64(); /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COPY_U64(); /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ COPY_U64(); /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ COPY_U64(); /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ COPY_U64(); /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ addr = in_array[i++];
+ if (addr >= PERF_CONTEXT_MAX) {
+ out_array[j++] = addr;
+ switch (addr) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case PERF_CONTEXT_GUEST:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ break;
+ case PERF_CONTEXT_GUEST_USER:
+ cpumode = PERF_RECORD_MISC_GUEST_USER;
+ break;
+ case PERF_CONTEXT_USER_DEFERRED:
+ if (cntr + 1 >= nr) {
+ pr_debug("Truncated callchain deferred cookie context\n");
+ ret = 0;
+ goto out_put;
+ }
+ /*
+ * Immediately followed by a 64-bit
+ * stitching cookie. Skip/Copy it!
+ */
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j++] = in_array[i++];
+ cntr++;
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ ret = 0;
+ goto out_put;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+ size_t u64_words = (bytes + 7) / 8;
+
+ if (i + u64_words > max_i || j + u64_words > max_j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += u64_words;
+ j += u64_words;
+ /*
+ * TODO: certain raw samples can be remapped, such as
+ * tracepoints by examining their fields.
+ */
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ u64 nr;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ nr = out_array[j++];
+ i++;
+
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ COPY_U64(); /* hw_idx */
+
+ if (nr > (ULLONG_MAX / 3)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ if (nr * 3 > max_i - i || nr * 3 > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* from */
+ out_array[j++] = aslr_tool__remap_address(aslr, thread,
+ sample->cpumode,
+ in_array[i++]); /* to */
+ out_array[j++] = in_array[i++]; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (nr > max_i - i || nr > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ COPY_U64(); /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ COPY_U64(); /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ COPY_U64(); /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (CHECK_BOUNDS(1, 0)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ /* abi */
+ COPY_U64();
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ COPY_U64(); /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ COPY_U64(); /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ COPY_U64(); /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ COPY_U64(); /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (CHECK_BOUNDS(1, 1)) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ out_array[j] = in_array[i];
+ size = out_array[j++];
+ i++;
+ if (size > 0) {
+ size_t u64_words = size / 8 + (size % 8 ? 1 : 0);
+
+ if (u64_words > max_i - i || u64_words > max_j - j) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ memcpy(&out_array[j], &in_array[i], size);
+ if (size % 8) {
+ size_t pad = 8 - (size % 8);
+
+ memset(((char *)&out_array[j]) + size, 0, pad);
+ }
+ i += u64_words;
+ j += u64_words;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ ret = 0;
+ goto out_put;
+ }
+
+ new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+
+ if (ret) {
+ perf_sample__exit(&new_sample);
+ goto out_put;
+ }
+
+ new_sample.evsel = evsel;
+ ret = delegate->sample(delegate, new_event, &new_sample, machine);
+ perf_sample__exit(&new_sample);
+
+out_put:
+ thread__put(thread);
+ return ret;
}
+#undef CHECK_BOUNDS
+#undef COPY_U64
+#undef REMAP_U64
+
static int skipn(int fd, off_t n)
{
char buf[4096];
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 34c03f47a913..05fa0010c858 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *data)
return -EFAULT;
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *data)
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap)
{
u64 type = evsel->core.attr.sample_type;
- bool swapped = evsel->needs_swap;
+ bool swapped = needs_swap;
const __u64 *array;
u16 max_size = event->header.size;
const void *endp = (void *)event + max_size;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 8178858d168a..8009be22cc3f 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evsel *evsel, int cpu_map_idx
return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true);
}
-int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
- struct perf_sample *sample);
+int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data, bool needs_swap);
+
+static inline int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ return __evsel__parse_sample(evsel, event, data, evsel->needs_swap);
+}
int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event,
u64 *timestamp);
--
2.54.0.1032.g2f8565e1d1-goog
Add a new shell test to verify the
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for piped into .
- Callchain address remapping.
- Consistency of output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (utilizing a dedicated kernel-intensive VFS
dd workload
to guarantee continuous timer interrupts sampling flow inside kernel
privilege states).
- Kernel report consistency with address normalization.
The test suite is hardened with global 'set -o pipefail' assertions to
catch pipeline failures, stream-consuming awk processors to handle
SIGPIPE signals, and a dedicated pipe output scenario validating raw
'perf inject -o -' stdout streams.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 464 ++++++++++++++++++++++++++
1 file changed, 464 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..d8ded16ba905
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,464 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/zero of=/dev/null bs=1M count=500"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup ${err}
+exit $err
--
2.54.0.1032.g2f8565e1d1-goog
Refactor the ASLR tool to strip out only the register dump payload
by masking out the relevant perf_event_attr fields when the delegated
tool is handling the data. struct aslr_evsel_priv maintains the
original perf_event_attr values and is looked up via the evsel_orig_attrs
hashmap.
This allows us to keep samples that would otherwise be dropped because
they contain registers, while still obfuscating the registers.
Co-developed-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Gabriel Marin <gmx@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-inject.c | 46 ++++--
tools/perf/tests/shell/inject_aslr.sh | 55 +++++++
tools/perf/util/aslr.c | 208 +++++++++++++++++++++-----
tools/perf/util/aslr.h | 4 +
4 files changed, 263 insertions(+), 50 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 65c7eccccf4d..de315bb334b3 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -253,6 +253,12 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
return -ENOMEM;
memcpy(stripped_event, event, event->header.size);
stripped_event->attr.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_user) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_user = 0;
+ if (stripped_event->attr.attr.size >=
+ (offsetof(struct perf_event_attr, sample_regs_intr) + sizeof(u64)))
+ stripped_event->attr.attr.sample_regs_intr = 0;
if (stripped_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
stripped_event->attr.attr.bp_addr = 0;
@@ -295,8 +301,13 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
attr.size = sizeof(struct perf_event_attr);
attr.sample_type &= ~PERF_SAMPLE_AUX;
- if (inject->aslr)
+ if (inject->aslr) {
attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ if (attr.type == PERF_TYPE_BREAKPOINT)
+ attr.bp_addr = 0;
+ attr.sample_regs_user = 0;
+ attr.sample_regs_intr = 0;
+ }
if (inject->itrace_synth_opts.add_last_branch) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -2618,6 +2629,9 @@ static int __cmd_inject(struct perf_inject *inject)
+ if (inject->aslr)
+ aslr_tool__strip_evlist(inject->session->tool, session->evlist);
+
session->header.data_offset = output_data_offset;
session->header.data_size = inject->bytes_written;
perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
@@ -2876,6 +2890,18 @@ int cmd_inject(int argc, const char **argv)
if (zstd_init(&(inject.session->zstd_data), 0) < 0)
pr_warning("Decompression initialization failed.\n");
+ if (inject.aslr) {
+ struct evsel *evsel;
+
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ ret = aslr_tool__cache_orig_attrs(tool, evsel);
+ if (ret) {
+ pr_err("Failed to cache original attributes: %d\n", ret);
+ goto out_delete;
+ }
+ }
+ }
+
/* Save original section info before feature bits change */
ret = save_section_info(&inject);
if (ret)
@@ -2894,10 +2920,17 @@ int cmd_inject(int argc, const char **argv)
* the input.
*/
if (!data.is_pipe) {
+ if (inject.aslr)
+ aslr_tool__strip_evlist(tool, inject.session->evlist);
+
ret = perf_event__synthesize_for_pipe(&inject.tool,
inject.session,
&inject.output,
perf_event__repipe);
+
+ if (inject.aslr)
+ aslr_tool__restore_evlist(tool, inject.session->evlist);
+
if (ret < 0)
goto out_delete;
}
@@ -2963,17 +2996,6 @@ int cmd_inject(int argc, const char **argv)
ret = __cmd_inject(&inject);
- if (inject.aslr) {
- struct evsel *evsel;
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
- evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
-
- if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
- evsel->core.attr.bp_addr = 0;
- }
- }
-
guest_session__exit(&inject.guest_session);
out_delete:
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
index d8ded16ba905..21d306a0ff2f 100755
--- a/tools/perf/tests/shell/inject_aslr.sh
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -450,6 +450,60 @@ test_kernel_report_aslr() {
fi
}
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "PERF_SAMPLE_REGS_USER" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
test_basic_aslr
test_pipe_aslr
test_callchain_aslr
@@ -459,6 +513,7 @@ test_pipe_out_report_aslr
test_dropped_samples
test_kernel_aslr
test_kernel_report_aslr
+test_regs_stripping
cleanup ${err}
exit $err
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 6d081dfae480..f9c00caf79b7 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -5,6 +5,7 @@
#include "debug.h"
#include "event.h"
#include "evsel.h"
+#include "evlist.h"
#include "machine.h"
#include "map.h"
#include "thread.h"
@@ -16,6 +17,7 @@
#include <internal/lib.h> /* page_size */
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
@@ -43,6 +45,22 @@ struct aslr_mapping {
u64 remap_start;
};
+struct aslr_evsel_priv {
+ u64 orig_sample_type;
+ u64 orig_sample_regs_user;
+ u64 orig_sample_regs_intr;
+};
+
+static size_t evsel_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
struct process_top_address {
u64 remapped_max;
u64 orig_last_end;
@@ -58,6 +76,11 @@ struct aslr_tool {
struct hashmap remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
+ /**
+ * @evsel_orig_attrs: mapping from evsel pointer to its original
+ * unstripped sample_type and registers bitmasks.
+ */
+ struct hashmap evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -613,6 +636,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
struct aslr_tool *aslr;
struct perf_tool *delegate;
int ret;
+ int orig_sample_size;
u64 sample_type;
struct thread *thread;
struct machine *aslr_machine;
@@ -625,7 +649,10 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
u64 addr;
size_t i;
size_t j;
-
+ struct aslr_evsel_priv *priv = NULL;
+ u64 orig_sample_type;
+ u64 orig_regs_user;
+ u64 orig_regs_intr;
del_tool = container_of(tool, struct delegate_tool, tool);
aslr = container_of(del_tool, struct aslr_tool, tool);
delegate = aslr->tool.delegate;
@@ -634,7 +661,23 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
return delegate->sample(delegate, event, sample, machine);
ret = -EFAULT;
- sample_type = evsel->core.attr.sample_type;
+
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ orig_sample_type = priv->orig_sample_type;
+ orig_regs_user = priv->orig_sample_regs_user;
+ orig_regs_intr = priv->orig_sample_regs_intr;
+ } else {
+ orig_sample_type = evsel->core.attr.sample_type;
+ orig_regs_user = evsel->core.attr.sample_regs_user;
+ orig_regs_intr = evsel->core.attr.sample_regs_intr;
+ }
+
+ orig_sample_size = evsel->sample_size;
+
+ sample_type = orig_sample_type;
+ sample_type &= ~PERF_SAMPLE_REGS_USER;
+ sample_type &= ~PERF_SAMPLE_REGS_INTR;
+
max_i = (event->header.size - sizeof(struct perf_event_header)) / sizeof(__u64);
max_j = (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / sizeof(__u64);
new_event = (union perf_event *)aslr->event_copy;
@@ -683,25 +726,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
i++; \
} while (0)
- if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (orig_sample_type & PERF_SAMPLE_IDENTIFIER)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_IP)
+ if (orig_sample_type & PERF_SAMPLE_IP)
REMAP_U64(sample->ip);
- if (sample_type & PERF_SAMPLE_TID)
+ if (orig_sample_type & PERF_SAMPLE_TID)
COPY_U64(); /* pid, tid */
- if (sample_type & PERF_SAMPLE_TIME)
+ if (orig_sample_type & PERF_SAMPLE_TIME)
COPY_U64(); /* time */
- if (sample_type & PERF_SAMPLE_ADDR)
+ if (orig_sample_type & PERF_SAMPLE_ADDR)
REMAP_U64(sample->addr);
- if (sample_type & PERF_SAMPLE_ID)
+ if (orig_sample_type & PERF_SAMPLE_ID)
COPY_U64(); /* id */
- if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (orig_sample_type & PERF_SAMPLE_STREAM_ID)
COPY_U64(); /* stream_id */
- if (sample_type & PERF_SAMPLE_CPU)
+ if (orig_sample_type & PERF_SAMPLE_CPU)
COPY_U64(); /* cpu, res */
- if (sample_type & PERF_SAMPLE_PERIOD)
+ if (orig_sample_type & PERF_SAMPLE_PERIOD)
COPY_U64(); /* period */
- if (sample_type & PERF_SAMPLE_READ) {
+ if (orig_sample_type & PERF_SAMPLE_READ) {
if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
COPY_U64(); /* value */
if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
@@ -735,7 +778,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
}
}
}
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -801,7 +844,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode, addr);
}
}
- if (sample_type & PERF_SAMPLE_RAW) {
+ if (orig_sample_type & PERF_SAMPLE_RAW) {
size_t bytes = sizeof(u32) + sample->raw_size;
size_t u64_words = (bytes + 7) / 8;
@@ -820,7 +863,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) {
u64 nr;
if (CHECK_BOUNDS(1, 1)) {
@@ -865,19 +908,25 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
goto out_put;
}
}
- if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_USER) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping regs user sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_user);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_STACK_USER) {
+ if (orig_sample_type & PERF_SAMPLE_STACK_USER) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -908,39 +957,45 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE)
COPY_U64(); /* perf_sample_weight */
- if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (orig_sample_type & PERF_SAMPLE_DATA_SRC)
COPY_U64(); /* data_src */
- if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (orig_sample_type & PERF_SAMPLE_TRANSACTION)
COPY_U64(); /* transaction */
- if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (orig_sample_type & PERF_SAMPLE_REGS_INTR) {
+ u64 abi;
+
if (CHECK_BOUNDS(1, 0)) {
ret = -EFAULT;
goto out_put;
}
- /* abi */
- COPY_U64();
- /* TODO: can this be less conservative? */
- pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
- ret = 0;
- goto out_put;
+ abi = in_array[i++];
+ if (abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(orig_regs_intr);
+
+ if (nr > max_i - i) {
+ ret = -EFAULT;
+ goto out_put;
+ }
+ i += nr;
+ }
}
- if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) {
COPY_U64(); /* phys_addr */
/* TODO: can this be less conservative? */
pr_debug("Dropping physical address sample as possible ASLR leak\n");
ret = 0;
goto out_put;
}
- if (sample_type & PERF_SAMPLE_CGROUP)
+ if (orig_sample_type & PERF_SAMPLE_CGROUP)
COPY_U64(); /* cgroup */
- if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
COPY_U64(); /* data_page_size */
- if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
COPY_U64(); /* code_page_size */
- if (sample_type & PERF_SAMPLE_AUX) {
+ if (orig_sample_type & PERF_SAMPLE_AUX) {
u64 size;
if (CHECK_BOUNDS(1, 1)) {
@@ -981,10 +1036,20 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
new_event->sample.header.size = sizeof(struct perf_event_header) + j * sizeof(u64);
+ /* Temporarily override evsel attributes to match the stripped new_event format! */
+ evsel->sample_size = __evsel__sample_size(sample_type);
+ evsel->core.attr.sample_type = sample_type;
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
perf_sample__init(&new_sample, /*all=*/ true);
ret = evsel__parse_sample(evsel, new_event, &new_sample);
if (ret) {
+ /* Restore original attributes immediately if parsing fails */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
perf_sample__exit(&new_sample);
goto out_put;
}
@@ -993,6 +1058,12 @@ static int aslr_tool__process_sample(const struct perf_tool *tool,
ret = delegate->sample(delegate, new_event, &new_sample, machine);
perf_sample__exit(&new_sample);
+ /* Restore original attributes so trace ingestion never desynchronizes! */
+ evsel->sample_size = orig_sample_size;
+ evsel->core.attr.sample_type = orig_sample_type;
+ evsel->core.attr.sample_regs_user = orig_regs_user;
+ evsel->core.attr.sample_regs_intr = orig_regs_intr;
+
out_put:
thread__put(thread);
return ret;
@@ -1059,6 +1130,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
hashmap__init(&aslr->top_addresses,
top_addresses__hash, top_addresses__equal,
/*ctx=*/NULL);
+ hashmap__init(&aslr->evsel_orig_attrs,
+ evsel_hash, evsel_equal,
+ /*ctx=*/NULL);
aslr->tool.tool.sample = aslr_tool__process_sample;
/* read - reads a counter, okay to delegate. */
@@ -1120,11 +1194,69 @@ void aslr_tool__delete(struct perf_tool *tool)
zfree(&cur->pkey);
zfree(&cur->pvalue);
}
+ hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
+ hashmap__clear(&aslr->evsel_orig_attrs);
aslr_tool__destroy_machines_priv(&aslr->machines);
machines__destroy_kernel_maps(&aslr->machines);
machines__exit(&aslr->machines);
free(aslr);
}
+
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
+{
+ struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct aslr_evsel_priv *priv = zalloc(sizeof(*priv));
+ int err;
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->orig_sample_type = evsel->core.attr.sample_type;
+ priv->orig_sample_regs_user = evsel->core.attr.sample_regs_user;
+ priv->orig_sample_regs_intr = evsel->core.attr.sample_regs_intr;
+
+ err = hashmap__add(&aslr->evsel_orig_attrs, evsel, priv);
+ if (err) {
+ free(priv);
+ return err;
+ }
+ return 0;
+}
+
+void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
+{
+ struct evsel *evsel;
+
+ evlist__for_each_entry(evlist, evsel) {
+ evsel->core.attr.sample_type &= ASLR_SUPPORTED_SAMPLE_TYPE;
+ evsel__reset_sample_bit(evsel, REGS_USER);
+ evsel__reset_sample_bit(evsel, REGS_INTR);
+ evsel->core.attr.sample_regs_user = 0;
+ evsel->core.attr.sample_regs_intr = 0;
+
+ if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT)
+ evsel->core.attr.bp_addr = 0;
+ }
+}
+
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist)
+{
+ const struct delegate_tool *del_tool = container_of(tool, const struct delegate_tool, tool);
+ const struct aslr_tool *aslr = container_of(del_tool, const struct aslr_tool, tool);
+ struct evsel *evsel;
+ struct aslr_evsel_priv *priv;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) {
+ evsel->core.attr.sample_type = priv->orig_sample_type;
+ evsel->core.attr.sample_regs_user = priv->orig_sample_regs_user;
+ evsel->core.attr.sample_regs_intr = priv->orig_sample_regs_intr;
+ }
+ }
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
index a9b90bf29540..4c2cffc0e500 100644
--- a/tools/perf/util/aslr.h
+++ b/tools/perf/util/aslr.h
@@ -30,8 +30,12 @@
struct perf_tool;
struct evsel;
+struct evlist;
struct perf_tool *aslr_tool__new(struct perf_tool *delegate);
void aslr_tool__delete(struct perf_tool *aslr);
+int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel);
+void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist);
+void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist *evlist);
#endif /* __PERF_ASLR_H */
--
2.54.0.1032.g2f8565e1d1-goog
© 2016 - 2026 Red Hat, Inc.