* [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses
@ 2026-04-24 22:05 Ian Rogers
2026-04-24 22:05 ` [PATCH v1 2/2] perf test: Add inject ASLR test Ian Rogers
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Ian Rogers @ 2026-04-24 22:05 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
Gabriel Marin, linux-kernel, linux-perf-users
If perf.data files are taken from one machine to another they make
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add a '--aslr' option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
When events are not known/handled by the tool they are dropped. This
makes the tool conservative and it should never leak ASLR information,
but it means virtual address remapping is needed for cases like
auxtrace.
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 | 11 +-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 752 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 10 +
4 files changed, 773 insertions(+), 1 deletion(-)
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 f174bc69cec4..fa3a71e23f1b 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"
@@ -123,6 +124,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;
@@ -2564,6 +2566,8 @@ int cmd_inject(int argc, const char **argv)
" instance has a subdir"),
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[] = {
@@ -2571,6 +2575,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. */
@@ -2684,7 +2689,9 @@ 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);
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
@@ -2789,6 +2796,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 70cc91d00804..65b96f3b87e2 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..23ef7b68896c
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,752 @@
+// 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 <internal/lib.h> // page_size
+#include <linux/compiler.h>
+#include <errno.h>
+#include <inttypes.h>
+
+struct remap_addresses_key {
+ u64 start_addr;
+ pid_t pid;
+};
+
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machine: The machine with the input, not remapped, virtual address layout. */
+ struct machine machine;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE];
+ /** @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;
+ /** @first_kernel_mapping: flag indicating if we are still to process any kernel mapping. */
+ bool first_kernel_mapping;
+};
+
+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 key->start_addr ^ (key->start_addr >> 12) ^ 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->pid == key2->pid && key1->start_addr == key2->start_addr;
+}
+
+static size_t top_addresses__hash(long key, void *ctx __maybe_unused)
+{
+ return key;
+}
+
+static bool top_addresses__equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
+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 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.
+ */
+ effective_cpumode = (cpumode == PERF_RECORD_MISC_KERNEL) ?
+ PERF_RECORD_MISC_USER : PERF_RECORD_MISC_KERNEL;
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ pr_debug("Cannot find mmap for address %lx in either space, pid=%d\n",
+ addr, aslr_thread->pid_);
+ addr_location__exit(&al);
+ return 0; // No mmap.
+ }
+ }
+
+ key.pid = effective_cpumode == PERF_RECORD_MISC_KERNEL ? kernel_pid : aslr_thread->pid_;
+ key.start_addr = map__start(al.map);
+ if (!hashmap__find(&aslr->remap_addresses, &key, &remap_addr)) {
+ 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 0;
+ }
+ remap_addr += addr - map__start(al.map);
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static u64 aslr_tool__remap_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 start, u64 len)
+{
+ struct addr_location prev_al;
+ struct remap_addresses_key key;
+ u64 remap_addr = 0;
+ /* If mapping is contiguous to the previous process mapping. */
+ bool is_contiguous = false;
+ bool first_mapping = false; // first process mapping.
+
+ if (!aslr_thread)
+ return 0; // No thread.
+
+ addr_location__init(&prev_al);
+ if (thread__find_map(aslr_thread, cpumode, start-1, &prev_al)) {
+ if (map__start(prev_al.map) + map__size(prev_al.map) == start) {
+ is_contiguous = true;
+ } else {
+ pr_debug("Previous mmap [%lx, %lx] overlaps current map [%lx, %lx]\n",
+ map__start(prev_al.map),
+ map__start(prev_al.map) + map__size(prev_al.map),
+ start, start+len);
+ }
+ }
+ addr_location__exit(&prev_al);
+
+ key.pid = cpumode == PERF_RECORD_MISC_KERNEL ? kernel_pid : aslr_thread->pid_;
+ key.start_addr = start;
+ if (hashmap__find(&aslr->remap_addresses, &key, &remap_addr))
+ return remap_addr;
+
+ if (!hashmap__find(&aslr->top_addresses, key.pid, &remap_addr)) {
+ /* First mapping in this process. Don't add a page gap. */
+ first_mapping = true;
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ?
+ kernel_space_start : user_space_start);
+ }
+
+ remap_addr = round_up_to_page_size(remap_addr);
+ if (!is_contiguous && !first_mapping)
+ remap_addr += page_size;
+
+ {
+ struct remap_addresses_key *new_key = malloc(sizeof(*new_key));
+
+ if (!new_key)
+ return 0;
+ *new_key = key;
+ if (hashmap__add(&aslr->remap_addresses, new_key, remap_addr) != 0) {
+ pr_debug("Failed to add remap_addresses entry for pid=%d, mapping start=%lx, remapped start=%lx",
+ key.pid, start, remap_addr);
+ free(new_key);
+ return 0;
+ }
+ }
+
+ hashmap__insert(&aslr->top_addresses, key.pid, remap_addr+len,
+ first_mapping ? HASHMAP_ADD : HASHMAP_UPDATE, NULL, NULL);
+ return remap_addr;
+}
+
+static u64 aslr_tool__remap_ksymbol(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u64 addr, u32 len)
+{
+ struct remap_addresses_key key;
+ u64 remap_addr = 0;
+ bool first_mapping = false;
+
+ if (!aslr_thread)
+ return 0; // No thread.
+
+ key.pid = aslr_thread->pid_;
+ key.start_addr = addr;
+ if (hashmap__find(&aslr->remap_addresses, &key, &remap_addr))
+ return remap_addr;
+
+ first_mapping = !hashmap__find(&aslr->top_addresses, key.pid, &remap_addr);
+ if (first_mapping)
+ remap_addr = kernel_space_start;
+ remap_addr = round_up_to_page_size(remap_addr) + page_size;
+
+ {
+ struct remap_addresses_key *new_key = malloc(sizeof(*new_key));
+
+ if (!new_key)
+ return 0;
+ *new_key = key;
+ if (hashmap__add(&aslr->remap_addresses, new_key, remap_addr) < 0) {
+ pr_debug("Failed to add remap_addresses entry for pid=%d, ksymbol=%lx, remapped address=%lx",
+ key.pid, addr, remap_addr);
+ free(new_key);
+ return 0;
+ }
+ }
+
+ hashmap__insert(&aslr->top_addresses, key.pid, remap_addr+len,
+ first_mapping ? HASHMAP_ADD : HASHMAP_UPDATE, NULL, NULL);
+ 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 = 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+ int err;
+
+ /* 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);
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__remap_mapping(aslr, thread, cpumode,
+ event->mmap.start, event->mmap.len);
+ if (aslr->first_kernel_mapping && cpumode == PERF_RECORD_MISC_KERNEL) {
+ /* If this is the first kernel image, we need to adjust the pgoff by a
+ * similar delta.
+ */
+ new_event->mmap.pgoff = event->mmap.pgoff - event->mmap.start +
+ new_event->mmap.start;
+ aslr->first_kernel_mapping = false;
+ }
+ return delegate->mmap(delegate, new_event, sample, machine);
+}
+
+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 = 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+ int err;
+
+ /* 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);
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__remap_mapping(aslr, thread, cpumode,
+ event->mmap2.start, event->mmap2.len);
+ if (aslr->first_kernel_mapping && cpumode == PERF_RECORD_MISC_KERNEL) {
+ /* If this is the first kernel image, we need to adjust the pgoff by a
+ * similar delta.
+ */
+ new_event->mmap2.pgoff = event->mmap2.pgoff - event->mmap2.start +
+ new_event->mmap2.start;
+ aslr->first_kernel_mapping = false;
+ }
+ return delegate->mmap2(delegate, new_event, sample, machine);
+}
+
+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 = 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;
+ int err;
+
+ /* 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 = 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;
+ int err;
+
+ /* 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 = 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;
+ int err;
+
+ /* 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,
+ 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+
+ thread = machine__findnew_thread(&aslr->machine, sample->pid, sample->tid);
+ memcpy(&new_event->text_poke, &event->text_poke, event->text_poke.header.size);
+ new_event->text_poke.addr = aslr_tool__remap_address(aslr, thread, cpumode,
+ event->text_poke.addr);
+
+ return delegate->text_poke(delegate, new_event, sample, machine);
+}
+
+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 = 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ struct thread *thread;
+ int err;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_ksymbol(tool, event, sample, &aslr->machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(&aslr->machine, kernel_pid, 0);
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__remap_ksymbol(aslr, thread,
+ event->ksymbol.addr, event->ksymbol.len);
+
+ return delegate->ksymbol(delegate, new_event, sample, machine);
+}
+
+static inline int copy_u64(__u64 *in_array, __u64 *out_array,
+ size_t *i, size_t *j, const __u64 max_i)
+{
+ if (*i > max_i)
+ return -EFAULT;
+ out_array[(*j)++] = in_array[(*i)++];
+ return 0;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool, union perf_event *event,
+ struct perf_sample *sample,
+ struct evsel *evsel, 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;
+ int ret;
+ u64 sample_type = evsel->core.attr.sample_type;
+ struct thread *thread = machine__findnew_thread(&aslr->machine, sample->pid, sample->tid);
+ const __u64 max_i = event->header.size / sizeof(__u64);
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode = sample->cpumode;
+ u64 addr;
+ size_t i = 0, j = 0;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // id
+ if (sample_type & PERF_SAMPLE_IP) {
+ i++;
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode,
+ sample->ip);
+ }
+ if (sample_type & PERF_SAMPLE_TID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // pid, tid
+ if (sample_type & PERF_SAMPLE_TIME)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // time
+ if (sample_type & PERF_SAMPLE_ADDR) {
+ i++; // addr
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode,
+ sample->addr);
+ }
+ if (sample_type & PERF_SAMPLE_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // id
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // stream_id
+ if (sample_type & PERF_SAMPLE_CPU)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // cpu, res
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // period
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // value
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // time_enabled
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // time_running
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // id
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // lost
+ } else {
+ u64 nr;
+
+ if (i > max_i)
+ return -EFAULT;
+ nr = out_array[j++] = in_array[i++];
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // time_enabled
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // time_running
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // value
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // id
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // lost
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // nr
+
+ for (u64 cntr = 0; cntr < sample->callchain->nr; cntr++) {
+ if (i > max_i)
+ return -EFAULT;
+ i++;
+ addr = sample->callchain->ips[cntr];
+ if (addr >= PERF_CONTEXT_MAX) {
+ // Copy context values as is.
+ 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;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ /*
+ * It seems the callchain is corrupted.
+ * Discard sample.
+ */
+ return 0;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode,
+ sample->callchain->ips[cntr]);
+ }
+ }
+ if (sample_type & PERF_SAMPLE_RAW) {
+ size_t bytes = sizeof(u32) + sample->raw_size;
+
+ if ((i + (bytes / sizeof(u64))) > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], bytes);
+ i += bytes / sizeof(u64);
+ j += bytes / sizeof(u64);
+ // TODO: certain raw samples can be remapped, such as
+ // tracepoints by examining their fields.
+ pr_debug("Dropping raw samples as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // nr
+ if (sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // hw_idx
+ if (i + (sample->branch_stack->nr * 3) > max_i)
+ return -EFAULT;
+ for (u64 cntr = 0; cntr < sample->branch_stack->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
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // flags
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (i + sample->branch_stack->nr > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], sample->branch_stack->nr * sizeof(u64));
+ i += sample->branch_stack->nr;
+ j += sample->branch_stack->nr;
+ // TODO: confirm branch counters don't leak ASLR information.
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ return 0;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // abi
+ if (sample->user_regs->abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(evsel->core.attr.sample_regs_user);
+
+ if (i + nr > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ }
+ // TODO: can this be less conservative?
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (i > max_i)
+ return -EFAULT;
+ size = out_array[j++] = in_array[i++];
+ if (size > 0) {
+ memcpy(&out_array[j], &in_array[i], size);
+ i += size / sizeof(u64);
+ j += size / sizeof(u64);
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // dyn_size
+ }
+ // TODO: can this be less conservative?
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // perf_sample_weight
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // data_src
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // transaction
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // abi
+ if (sample->intr_regs->abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(evsel->core.attr.sample_regs_intr);
+
+ if (i + nr > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ }
+ // TODO: can this be less conservative?
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // phys_addr
+ // TODO: can this be less conservative?
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // cgroup
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // data_page_size
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; // code_page_size
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (i > max_i)
+ return -EFAULT;
+ size = out_array[j++] = in_array[i++];
+ if (i + (size / sizeof(u64)) > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], size);
+ i += size / sizeof(u64);
+ j += size / sizeof(u64);
+ // TODO: can this be less conservative?
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ return 0;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ // TODO: can this be less conservative?
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ return 0;
+ }
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+ if (ret)
+ return ret;
+
+ ret = delegate->sample(delegate, new_event, &new_sample, evsel, machine);
+ perf_sample__exit(&new_sample);
+ return ret;
+}
+
+static int aslr_tool__process_attr(const struct perf_tool *tool,
+ union perf_event *event,
+ struct evlist **pevlist)
+{
+ 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+
+ memcpy(&new_event->attr, &event->attr, event->attr.header.size);
+ new_event->attr.attr.bp_addr = 0; // Conservatively remove addresses.
+ new_event->attr.attr.kprobe_addr = 0; // Conservatively remove addresses.
+
+ return delegate->attr(delegate, new_event, pevlist);
+}
+
+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;
+
+ machine__init(&aslr->machine, "", HOST_KERNEL_ID);
+
+ 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->first_kernel_mapping = true;
+
+ 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;
+ aslr->tool.tool.attr = aslr_tool__process_attr;
+ // 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.
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = malloc(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 = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt)
+ zfree(&cur->pkey);
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ machine__exit(&aslr->machine);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..ea984d82681f
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+struct perf_tool;
+
+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.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v1 2/2] perf test: Add inject ASLR test
2026-04-24 22:05 [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
@ 2026-04-24 22:05 ` Ian Rogers
2026-04-24 22:47 ` sashiko-bot
2026-04-24 22:36 ` [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses sashiko-bot
2026-04-25 2:05 ` [PATCH v2 " Ian Rogers
2 siblings, 1 reply; 6+ messages in thread
From: Ian Rogers @ 2026-04-24 22:05 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
Gabriel Marin, linux-kernel, linux-perf-users
Add a new shell test `inject_aslr.sh` to verify the `perf inject --aslr`
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for `perf record` piped into `perf inject --aslr`.
- Callchain address remapping.
- Consistency of `perf report` output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (skipping gracefully if permissions restrict
recording the kernel map).
- Kernel report consistency with address normalization.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 381 ++++++++++++++++++++++++++
1 file changed, 381 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..17544fe9ef6c
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,381 @@
+#!/bin/bash
+# perf inject --aslr test
+# SPDX-License-Identifier: GPL-2.0
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+# Set path to built perf
+PERF="/tmp/perf3/perf"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+data="${temp_dir}/perf.data"
+data2="${temp_dir}/perf.data2"
+
+prog="${PERF} test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+
+set -e
+
+cleanup() {
+ # 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 - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ ${PERF} script -i "$file" | awk '{for(i=1;i<=NF;i++) if($i ~ /noploop\+/) {print $(i-1); exit}}'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp /tmp/perf.data.basic.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp /tmp/perf.data.pipe.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp /tmp/perf.data.callchain.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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
+ # Also check that the full script output differs (to cover callchains)
+ orig_script=$(${PERF} script -i "${data}" | grep -A 5 noploop | head -n 20)
+ new_script=$(${PERF} script -i "${data2}" | grep -A 5 noploop | head -n 20)
+
+ if [ "$orig_script" = "$new_script" ]; then
+ echo "Callchain ASLR test [Failed - callchain output is identical]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp /tmp/perf.data.report.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/perf.data2.report.XXXXXX)
+ local data_clean
+ data_clean=$(mktemp /tmp/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}"
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}"
+
+ diff -u "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ -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
+ rm -f "${data}" "${data2}" "${data_clean}" "${data}.old" "${data2}.old" "${data_clean}.old"
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp /tmp/perf.data.pipe_report.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/perf.data2.pipe_report.XXXXXX)
+ local data_clean
+ data_clean=$(mktemp /tmp/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}"
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}"
+
+ diff -u "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ -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
+ rm -f "${data}" "${data2}" "${data_clean}" "${data}.old" "${data2}.old" "${data_clean}.old"
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp /tmp/perf.data.dropped.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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 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]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp /tmp/perf.data.kernel.XXXXXX)
+ local kdata2
+ kdata2=$(mktemp /tmp/perf.data2.kernel.XXXXXX)
+ local log_file
+ log_file=$(mktemp /tmp/kernel_record.log.XXXXXX)
+
+ # Try to record kernel samples
+ if ! ${PERF} record -e task-clock:k -o "${kdata}" ${prog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ rm -f "${kdata}" "${log_file}"
+ 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)"
+ rm -f "${kdata}" "${log_file}"
+ return
+ fi
+
+ ${PERF} inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ orig_addr=$(${PERF} script -i "${kdata}" | awk '{for(i=1;i<=NF;i++) if($i ~ /^ffff/) {print $i; exit}}')
+ new_addr=$(${PERF} script -i "${kdata2}" | awk '{for(i=1;i<=NF;i++) if($i ~ /^ffff/) {print $i; exit}}')
+
+ 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]"
+ rm -f "${kdata}" "${kdata2}" "${log_file}" "${kdata}.old" "${kdata2}.old"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp /tmp/perf.data.kernel_report.XXXXXX)
+ local kdata2
+ kdata2=$(mktemp /tmp/perf.data2.kernel_report.XXXXXX)
+ local data_clean
+ data_clean=$(mktemp /tmp/perf.data.clean.XXXXXX)
+ local log_file
+ log_file=$(mktemp /tmp/kernel_report_record.log.XXXXXX)
+
+ # Try to record kernel samples
+ if ! ${PERF} record -e task-clock:k -o "${kdata}" ${prog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ rm -f "${kdata}" "${log_file}"
+ 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)"
+ rm -f "${kdata}" "${log_file}"
+ 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"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ ${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}"
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}"
+
+ # 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"
+ awk '{$3 = ($3 ~ /^\[.*\]$/ ? "[kernel]" : $3); \
+ for(i=1;i<=NF;i++) \
+ if($i ~ /^ffff/ || $i ~ /^0x/ || $i == "0000000000000000") \
+ $i = "[addr]"; \
+ print}' "${report1_clean}" | sort > "${report1_norm}"
+ awk '{$3 = ($3 ~ /^\[.*\]$/ ? "[kernel]" : $3); \
+ for(i=1;i<=NF;i++) \
+ if($i ~ /^ffff/ || $i ~ /^0x/ || $i == "0000000000000000") \
+ $i = "[addr]"; \
+ print}' "${report2_clean}" | sort > "${report2_norm}"
+
+ diff -u "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ -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
+ rm -f "${kdata}" "${kdata2}" "${data_clean}" "${log_file}" "${kdata}.old" "${kdata2}.old" "${data_clean}.old"
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup
+exit $err
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses
2026-04-24 22:05 [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
2026-04-24 22:05 ` [PATCH v1 2/2] perf test: Add inject ASLR test Ian Rogers
@ 2026-04-24 22:36 ` sashiko-bot
2026-04-25 2:05 ` [PATCH v2 " Ian Rogers
2 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-04-24 22:36 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
Sashiko AI review found 8 potential issue(s):
- [High] Missing `thread__put()` causes severe memory leak of thread objects per event.
- [High] Incorrect bitmask check for branch stack flags causes catastrophic sample parsing failure.
- [High] Incorrect size calculation for `PERF_SAMPLE_RAW` causes parsing desynchronization and uninitialized memory leak.
- [High] Off-by-one error in `copy_u64()` bounds checking allows out-of-bounds memory reads.
- [High] Unconditional zeroing of union fields in `perf_event_attr` corrupts PMU configurations.
- [High] `hashmap` usage causes 32-bit compilation failures and silent 64-bit address truncation.
- [Medium] Unhandled guest callchain contexts cause valid samples to be discarded.
- [Medium] Missing integer overflow checks on `branch_stack->nr` allow bounds-check bypass.
--
commit cb2629f8e0c5449ea2cd944df7889ca64868a5eb
Author: Ian Rogers <irogers@google.com>
Subject: perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses
This commit introduces an ASLR tool to perf inject. It allows remapping
virtual memory addresses to prevent leaking ASLR information when sharing
perf.data files. Unhandled events are dropped to remain conservative.
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
--- /dev/null
+++ b/tools/perf/util/aslr.c
[ ... ]
> +static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
> + struct thread *aslr_thread,
> + u8 cpumode,
> + u64 addr)
> +{
[ ... ]
> + if (!hashmap__find(&aslr->remap_addresses, &key, &remap_addr)) {
Will storing and retrieving 64-bit addresses in this hashmap cause truncation
or build failures on 32-bit architectures?
The underlying hashmap API stores long values. On 32-bit systems, long is 32
bits wide, which might truncate the upper 32 bits of the remapped address
during hashmap__add.
Additionally, passing a u64 pointer to hashmap__find triggers
hashmap_cast_ptr(), which contains a static assertion that the value size
equals sizeof(long). Might this result in a build failure when compiling on
32-bit platforms?
[ ... ]
> +static int aslr_tool__process_mmap(const struct perf_tool *tool,
> + union perf_event *event,
> + struct perf_sample *sample,
> + struct machine *machine)
> +{
[ ... ]
> + thread = machine__findnew_thread(&aslr->machine, event->mmap.pid, event->mmap.tid);
> + memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
> + /* Remaps the mmap.start. */
> + new_event->mmap.start = aslr_tool__remap_mapping(aslr, thread, cpumode,
> + event->mmap.start, event->mmap.len);
[ ... ]
> + return delegate->mmap(delegate, new_event, sample, machine);
> +}
Since machine__findnew_thread() increments the reference count of the
returned thread, could returning from this function without calling
thread__put() leak the thread reference for every processed event?
This pattern appears to be repeated in aslr_tool__process_mmap2,
aslr_tool__process_ksymbol, aslr_tool__process_text_poke, and
aslr_tool__process_sample.
[ ... ]
> +static inline int copy_u64(__u64 *in_array, __u64 *out_array,
> + size_t *i, size_t *j, const __u64 max_i)
> +{
> + if (*i > max_i)
> + return -EFAULT;
> + out_array[(*j)++] = in_array[(*i)++];
> + return 0;
> +}
Does this bounds check allow reading past the end of the input event buffer?
The max_i value is calculated as event->header.size / sizeof(__u64).
Since in_array points to event->sample.array[0], which starts after the
8-byte perf_event_header, the array contains max_i - 1 elements.
Should the check be if (*i >= max_i - 1) instead?
[ ... ]
> +static int aslr_tool__process_sample(const struct perf_tool *tool, union perf_event *event,
> + struct perf_sample *sample,
> + struct evsel *evsel, struct machine *machine)
> +{
[ ... ]
> + if (sample_type & PERF_SAMPLE_CALLCHAIN) {
> + if (copy_u64(in_array, out_array, &i, &j, max_i))
> + return -EFAULT; // nr
> +
> + for (u64 cntr = 0; cntr < sample->callchain->nr; cntr++) {
> + if (i > max_i)
> + return -EFAULT;
> + i++;
> + addr = sample->callchain->ips[cntr];
> + if (addr >= PERF_CONTEXT_MAX) {
> + // Copy context values as is.
> + 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;
> + default:
> + pr_debug("invalid callchain context: %"PRIx64"\n", addr);
> + /*
> + * It seems the callchain is corrupted.
> + * Discard sample.
> + */
> + return 0;
> + }
Should the switch statement handle other valid context markers like
PERF_CONTEXT_GUEST, PERF_CONTEXT_GUEST_KERNEL, PERF_CONTEXT_GUEST_USER, and
PERF_CONTEXT_USER_DEFERRED?
Falling into the default block here abruptly discards the sample, which might
break profiling workflows involving KVM guests.
[ ... ]
> + if (sample_type & PERF_SAMPLE_RAW) {
> + size_t bytes = sizeof(u32) + sample->raw_size;
> +
> + if ((i + (bytes / sizeof(u64))) > max_i)
> + return -EFAULT;
> + memcpy(&out_array[j], &in_array[i], bytes);
> + i += bytes / sizeof(u64);
> + j += bytes / sizeof(u64);
Will the integer division truncate the padding bytes if the raw sample size
is not perfectly aligned to an 8-byte boundary?
Because the kernel pads raw sample payloads to an 8-byte boundary, truncation
might cause i and j to fail to advance past the padding.
Could this lead to the tool misinterpreting the padding bytes as the next
fields in the sample, and also leave uninitialized heap memory from
aslr->event_copy in the output?
[ ... ]
> + if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
> + if (copy_u64(in_array, out_array, &i, &j, max_i))
> + return -EFAULT; // nr
> + if (sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
> + if (copy_u64(in_array, out_array, &i, &j, max_i))
> + return -EFAULT; // hw_idx
Is this checking the correct bitmask?
The PERF_SAMPLE_BRANCH_HW_INDEX and PERF_SAMPLE_BRANCH_COUNTERS flags
belong to the branch_sample_type bitmask, not the sample_type bitmask.
Because the sample_type enum uses those bit values for
PERF_SAMPLE_TRANSACTION and PERF_SAMPLE_PHYS_ADDR, could a sample with a
transaction or physical address field cause the parser to incorrectly assume
the presence of branch hardware indices or counters?
> + if (i + (sample->branch_stack->nr * 3) > max_i)
> + return -EFAULT;
Is it possible for sample->branch_stack->nr * 3 to wrap around and bypass
this bounds check?
If a crafted perf.data file contains an exceptionally large nr value, the
multiplication might overflow and result in a small number. Could this
allow the subsequent loop to perform out-of-bounds reads from in_array?
[ ... ]
> +static int aslr_tool__process_attr(const struct perf_tool *tool,
> + union perf_event *event,
> + struct evlist **pevlist)
> +{
[ ... ]
> + memcpy(&new_event->attr, &event->attr, event->attr.header.size);
> + new_event->attr.attr.bp_addr = 0; // Conservatively remove addresses.
> + new_event->attr.attr.kprobe_addr = 0; // Conservatively remove addresses.
> +
> + return delegate->attr(delegate, new_event, pevlist);
> +}
Could clearing bp_addr and kprobe_addr unintentionally corrupt PMU
configurations?
In struct perf_event_attr, bp_addr is an alias for the config1 union field,
and kprobe_addr is an alias for config2.
Since many hardware PMU drivers rely on config1 and config2 to store
critical event configuration data, could clearing these fields break
configurations for non-probe hardware events?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260424220519.2743472-1-irogers@google.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1 2/2] perf test: Add inject ASLR test
2026-04-24 22:05 ` [PATCH v1 2/2] perf test: Add inject ASLR test Ian Rogers
@ 2026-04-24 22:47 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-04-24 22:47 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
Sashiko AI review found 4 potential issue(s):
- [High] Hardcoded and insecure PERF binary path
- [Medium] Unhandled grep pipeline aborts script via set -e
- [Medium] Architecture assumption in kernel address parsing breaks 32-bit systems
- [Medium] Improper cleanup of temporary files and directories
--
commit 55c497e7d1e9a304b5ad52e13004ec9955f6ffcb
Author: Ian Rogers <irogers@google.com>
perf test: Add inject ASLR test
Add a new shell test inject_aslr.sh to verify the perf inject --aslr
feature.
> diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
> new file mode 100755
> index 00000000000000..17544fe9ef6c02
> --- /dev/null
> +++ b/tools/perf/tests/shell/inject_aslr.sh
> @@ -0,0 +1,381 @@
> +#!/bin/bash
> +# perf inject --aslr test
> +# SPDX-License-Identifier: GPL-2.0
> +
> +shelldir=$(dirname "$0")
> +# shellcheck source=lib/perf_has_symbol.sh
> +. "${shelldir}"/lib/perf_has_symbol.sh
> +
> +sym="noploop"
> +
> +# Set path to built perf
> +PERF="/tmp/perf3/perf"
Is this hardcoded path safe and portable? If the test is run as root, could a
local user place a malicious executable at /tmp/perf3/perf and escalate
privileges?
Also, won't this break the test in standard CI environments?
[ ... ]
> +cleanup() {
> + # 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 - EXIT TERM INT
> +}
> +
> +trap_cleanup() {
> + echo "Unexpected signal in ${FUNCNAME[1]}"
> + cleanup
> + exit 1
> +}
> +trap trap_cleanup TERM INT
Because the script uses set -e, should this also trap EXIT? Without an EXIT
trap, if a command fails and triggers an exit, temp_dir might be leaked.
[ ... ]
> +test_basic_aslr() {
> + echo "Test basic ASLR remapping"
> + local data
> + data=$(mktemp /tmp/perf.data.basic.XXXXXX)
Should these temporary files be created inside temp_dir instead of directly
in /tmp?
[ ... ]
> + 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]"
> + rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
> + fi
> +}
If the test fails and err=1 is set, won't these temporary files be permanently
leaked?
This pattern appears in multiple test functions. Also, in
test_dropped_samples, if the test returns early due to unsupported
--phys-data, these files are also leaked.
[ ... ]
> +test_kernel_aslr() {
> + echo "Test kernel ASLR remapping"
> + local kdata
> + kdata=$(mktemp /tmp/perf.data.kernel.XXXXXX)
> + local kdata2
> + kdata2=$(mktemp /tmp/perf.data2.kernel.XXXXXX)
> + local log_file
> + log_file=$(mktemp /tmp/kernel_record.log.XXXXXX)
> +
> + # Try to record kernel samples
> + if ! ${PERF} record -e task-clock:k -o "${kdata}" ${prog} > "${log_file}" 2>&1; then
> + echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
> + rm -f "${kdata}" "${log_file}"
> + 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)"
> + rm -f "${kdata}" "${log_file}"
> + return
> + fi
> +
> + ${PERF} inject -v --aslr -i "${kdata}" -o "${kdata2}"
> +
> + # Check if kernel addresses are remapped.
> + orig_addr=$(${PERF} script -i "${kdata}" | awk '{for(i=1;i<=NF;i++) if($i ~ /^ffff/) {print $i; exit}}')
> + new_addr=$(${PERF} script -i "${kdata2}" | awk '{for(i=1;i<=NF;i++) if($i ~ /^ffff/) {print $i; exit}}')
Will this parsing work on 32-bit architectures? Kernel space typically resides
at different offsets (such as 0xc0000000 or 0x80000000) on 32-bit platforms,
so hardcoding ^ffff might cause the test to spuriously fail there.
[ ... ]
> +test_kernel_report_aslr() {
[ ... ]
> + ${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}"
> + grep '%' "${report2}" | grep -v '^#' > "${report2_clean}"
What happens if report1 contains no matching lines?
The second grep -v would receive empty input and exit with a status code of 1.
Since the script uses set -e and doesn't have || true on this pipeline, won't
this cause the script to abort immediately and silently?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260424220519.2743472-1-irogers@google.com?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses
2026-04-24 22:05 [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
2026-04-24 22:05 ` [PATCH v1 2/2] perf test: Add inject ASLR test Ian Rogers
2026-04-24 22:36 ` [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses sashiko-bot
@ 2026-04-25 2:05 ` Ian Rogers
2026-04-25 2:05 ` [PATCH v2 2/2] perf test: Add inject ASLR test Ian Rogers
2 siblings, 1 reply; 6+ messages in thread
From: Ian Rogers @ 2026-04-25 2:05 UTC (permalink / raw)
To: irogers
Cc: acme, adrian.hunter, gmx, james.clark, jolsa, linux-kernel,
linux-perf-users, mingo, namhyung, peterz
If perf.data files are taken from one machine to another they make
leak virtual addresses and so weaken ASLR on the machine they are
coming from. Add a '--aslr' option for perf inject that remaps all
virtual addresses, or drops data/events, so that the virtual address
information isn't leaked.
When events are not known/handled by the tool they are dropped. This
makes the tool conservative and it should never leak ASLR information,
but it means virtual address remapping is needed for cases like
auxtrace.
Assisted-by: Gemini:gemini-3.1-pro-preview
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 | 11 +-
tools/perf/util/Build | 1 +
tools/perf/util/aslr.c | 816 ++++++++++++++++++++++++++++++++++++
tools/perf/util/aslr.h | 10 +
4 files changed, 837 insertions(+), 1 deletion(-)
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 f174bc69cec4..fa3a71e23f1b 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"
@@ -123,6 +124,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;
@@ -2564,6 +2566,8 @@ int cmd_inject(int argc, const char **argv)
" instance has a subdir"),
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[] = {
@@ -2571,6 +2575,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. */
@@ -2684,7 +2689,9 @@ 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);
+ inject.session = __perf_session__new(&data, tool,
/*trace_event_repipe=*/inject.output.is_pipe,
/*host_env=*/NULL);
@@ -2789,6 +2796,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 70cc91d00804..65b96f3b87e2 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..b21c5e82539d
--- /dev/null
+++ b/tools/perf/util/aslr.c
@@ -0,0 +1,816 @@
+// 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 <internal/lib.h> /* page_size */
+#include <linux/compiler.h>
+#include <errno.h>
+#include <inttypes.h>
+
+struct remap_addresses_key {
+ u64 start_addr;
+ pid_t pid;
+};
+
+struct aslr_tool {
+ /** @tool: The tool implemented here and a pointer to a delegate to process the data. */
+ struct delegate_tool tool;
+ /** @machine: The machine with the input, not remapped, virtual address layout. */
+ struct machine machine;
+ /** @event_copy: Buffer used to create an event to pass to the delegate. */
+ char event_copy[PERF_SAMPLE_MAX_SIZE];
+ /** @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;
+ /** @first_kernel_mapping: flag indicating if we are still to process any kernel mapping. */
+ bool first_kernel_mapping;
+};
+
+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 key->start_addr ^ (key->start_addr >> 12) ^ 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->pid == key2->pid && key1->start_addr == key2->start_addr;
+}
+
+static size_t top_addresses__hash(long key, void *ctx __maybe_unused)
+{
+ return key;
+}
+
+static bool top_addresses__equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
+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 remap_addr = 0;
+ u64 *remap_addr_ptr = NULL;
+ 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.
+ */
+ effective_cpumode = (cpumode == PERF_RECORD_MISC_KERNEL) ?
+ PERF_RECORD_MISC_USER : PERF_RECORD_MISC_KERNEL;
+ if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+ pr_debug("Cannot find mmap for address %lx in either space, pid=%d\n",
+ addr, aslr_thread->pid_);
+ addr_location__exit(&al);
+ return 0; /* No mmap. */
+ }
+ }
+
+ key.pid = effective_cpumode == PERF_RECORD_MISC_KERNEL ? kernel_pid : aslr_thread->pid_;
+ key.start_addr = map__start(al.map);
+ if (!hashmap__find(&aslr->remap_addresses, &key, &remap_addr_ptr)) {
+ 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 0;
+ }
+ remap_addr = *remap_addr_ptr + (addr - map__start(al.map));
+ addr_location__exit(&al);
+ return remap_addr;
+}
+
+static u64 aslr_tool__remap_mapping(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u8 cpumode,
+ u64 start, u64 len)
+{
+ struct addr_location prev_al;
+ struct remap_addresses_key key;
+ u64 remap_addr = 0;
+ u64 *remap_addr_ptr = NULL;
+ u64 *max_addr_ptr = NULL;
+ /* If mapping is contiguous to the previous process mapping. */
+ bool is_contiguous = false;
+ bool first_mapping = false; /* first process mapping. */
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ addr_location__init(&prev_al);
+ if (thread__find_map(aslr_thread, cpumode, start-1, &prev_al)) {
+ if (map__start(prev_al.map) + map__size(prev_al.map) == start) {
+ is_contiguous = true;
+ } else {
+ pr_debug("Previous mmap [%lx, %lx] overlaps current map [%lx, %lx]\n",
+ map__start(prev_al.map),
+ map__start(prev_al.map) + map__size(prev_al.map),
+ start, start+len);
+ }
+ }
+ addr_location__exit(&prev_al);
+
+ key.pid = cpumode == PERF_RECORD_MISC_KERNEL ? kernel_pid : aslr_thread->pid_;
+ key.start_addr = start;
+ if (hashmap__find(&aslr->remap_addresses, &key, &remap_addr_ptr))
+ return *remap_addr_ptr;
+
+ if (!hashmap__find(&aslr->top_addresses, key.pid, &max_addr_ptr)) {
+ /* First mapping in this process. Don't add a page gap. */
+ first_mapping = true;
+ remap_addr = (cpumode == PERF_RECORD_MISC_KERNEL ?
+ kernel_space_start : user_space_start);
+ } else {
+ remap_addr = *max_addr_ptr;
+ }
+
+ remap_addr = round_up_to_page_size(remap_addr);
+ if (!is_contiguous && !first_mapping)
+ remap_addr += page_size;
+
+ {
+ struct remap_addresses_key *new_key = malloc(sizeof(*new_key));
+
+ remap_addr_ptr = malloc(sizeof(u64));
+
+ if (!new_key || !remap_addr_ptr) {
+ free(new_key);
+ free(remap_addr_ptr);
+ return 0;
+ }
+ *new_key = key;
+ *remap_addr_ptr = remap_addr;
+ if (hashmap__add(&aslr->remap_addresses, new_key, remap_addr_ptr) != 0) {
+ pr_debug("Failed to add remap_addresses entry for pid=%d, mapping start=%lx, remapped start=%lx",
+ key.pid, start, remap_addr);
+ free(new_key);
+ free(remap_addr_ptr);
+ return 0;
+ }
+ }
+
+ max_addr_ptr = malloc(sizeof(u64));
+
+ if (!max_addr_ptr)
+ return 0;
+ *max_addr_ptr = remap_addr + len;
+ hashmap__insert(&aslr->top_addresses, key.pid, max_addr_ptr,
+ first_mapping ? HASHMAP_ADD : HASHMAP_UPDATE, NULL, NULL);
+ return remap_addr;
+}
+
+static u64 aslr_tool__remap_ksymbol(struct aslr_tool *aslr,
+ struct thread *aslr_thread,
+ u64 addr, u32 len)
+{
+ struct remap_addresses_key key;
+ u64 remap_addr = 0;
+ u64 *max_addr_ptr = NULL;
+ u64 *remap_addr_ptr = NULL;
+ bool first_mapping = false;
+
+ if (!aslr_thread)
+ return 0; /* No thread. */
+
+ key.pid = aslr_thread->pid_;
+ key.start_addr = addr;
+ if (hashmap__find(&aslr->remap_addresses, &key, &remap_addr_ptr))
+ return *remap_addr_ptr;
+
+ first_mapping = !hashmap__find(&aslr->top_addresses, key.pid, &max_addr_ptr);
+ if (first_mapping)
+ remap_addr = kernel_space_start;
+ else
+ remap_addr = *max_addr_ptr;
+ remap_addr = round_up_to_page_size(remap_addr) + page_size;
+
+ {
+ struct remap_addresses_key *new_key = malloc(sizeof(*new_key));
+
+ remap_addr_ptr = malloc(sizeof(u64));
+
+ if (!new_key || !remap_addr_ptr) {
+ free(new_key);
+ free(remap_addr_ptr);
+ return 0;
+ }
+ *new_key = key;
+ *remap_addr_ptr = remap_addr;
+ if (hashmap__add(&aslr->remap_addresses, new_key, remap_addr_ptr) < 0) {
+ pr_debug("Failed to add remap_addresses entry for pid=%d, ksymbol=%lx, remapped address=%lx",
+ key.pid, addr, remap_addr);
+ free(new_key);
+ free(remap_addr_ptr);
+ return 0;
+ }
+ }
+
+ max_addr_ptr = malloc(sizeof(u64));
+
+ if (!max_addr_ptr)
+ return 0;
+ *max_addr_ptr = remap_addr + len;
+ hashmap__insert(&aslr->top_addresses, key.pid, max_addr_ptr,
+ first_mapping ? HASHMAP_ADD : HASHMAP_UPDATE, NULL, NULL);
+ 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 = 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+ int err;
+
+ /* 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);
+ memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap.start = aslr_tool__remap_mapping(aslr, thread, cpumode,
+ event->mmap.start, event->mmap.len);
+ if (aslr->first_kernel_mapping && cpumode == PERF_RECORD_MISC_KERNEL) {
+ /*
+ * If this is the first kernel image, we need to adjust the pgoff by a
+ * similar delta.
+ */
+ new_event->mmap.pgoff = event->mmap.pgoff - event->mmap.start +
+ new_event->mmap.start;
+ aslr->first_kernel_mapping = false;
+ }
+ 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 = 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+ int err;
+
+ /* 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);
+ memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size);
+ /* Remaps the mmap.start. */
+ new_event->mmap2.start = aslr_tool__remap_mapping(aslr, thread, cpumode,
+ event->mmap2.start, event->mmap2.len);
+ if (aslr->first_kernel_mapping && cpumode == PERF_RECORD_MISC_KERNEL) {
+ /*
+ * If this is the first kernel image, we need to adjust the pgoff by a
+ * similar delta.
+ */
+ new_event->mmap2.pgoff = event->mmap2.pgoff - event->mmap2.start +
+ new_event->mmap2.start;
+ aslr->first_kernel_mapping = false;
+ }
+ 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 = 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;
+ int err;
+
+ /* 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 = 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;
+ int err;
+
+ /* 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 = 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;
+ int err;
+
+ /* 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,
+ 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+ int err;
+
+ thread = machine__findnew_thread(&aslr->machine, sample->pid, sample->tid);
+ memcpy(&new_event->text_poke, &event->text_poke, event->text_poke.header.size);
+ new_event->text_poke.addr = aslr_tool__remap_address(aslr, thread, cpumode,
+ event->text_poke.addr);
+
+ err = delegate->text_poke(delegate, new_event, sample, machine);
+
+ thread__put(thread);
+ return err;
+}
+
+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 = 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ struct thread *thread;
+ int err;
+
+ /* Create the thread, map, etc. in the ASLR before virtual address space. */
+ err = perf_event__process_ksymbol(tool, event, sample, &aslr->machine);
+ if (err)
+ return err;
+
+ thread = machine__findnew_thread(&aslr->machine, kernel_pid, 0);
+ memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size);
+ /* Remaps the ksymbol.start */
+ new_event->ksymbol.addr = aslr_tool__remap_ksymbol(aslr, thread,
+ event->ksymbol.addr, event->ksymbol.len);
+
+ err = delegate->ksymbol(delegate, new_event, sample, machine);
+ thread__put(thread);
+ return err;
+}
+
+static inline int copy_u64(__u64 *in_array, __u64 *out_array,
+ size_t *i, size_t *j, const __u64 max_i)
+{
+ if (*i >= max_i - 1)
+ return -EFAULT;
+ out_array[(*j)++] = in_array[(*i)++];
+ return 0;
+}
+
+static int aslr_tool__process_sample(const struct perf_tool *tool, union perf_event *event,
+ struct perf_sample *sample,
+ struct evsel *evsel, 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;
+ int ret;
+ u64 sample_type = evsel->core.attr.sample_type;
+ struct thread *thread = machine__findnew_thread(&aslr->machine, sample->pid, sample->tid);
+ const __u64 max_i = event->header.size / sizeof(__u64);
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+ struct perf_sample new_sample;
+ __u64 *in_array, *out_array;
+ u8 cpumode = sample->cpumode;
+ u64 addr;
+ size_t i = 0, j = 0;
+
+ new_event->sample.header = event->sample.header;
+
+ in_array = &event->sample.array[0];
+ out_array = &new_event->sample.array[0];
+
+
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* id */
+ if (sample_type & PERF_SAMPLE_IP) {
+ i++;
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode,
+ sample->ip);
+ }
+ if (sample_type & PERF_SAMPLE_TID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* pid, tid */
+ if (sample_type & PERF_SAMPLE_TIME)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* time */
+ if (sample_type & PERF_SAMPLE_ADDR) {
+ i++; /* addr */
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode,
+ sample->addr);
+ }
+ if (sample_type & PERF_SAMPLE_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* id */
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* stream_id */
+ if (sample_type & PERF_SAMPLE_CPU)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* cpu, res */
+ if (sample_type & PERF_SAMPLE_PERIOD)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* period */
+ if (sample_type & PERF_SAMPLE_READ) {
+ if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* time_running */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* lost */
+ } else {
+ u64 nr;
+
+ if (i > max_i)
+ return -EFAULT;
+ nr = out_array[j++] = in_array[i++];
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* time_enabled */
+ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* time_running */
+ for (u64 cntr = 0; cntr < nr; cntr++) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* value */
+ if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* id */
+ if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* lost */
+ }
+ }
+ }
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* nr */
+
+ for (u64 cntr = 0; cntr < sample->callchain->nr; cntr++) {
+ if (i > max_i)
+ return -EFAULT;
+ i++;
+ addr = sample->callchain->ips[cntr];
+ if (addr >= PERF_CONTEXT_MAX) {
+ /* Copy context values as is. */
+ 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:
+ case PERF_CONTEXT_GUEST_KERNEL:
+ case PERF_CONTEXT_GUEST_USER:
+ case PERF_CONTEXT_USER_DEFERRED:
+ break;
+ default:
+ pr_debug("invalid callchain context: %"PRIx64"\n", addr);
+ /*
+ * It seems the callchain is corrupted.
+ * Discard sample.
+ */
+ return 0;
+ }
+ continue;
+ }
+ out_array[j++] = aslr_tool__remap_address(aslr, thread, cpumode,
+ sample->callchain->ips[cntr]);
+ }
+ }
+ 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)
+ return -EFAULT;
+ 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");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* nr */
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* hw_idx */
+ if (sample->branch_stack->nr > (ULLONG_MAX / 3))
+ return -EFAULT;
+ if (i + (sample->branch_stack->nr * 3) > max_i)
+ return -EFAULT;
+ for (u64 cntr = 0; cntr < sample->branch_stack->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 */
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* flags */
+ }
+ if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+ if (i + sample->branch_stack->nr > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], sample->branch_stack->nr * sizeof(u64));
+ i += sample->branch_stack->nr;
+ j += sample->branch_stack->nr;
+ /* TODO: confirm branch counters don't leak ASLR information. */
+ pr_debug("Dropping sample branch counters as possible ASLR leak\n");
+ return 0;
+ }
+ }
+ if (sample_type & PERF_SAMPLE_REGS_USER) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* abi */
+ if (sample->user_regs->abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(evsel->core.attr.sample_regs_user);
+
+ if (i + nr > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping regs user sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_STACK_USER) {
+ u64 size;
+
+ if (i > max_i)
+ return -EFAULT;
+ size = out_array[j++] = in_array[i++];
+ if (size > 0) {
+ memcpy(&out_array[j], &in_array[i], size);
+ i += size / sizeof(u64);
+ j += size / sizeof(u64);
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* dyn_size */
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping stack user sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_WEIGHT_TYPE)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* perf_sample_weight */
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* data_src */
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* transaction */
+ if (sample_type & PERF_SAMPLE_REGS_INTR) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* abi */
+ if (sample->intr_regs->abi != PERF_SAMPLE_REGS_ABI_NONE) {
+ u64 nr = hweight64(evsel->core.attr.sample_regs_intr);
+
+ if (i + nr > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+ i += nr;
+ j += nr;
+ }
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping interrupt register sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* phys_addr */
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping physical address sample as possible ASLR leak\n");
+ return 0;
+ }
+ if (sample_type & PERF_SAMPLE_CGROUP)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* cgroup */
+ if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* data_page_size */
+ if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
+ if (copy_u64(in_array, out_array, &i, &j, max_i))
+ return -EFAULT; /* code_page_size */
+
+ if (sample_type & PERF_SAMPLE_AUX) {
+ u64 size;
+
+ if (i > max_i)
+ return -EFAULT;
+ size = out_array[j++] = in_array[i++];
+ if (i + (size / sizeof(u64)) > max_i)
+ return -EFAULT;
+ memcpy(&out_array[j], &in_array[i], size);
+ i += size / sizeof(u64);
+ j += size / sizeof(u64);
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping aux sample as possible ASLR leak\n");
+ return 0;
+ }
+
+ if (evsel__is_offcpu_event(evsel)) {
+ /* TODO: can this be less conservative? */
+ pr_debug("Dropping off-CPU sample as possible ASLR leak\n");
+ return 0;
+ }
+
+ perf_sample__init(&new_sample, /*all=*/ true);
+ ret = evsel__parse_sample(evsel, new_event, &new_sample);
+ if (ret)
+ return ret;
+
+ ret = delegate->sample(delegate, new_event, &new_sample, evsel, machine);
+ perf_sample__exit(&new_sample);
+ thread__put(thread);
+ return ret;
+}
+
+static int aslr_tool__process_attr(const struct perf_tool *tool,
+ union perf_event *event,
+ struct evlist **pevlist)
+{
+ 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;
+ union perf_event *new_event = (union perf_event *)aslr->event_copy;
+
+ memcpy(&new_event->attr, &event->attr, event->attr.header.size);
+ if (new_event->attr.attr.type == PERF_TYPE_BREAKPOINT)
+ new_event->attr.attr.bp_addr = 0; /* Conservatively remove addresses. */
+ if (new_event->attr.attr.kprobe_addr >= 0xffff800000000000)
+ new_event->attr.attr.kprobe_addr = 0; /* Conservatively remove addresses. */
+
+ return delegate->attr(delegate, new_event, pevlist);
+}
+
+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;
+
+ machine__init(&aslr->machine, "", HOST_KERNEL_ID);
+
+ 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->first_kernel_mapping = true;
+
+ 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;
+ aslr->tool.tool.attr = aslr_tool__process_attr;
+ /* 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. */
+}
+
+struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
+{
+ struct aslr_tool *aslr = malloc(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 = container_of(tool, struct delegate_tool, tool);
+ struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool);
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
+ zfree(&cur->pkey);
+ zfree(&cur->pvalue);
+ }
+ hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
+ zfree(&cur->pvalue);
+ }
+
+ hashmap__clear(&aslr->remap_addresses);
+ hashmap__clear(&aslr->top_addresses);
+ machine__exit(&aslr->machine);
+ free(aslr);
+}
diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h
new file mode 100644
index 000000000000..ea984d82681f
--- /dev/null
+++ b/tools/perf/util/aslr.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_ASLR_H
+#define __PERF_ASLR_H
+
+struct perf_tool;
+
+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.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 2/2] perf test: Add inject ASLR test
2026-04-25 2:05 ` [PATCH v2 " Ian Rogers
@ 2026-04-25 2:05 ` Ian Rogers
0 siblings, 0 replies; 6+ messages in thread
From: Ian Rogers @ 2026-04-25 2:05 UTC (permalink / raw)
To: irogers
Cc: acme, adrian.hunter, gmx, james.clark, jolsa, linux-kernel,
linux-perf-users, mingo, namhyung, peterz
Add a new shell test `inject_aslr.sh` to verify the `perf inject --aslr`
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for `perf record` piped into `perf inject --aslr`.
- Callchain address remapping.
- Consistency of `perf report` output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (skipping gracefully if permissions restrict
recording the kernel map).
- Kernel report consistency with address normalization.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/inject_aslr.sh | 386 ++++++++++++++++++++++++++
1 file changed, 386 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..951809eecfd4
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,386 @@
+#!/bin/bash
+# perf inject --aslr test
+# SPDX-License-Identifier: GPL-2.0
+
+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)
+data="${temp_dir}/perf.data"
+data2="${temp_dir}/perf.data2"
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+
+set -e
+
+cleanup() {
+ # 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 - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '{for(i=1;i<=NF;i++) if($i ~ /noploop\+/) {print $(i-1); exit}}'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp /tmp/perf.data.basic.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp /tmp/perf.data.pipe.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp /tmp/perf.data.callchain.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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
+ # Also check that the full script output differs (to cover callchains)
+ orig_script=$(perf script -i "${data}" | grep -A 5 noploop | head -n 20)
+ new_script=$(perf script -i "${data2}" | grep -A 5 noploop | head -n 20)
+
+ if [ "$orig_script" = "$new_script" ]; then
+ echo "Callchain ASLR test [Failed - callchain output is identical]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp /tmp/perf.data.report.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/perf.data2.report.XXXXXX)
+ local data_clean
+ data_clean=$(mktemp /tmp/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}"
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}"
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ -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
+ rm -f "${data}" "${data2}" "${data_clean}" "${data}.old" "${data2}.old" "${data_clean}.old"
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp /tmp/perf.data.pipe_report.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/perf.data2.pipe_report.XXXXXX)
+ local data_clean
+ data_clean=$(mktemp /tmp/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}"
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}"
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ -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
+ rm -f "${data}" "${data2}" "${data_clean}" "${data}.old" "${data2}.old" "${data_clean}.old"
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp /tmp/perf.data.dropped.XXXXXX)
+ local data2
+ data2=$(mktemp /tmp/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 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]"
+ rm -f "${data}" "${data2}" "${data}.old" "${data2}.old"
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp /tmp/perf.data.kernel.XXXXXX)
+ local kdata2
+ kdata2=$(mktemp /tmp/perf.data2.kernel.XXXXXX)
+ local log_file
+ log_file=$(mktemp /tmp/kernel_record.log.XXXXXX)
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${prog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ rm -f "${kdata}" "${log_file}"
+ 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)"
+ rm -f "${kdata}" "${log_file}"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ orig_addr=$(perf script -i "${kdata}" | \
+ awk '{for(i=1;i<=NF;i++) if($i ~ /^ffff/) {print $i; exit}}')
+ new_addr=$(perf script -i "${kdata2}" | \
+ awk '{for(i=1;i<=NF;i++) if($i ~ /^ffff/) {print $i; exit}}')
+
+ 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]"
+ rm -f "${kdata}" "${kdata2}" "${log_file}" "${kdata}.old" "${kdata2}.old"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp /tmp/perf.data.kernel_report.XXXXXX)
+ local kdata2
+ kdata2=$(mktemp /tmp/perf.data2.kernel_report.XXXXXX)
+ local data_clean
+ data_clean=$(mktemp /tmp/perf.data.clean.XXXXXX)
+ local log_file
+ log_file=$(mktemp /tmp/kernel_report_record.log.XXXXXX)
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${prog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ rm -f "${kdata}" "${log_file}"
+ 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)"
+ rm -f "${kdata}" "${log_file}"
+ 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}"
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}"
+
+ # 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"
+
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); \
+ gsub(/0x[0-9a-f]+/, "[addr]", $0); \
+ gsub(/ffff[0-9a-f]+/, "[addr]", $0); \
+ gsub(/0000000000000000/, "[addr]", $0); \
+ print}' "${report1_clean}" > "${report1_norm}"
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); \
+ gsub(/0x[0-9a-f]+/, "[addr]", $0); \
+ gsub(/ffff[0-9a-f]+/, "[addr]", $0); \
+ gsub(/0000000000000000/, "[addr]", $0); \
+ print}' "${report2_clean}" > "${report2_norm}"
+
+ # Calculate sum of percentages for [kernel] samples
+ sum1=$(awk '/\[kernel\]/ {sum += $1} END {print sum}' "${report1_norm}")
+ sum2=$(awk '/\[kernel\]/ {sum += $1} END {print sum}' "${report2_norm}")
+
+ echo "Kernel report sums: sum1=$sum1, sum2=$sum2"
+
+ # Compare sums with tolerance
+ if awk -v s1="$sum1" -v s2="$sum2" \
+ 'BEGIN {diff = s1 - s2; if (diff < 0) diff = -diff; if (diff < 0.05) exit 0; else exit 1}'; then
+ echo "Kernel Report ASLR test [Success]"
+ else
+ echo "Kernel Report ASLR test [Failed - sums differ too much]"
+ err=1
+ fi
+
+ rm -f "${kdata}" "${kdata2}" "${data_clean}" "${log_file}" \
+ "${kdata}.old" "${kdata2}.old" "${data_clean}.old"
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup
+exit $err
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-04-25 2:05 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-24 22:05 [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
2026-04-24 22:05 ` [PATCH v1 2/2] perf test: Add inject ASLR test Ian Rogers
2026-04-24 22:47 ` sashiko-bot
2026-04-24 22:36 ` [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses sashiko-bot
2026-04-25 2:05 ` [PATCH v2 " Ian Rogers
2026-04-25 2:05 ` [PATCH v2 2/2] perf test: Add inject ASLR test Ian Rogers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox