From nobody Mon Jun 8 04:24:55 2026 Received: from mail-dy1-f202.google.com (mail-dy1-f202.google.com [74.125.82.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1514E3203B6 for ; Sun, 7 Jun 2026 21:37:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868234; cv=none; b=kLjMRS8a0EBCRQwiJc/kkjLvAsn1FTLbMg9xabjaoAOKK4ltpxUIBrzATk0V0nnvO7jD9f1ef8WE3fv5WcqAslVyqYdcwfFOEiFpS7RIzU14jb+kTYL6iM1JUFt7Qc9E2NuLK/rVNJl5QbA/BcfeLpm+7H1mrAD50xu8CnsmdUA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868234; c=relaxed/simple; bh=2RVcWUqIVwoyVmYrdvYeK5CQMoxlCp+CKrq/aZKQ1+o=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=FsepvRpWhJIrEs8CyQ9XZo9SSDhCeDu8KPNQ/BJOzHnBLAB3pJy1zK38rsRAK2WXl8kGlDhW0BIWkXTc23yd2yJ5DXb/nnguYcc0eEzAFBMF2eDhbwM/Tmk9GSgFb+pg8lJ5vRzTjeYdXaDrYex9iwY5VopNq7wQ8D8OTydrZtg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=Iq5rrM+k; arc=none smtp.client-ip=74.125.82.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="Iq5rrM+k" Received: by mail-dy1-f202.google.com with SMTP id 5a478bee46e88-30761ab3483so7019351eec.0 for ; Sun, 07 Jun 2026 14:37:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1780868231; x=1781473031; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=5ggl5TjTDOp3j8NcRMD+U0EgWobSr6C/ivFQe7eIhH0=; b=Iq5rrM+kwz1+0dlyX3xmthAlyFpkWFAoQ5OjHHajL06+KiwsyiRk2TZHXSbaKGfJDV VqXy+snnlkg3ST17x+ZBXZNgRXc0FYC/maJLGwAedON/BrGJX2ATILu8rTLCY63ZDrfN +nWdCShlw+oUgtE2tJSerJIb3Rk+Y04aAYu/LPwP/HxTN+tQpknF9teVVzZZGgaN+F9G zi4HuXVdiDaq1yvJU64L95LeIKL5x6xu/N8PGfCi0j4QbmVskCjG0IR1k8aVw0wjvaRT ZYzrwBgVsrCC97XcsDn9A9TcHEnZ191TA9XTrA73GxYbkf0dylh8uhCPtaFVpXMxT4Y3 U0HQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780868231; x=1781473031; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=5ggl5TjTDOp3j8NcRMD+U0EgWobSr6C/ivFQe7eIhH0=; b=os0x7UB3TtkZok16/Wz/C/H+uLtu48o6PS8Q57coL1M6niOywm5ZWLscjlfCskTMLS oC1Bzb9wLOoiqmakv9LNSgyzts1vOzAQdiq0fU7wW/2NwY8r8T+m910x0opcgtr4R9lb icDwB3VnzVP0InbyDszODuuOSjaaGCgFWeAtce8veOQ7CBf76ExMMmq69knwHeuTP+h5 l3gdqJ2X3D8M5DaR+YVwwsv4bU51x7G8iozd1BeRLnvSQvTOMojPkMPHmQ8bETnwkyX+ xxxfQpuX9to5bhbSHq+9EMPfA5IJIqU157men11nOhInFKpSB2NNeIGRWZP3uqA/uaJB aQzg== X-Forwarded-Encrypted: i=1; AFNElJ8GLsYfGzCG0NUodK/sGkFHHd8A4XWyyOMvjP+mWahP+F9R43WoKw/5/TLA/4sih7BHt333qZtiHJbzrYM=@vger.kernel.org X-Gm-Message-State: AOJu0Yz45/Za0MpWRFd65rC/z3ZevUesdbbQuPG8ejt3hlmHELFU9ycs pTScW4rnQWgP6v9jZNY5nCY95ysky2F4mCyPtw1+viKEYHYqQAcxWpQjtLru0ncvp+6TPxzL+dh Ab2weN+bA2Q== X-Received: from dlbqq2.prod.google.com ([2002:a05:7022:ed02:b0:138:17ae:de57]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:ea81:20b0:138:12fa:3788 with SMTP id a92af1059eb24-13812fa38c7mr2818531c88.23.1780868230955; Sun, 07 Jun 2026 14:37:10 -0700 (PDT) Date: Sun, 7 Jun 2026 14:36:56 -0700 In-Reply-To: <20260607213700.3563842-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260607060933.3274263-1-irogers@google.com> <20260607213700.3563842-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.1032.g2f8565e1d1-goog Message-ID: <20260607213700.3563842-2-irogers@google.com> Subject: [PATCH v18 1/5] perf maps: Add maps__mutate_mapping From: Ian Rogers To: irogers@google.com, acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, gmx@google.com, james.clark@linaro.org, jolsa@kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" During kernel ELF symbol parsing (dso__process_kernel_symbol), proc kallsyms image loading (dso__load_kernel_sym, dso__load_guest_kernel_sym), and dynamic kernel memory map alignment updates (machine__update_kernel_mmap), the loader directly modifies live virtual address boundary keys fields on map objects. If these boundaries are mutated while the map pointer actively resides inside the parent maps cache array list (kmaps) outside of any lock closure, an unsafe concurrent window is exposed where parallel worker lookup threads (e.g., inside perf top) can mistakenly assume the cache remains sorted based on stale parameters, executing binary search queries (bsearch) across an unsorted range and triggering lookup failures. Fix this by introducing maps__mutate_mapping() that explicitly acquires the parent maps write semaphore lock, executes an incoming mutation callback block to perform the field updates under lock protection, and invalidates the sorted tracking flags prior to releasing the write lock. This guarantees synchronization invariants, closing the concurrent lookup race window. The adjacent module alignment pass inside machine__create_kernel_maps() is safely preserved as a high-performance lockless pass, as its invocation lifecycle bounds remain strictly single-threaded by contract during session initialization construction. To safely support this unconditional down_write write lock mutator without recursive read-to-write self-deadlock upgrades during lazy symbol loading, we introduce a public maps__load_maps() API. It copies map pointers under a brief read lock and force-loads all modules locklessly outside the lock. Callers (such as perf inject) must pre-load all kernel symbol maps up front at startup using maps__load_maps(), completely bypassing dynamic runtime mutations. Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from= vmlinux") Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.1-pro --- tools/perf/util/machine.c | 32 +++++--- tools/perf/util/maps.c | 149 ++++++++++++++++++++++++++++------- tools/perf/util/maps.h | 3 + tools/perf/util/symbol-elf.c | 41 ++++++---- tools/perf/util/symbol.c | 17 +++- 5 files changed, 184 insertions(+), 58 deletions(-) diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index da1ad58758af..1ea06fde14e0 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -1539,22 +1539,30 @@ static void machine__set_kernel_mmap(struct machine= *machine, map__set_end(machine->vmlinux_map, ~0ULL); } =20 -static int machine__update_kernel_mmap(struct machine *machine, - u64 start, u64 end) +struct kernel_mmap_mutation_ctx { + u64 start; + u64 end; +}; + +static int kernel_mmap_mutate_cb(struct map *map, void *data) { - struct map *orig, *updated; - int err; + struct kernel_mmap_mutation_ctx *ctx =3D data; =20 - orig =3D machine->vmlinux_map; - updated =3D map__get(orig); + map__set_start(map, ctx->start); + map__set_end(map, ctx->end); + if (ctx->start =3D=3D 0 && ctx->end =3D=3D 0) + map__set_end(map, ~0ULL); + return 0; +} =20 - machine->vmlinux_map =3D updated; - maps__remove(machine__kernel_maps(machine), orig); - machine__set_kernel_mmap(machine, start, end); - err =3D maps__insert(machine__kernel_maps(machine), updated); - map__put(orig); +static int machine__update_kernel_mmap(struct machine *machine, + u64 start, u64 end) +{ + struct kernel_mmap_mutation_ctx ctx =3D { .start =3D start, .end =3D end = }; =20 - return err; + return maps__mutate_mapping(machine__kernel_maps(machine), + machine->vmlinux_map, + kernel_mmap_mutate_cb, &ctx); } =20 int machine__create_kernel_maps(struct machine *machine) diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c index 923935ee21b6..b1b8efe42149 100644 --- a/tools/perf/util/maps.c +++ b/tools/perf/util/maps.c @@ -576,6 +576,49 @@ void maps__remove(struct maps *maps, struct map *map) #endif } =20 +/** + * maps__mutate_mapping - Apply write-protected mutations to a map. + * @maps: The maps collection containing the map. + * @map: The map to mutate. + * @mutate_cb: Callback function that performs the actual mutations. + * @data: Private data passed to the callback. + * + * This acquires the write lock on the maps semaphore to safely protect + * concurrent readers from seeing partially mutated or unsorted map bounda= ries. + * + * WARNING: Acquiring down_write() here can trigger a recursive self-deadl= ock if + * the caller already holds the read lock (e.g., during maps__for_each_map= () or + * maps__find() iteration paths that trigger lazy symbol loading). To comp= letely + * avoid this deadlock, all kernel/module maps must be pre-loaded up-front= (via + * maps__load_maps()) under a clean, single-threaded context before enteri= ng + * multi-threaded event processing loops. + */ +int maps__mutate_mapping(struct maps *maps, struct map *map, + int (*mutate_cb)(struct map *map, void *data), void *data) +{ + int err =3D 0; + + if (maps) + down_write(maps__lock(maps)); + + err =3D mutate_cb(map, data); + + if (maps) { + RC_CHK_ACCESS(maps)->maps_by_address_sorted =3D false; + RC_CHK_ACCESS(maps)->maps_by_name_sorted =3D false; + } + + if (maps) + up_write(maps__lock(maps)); + +#ifdef HAVE_LIBDW_SUPPORT + if (maps) + libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps)); +#endif + + return err; +} + bool maps__empty(struct maps *maps) { bool res; @@ -626,6 +669,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(st= ruct map *map, void *data) return ret; } =20 +int maps__load_maps(struct maps *maps) +{ + struct map **maps_copy; + unsigned int nr_maps; + int err =3D 0; + + if (!maps) + return 0; + + down_read(maps__lock(maps)); + nr_maps =3D maps__nr_maps(maps); + if (nr_maps =3D=3D 0) { + up_read(maps__lock(maps)); + return 0; + } + maps_copy =3D calloc(nr_maps, sizeof(*maps_copy)); + if (!maps_copy) { + up_read(maps__lock(maps)); + return -ENOMEM; + } + for (unsigned int i =3D 0; i < nr_maps; i++) + maps_copy[i] =3D map__get(maps__maps_by_address(maps)[i]); + up_read(maps__lock(maps)); + + for (unsigned int i =3D 0; i < nr_maps; i++) { + if (map__load(maps_copy[i]) < 0) { + pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i]))= ); + err =3D -1; + } + map__put(maps_copy[i]); + } + free(maps_copy); + return err; +} + void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void= *data), void *data) { struct map **maps_by_address; @@ -668,40 +746,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u= 64 addr, struct map **mapp) return result; } =20 -struct maps__find_symbol_by_name_args { - struct map **mapp; - const char *name; - struct symbol *sym; -}; - -static int maps__find_symbol_by_name_cb(struct map *map, void *data) +struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *na= me, struct map **mapp) { - struct maps__find_symbol_by_name_args *args =3D data; + struct map **maps_copy; + unsigned int nr_maps; + struct symbol *sym =3D NULL; =20 - args->sym =3D map__find_symbol_by_name(map, args->name); - if (!args->sym) - return 0; + if (!maps) + return NULL; =20 - if (!map__contains_symbol(map, args->sym)) { - args->sym =3D NULL; - return 0; + /* + * First, ensure all maps are loaded. We pre-load them outside of any + * read-to-write locks to avoid deadlocks. Even if some fail, we proceed. + */ + maps__load_maps(maps); + + /* + * Create a local snapshot of the maps while holding the read lock. + * This prevents deadlocking if iteration triggers further map insertions. + */ + down_read(maps__lock(maps)); + nr_maps =3D maps__nr_maps(maps); + maps_copy =3D calloc(nr_maps, sizeof(*maps_copy)); + if (maps_copy) { + for (unsigned int i =3D 0; i < nr_maps; i++) { + struct map *map =3D maps__maps_by_address(maps)[i]; + + maps_copy[i] =3D map__get(map); + } } + up_read(maps__lock(maps)); =20 - if (args->mapp !=3D NULL) - *args->mapp =3D map__get(map); - return 1; -} + if (!maps_copy) + return NULL; =20 -struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *na= me, struct map **mapp) -{ - struct maps__find_symbol_by_name_args args =3D { - .mapp =3D mapp, - .name =3D name, - .sym =3D NULL, - }; + for (unsigned int i =3D 0; i < nr_maps; i++) { + struct map *map =3D maps_copy[i]; + + sym =3D map__find_symbol_by_name(map, name); + if (sym && map__contains_symbol(map, sym)) { + if (mapp) + *mapp =3D map__get(map); + break; + } + sym =3D NULL; + } + + for (unsigned int i =3D 0; i < nr_maps; i++) + map__put(maps_copy[i]); =20 - maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args); - return args.sym; + free(maps_copy); + return sym; } =20 int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams) diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h index 5b80b199685e..4ec9b7453a3b 100644 --- a/tools/perf/util/maps.h +++ b/tools/perf/util/maps.h @@ -59,8 +59,11 @@ void maps__set_libdw_addr_space_dwfl(struct maps *maps, = void *dwfl); =20 size_t maps__fprintf(struct maps *maps, FILE *fp); =20 +int maps__load_maps(struct maps *maps); int maps__insert(struct maps *maps, struct map *map); void maps__remove(struct maps *maps, struct map *map); +int maps__mutate_mapping(struct maps *maps, struct map *map, + int (*mutate_cb)(struct map *map, void *data), void *data); =20 struct map *maps__find(struct maps *maps, u64 addr); struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map *= *mapp); diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 186e6d92ac3d..d1e93c0556dd 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -1342,6 +1342,24 @@ static u64 ref_reloc(struct kmap *kmap) void __weak arch__sym_update(struct symbol *s __maybe_unused, GElf_Sym *sym __maybe_unused) { } =20 +struct remap_kernel_ctx { + u64 sh_addr; + u64 sh_size; + u64 sh_offset; + struct kmap *kmap; +}; + +static int remap_kernel_cb(struct map *map, void *data) +{ + struct remap_kernel_ctx *ctx =3D data; + + map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap)); + map__set_end(map, map__start(map) + ctx->sh_size); + map__set_pgoff(map, ctx->sh_offset); + map__set_mapping_type(map, MAPPING_TYPE__DSO); + return 0; +} + static int dso__process_kernel_symbol(struct dso *dso, struct map *map, GElf_Sym *sym, GElf_Shdr *shdr, struct maps *kmaps, struct kmap *kmap, @@ -1372,22 +1390,15 @@ static int dso__process_kernel_symbol(struct dso *d= so, struct map *map, * map to the kernel dso. */ if (*remap_kernel && dso__kernel(dso) && !kmodule) { + struct remap_kernel_ctx ctx =3D { + .sh_addr =3D shdr->sh_addr, + .sh_size =3D shdr->sh_size, + .sh_offset =3D shdr->sh_offset, + .kmap =3D kmap + }; + *remap_kernel =3D false; - map__set_start(map, shdr->sh_addr + ref_reloc(kmap)); - map__set_end(map, map__start(map) + shdr->sh_size); - map__set_pgoff(map, shdr->sh_offset); - map__set_mapping_type(map, MAPPING_TYPE__DSO); - /* Ensure maps are correctly ordered */ - if (kmaps) { - int err; - struct map *tmp =3D map__get(map); - - maps__remove(kmaps, map); - err =3D maps__insert(kmaps, map); - map__put(tmp); - if (err) - return err; - } + maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx); } =20 /* diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 0c46b24ee098..2cc911af8c81 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -48,6 +48,13 @@ #include #include =20 +static int map_fixup_cb(struct map *map, void *data __maybe_unused) +{ + map__fixup_start(map); + map__fixup_end(map); + return 0; +} + static int dso__load_kernel_sym(struct dso *dso, struct map *map); static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map); =20 @@ -2240,10 +2247,11 @@ static int dso__load_kernel_sym(struct dso *dso, st= ruct map *map) free(kallsyms_allocated_filename); =20 if (err > 0 && !dso__is_kcore(dso)) { + struct maps *kmaps =3D map__kmaps(map); + dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS); dso__set_long_name(dso, DSO__NAME_KALLSYMS, false); - map__fixup_start(map); - map__fixup_end(map); + maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL); } =20 return err; @@ -2283,10 +2291,11 @@ static int dso__load_guest_kernel_sym(struct dso *d= so, struct map *map) if (err > 0) pr_debug("Using %s for symbols\n", kallsyms_filename); if (err > 0 && !dso__is_kcore(dso)) { + struct maps *kmaps =3D map__kmaps(map); + dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS); dso__set_long_name(dso, machine->mmap_name, false); - map__fixup_start(map); - map__fixup_end(map); + maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL); } =20 return err; --=20 2.54.0.1032.g2f8565e1d1-goog From nobody Mon Jun 8 04:24:55 2026 Received: from mail-dy1-f202.google.com (mail-dy1-f202.google.com [74.125.82.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 63A2F2E0925 for ; Sun, 7 Jun 2026 21:37:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868236; cv=none; b=L/YcNjHHLe1l+OLQslJgoqgsK6KUVi+MPpwyBT+5xgK732HYSXII+xs8tgTstLXRkZbWdQ/wpCF9DMgehM0N41zMz8ISEKSUWUlFCuyicmuFqoMhp66Kze7WeUXm2ffo7+XV/9CE6I9C49d4ZDNSqh7TNBSdq2dqkVkAcOKVwi0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868236; c=relaxed/simple; bh=Q1C1p20JsCyQvBs4BQlBuLKai96375EbIaEiZwmywPg=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=uK9XyGVPb6FR5piLjdYDJ0ngkPzyvij/pxeJrpINIpk5UTDi/DUe8j3xL7fB7DFCO52CB64qitTYsnO7vwQTjsM4pk+jlWFMkSs88BnAnCfDU4qFCtkMdbCABIsF6pJbXXNldlfD70S/Y8Of3pt/jreIqZ7VJ7NIB9XpwAqL9cY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=txVf8Hs6; arc=none smtp.client-ip=74.125.82.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="txVf8Hs6" Received: by mail-dy1-f202.google.com with SMTP id 5a478bee46e88-304ea42b025so1916561eec.0 for ; Sun, 07 Jun 2026 14:37:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1780868234; x=1781473034; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=TLP1ylDLigM1oHgXsME/Fu+sbGylGWjRWtjmdJTyUJM=; b=txVf8Hs6qGuONj+xSPJ7H49cKwp0+jJhTL49DiuDUURH40+tOmXaDla3US61RD7xch 06hiQ3/9IOLt9P0gb45TMybSXTw0uTHuor2E0OgJxOlz9Z1iWsF5pc6HrN+vhCJRzo8h QZ4ROk76ka6oIuCcK+u46VyAfM0zWJz3M/dN8wJ60T5Q8RevKC5C4uLG78kU/w5+rwss A+hOAi0riTtDsj2mDmQbiEvgAzayr4X2W6K5mSt1UkFYWa8ckpn5umM4O2w+I5oVhAIa u0HL9ukFMa0ir323VS8H/A3OLJWEODREePXrN439oaBv7/H8kWGqh83Y0Zmt8JV8nEhB CcFg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780868234; x=1781473034; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=TLP1ylDLigM1oHgXsME/Fu+sbGylGWjRWtjmdJTyUJM=; b=IQaY+L7RgmRbfQbIOnZj1ZDzyWyPO1gnA6Qc/ORjrdp5bmV8cduwYUFsP1IRJ/jAGm 6VyKgK8WU8VLzgYWt4VaGysI+bpix8Upc4F5GIKml81sad19cpZtcuCXDw/YSDGc7V98 a81g+lua+oMHG7F1Ja1AnVohAlhvupc6t26wW0sItW70vjqUuOdQdpWpcRkYpysMY+il M8Ycgqo/MUEU0t3e/AxrrL2G6lOxsija5NkqILFDZdnNoCEEd/2Vu0M/50676exEOAs1 6Nzq8hi+MUBtzZ6M6cZVtqO6Ey2bfV88Q4xQwnwg4RryLdUQzQ5FF7r5NX7zvSpvrt+e 7R6Q== X-Forwarded-Encrypted: i=1; AFNElJ+xqMofllaKFKos6T+melQqKIfxzBBXcnvRkNZFgLXcvQkopx45cf0kupzXxdbPBeGgfKOUG3smSkgBaMs=@vger.kernel.org X-Gm-Message-State: AOJu0YxGe9VG3iI5pXytA2QgHFl2A/IvLabfo1FFIQUoI0h68SivF83K BnXq9yZGOczPIc4+/EuYBXW9lMm1TUgKAJAunzYvd/obxLOmyKl+RsAW9U0G6CwjtYHt0pgSi0/ veL9YIMQKFw== X-Received: from dybrq32.prod.google.com ([2002:a05:7301:46a0:b0:307:23d9:99c9]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:693c:20d1:10b0:2ee:7b2e:8a3e with SMTP id 5a478bee46e88-3077fd76cc3mr3031472eec.1.1780868233291; Sun, 07 Jun 2026 14:37:13 -0700 (PDT) Date: Sun, 7 Jun 2026 14:36:57 -0700 In-Reply-To: <20260607213700.3563842-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260607060933.3274263-1-irogers@google.com> <20260607213700.3563842-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.1032.g2f8565e1d1-goog Message-ID: <20260607213700.3563842-3-irogers@google.com> Subject: [PATCH v18 2/5] perf inject/aslr: Add ASLR tool infrastructure and MMAP tracking From: Ian Rogers To: irogers@google.com, acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, gmx@google.com, james.clark@linaro.org, jolsa@kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" If perf.data files are taken from one machine to another they may leak virtual addresses and so weaken ASLR on the machine they are coming from. Add an aslr option for perf inject that remaps all virtual addresses, or drops data/events, so that the virtual address information isn't leaked. This patch introduces the core ASLR remapping tool infrastructure and implements remapping/tracking for metadata events (MMAP, MMAP2, COMM, FORK, EXIT, KSYMBOL, TEXT_POKE). Sample events are delegated without remapping for now. Signed-off-by: Ian Rogers Co-developed-by: Gabriel Marin Signed-off-by: Gabriel Marin Assisted-by: Antigravity:gemini-3.1-pro --- tools/perf/builtin-inject.c | 61 ++- tools/perf/util/Build | 1 + tools/perf/util/aslr.c | 825 ++++++++++++++++++++++++++++++++++++ tools/perf/util/aslr.h | 41 ++ 4 files changed, 919 insertions(+), 9 deletions(-) create mode 100644 tools/perf/util/aslr.c create mode 100644 tools/perf/util/aslr.h diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 75ffe31d03fe..8bb37095e2de 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -8,6 +8,7 @@ */ #include "builtin.h" =20 +#include "util/aslr.h" #include "util/color.h" #include "util/dso.h" #include "util/vdso.h" @@ -24,6 +25,7 @@ #include "util/string2.h" #include "util/symbol.h" #include "util/synthetic-events.h" +#include "util/pmus.h" #include "util/thread.h" #include "util/namespaces.h" #include "util/unwind.h" @@ -124,6 +126,7 @@ struct perf_inject { bool in_place_update_dry_run; bool copy_kcore_dir; bool convert_callchain; + bool aslr; const char *input_name; struct perf_data output; u64 bytes_written; @@ -234,20 +237,36 @@ static int perf_event__repipe_attr(const struct perf_= tool *tool, u64 *ids; int ret; =20 + union perf_event *aslr_event =3D NULL; + ret =3D perf_event__process_attr(tool, event, pevlist); if (ret) return ret; =20 + if (inject->aslr) { + aslr_event =3D malloc(event->header.size); + if (!aslr_event) + return -ENOMEM; + memcpy(aslr_event, event, event->header.size); + aslr_tool__strip_attr_event(aslr_event, pevlist); + event =3D aslr_event; + } + /* If the output isn't a pipe then the attributes will be written as part= of the header. */ - if (!inject->output.is_pipe) - return 0; + if (!inject->output.is_pipe) { + ret =3D 0; + goto out; + } =20 - if (!inject->itrace_synth_opts.set) - return perf_event__repipe_synth(tool, event); + if (!inject->itrace_synth_opts.set) { + ret =3D perf_event__repipe_synth(tool, event); + goto out; + } =20 if (event->header.size < sizeof(struct perf_event_header) + PERF_ATTR_SIZ= E_VER0) { pr_err("Attribute event size %u is too small\n", event->header.size); - return -EINVAL; + ret =3D -EINVAL; + goto out; } =20 /* @@ -263,7 +282,8 @@ static int perf_event__repipe_attr(const struct perf_to= ol *tool, raw_attr_size > event->header.size - sizeof(event->header))) { pr_err("Attribute event size %u is too small for attr.size %u\n", event->header.size, raw_attr_size); - return -EINVAL; + ret =3D -EINVAL; + goto out; } =20 memset(&attr, 0, sizeof(attr)); @@ -281,8 +301,11 @@ static int perf_event__repipe_attr(const struct perf_t= ool *tool, attr.sample_type |=3D PERF_SAMPLE_BRANCH_STACK; attr.branch_sample_type |=3D PERF_SAMPLE_BRANCH_HW_INDEX; } - return perf_event__synthesize_attr(tool, &attr, (u32)n_ids, ids, + ret =3D perf_event__synthesize_attr(tool, &attr, (u32)n_ids, ids, perf_event__repipe_synth_cb); +out: + free(aslr_event); + return ret; } =20 static int perf_event__repipe_event_update(const struct perf_tool *tool, @@ -2594,7 +2617,6 @@ static int __cmd_inject(struct perf_inject *inject) evsel->core.attr.exclude_callchain_user =3D 0; } } - session->header.data_offset =3D output_data_offset; session->header.data_size =3D inject->bytes_written; perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc, @@ -2704,6 +2726,8 @@ int cmd_inject(int argc, const char **argv) unwind__option), OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain, "Generate callchains using DWARF and drop register/stack data"), + OPT_BOOLEAN(0, "aslr", &inject.aslr, + "Remap virtual memory addresses similar to ASLR"), OPT_END() }; const char * const inject_usage[] =3D { @@ -2711,6 +2735,7 @@ int cmd_inject(int argc, const char **argv) NULL }; bool ordered_events; + struct perf_tool *tool =3D &inject.tool; =20 if (!inject.itrace_synth_opts.set) { /* Disable eager loading of kernel symbols that adds overhead to perf in= ject. */ @@ -2731,6 +2756,11 @@ int cmd_inject(int argc, const char **argv) if (argc) usage_with_options(inject_usage, options); =20 + if (inject.aslr && inject.convert_callchain) { + pr_err("Error: --aslr and --convert-callchain are mutually exclusive fea= tures.\n"); + return -EINVAL; + } + if (inject.strip && !inject.itrace_synth_opts.set) { pr_err("--strip option requires --itrace option\n"); return -1; @@ -2824,12 +2854,21 @@ int cmd_inject(int argc, const char **argv) inject.tool.schedstat_domain =3D perf_event__repipe_op2_synth; inject.tool.dont_split_sample_group =3D true; inject.tool.merge_deferred_callchains =3D false; - inject.session =3D __perf_session__new(&data, &inject.tool, + if (inject.aslr) { + tool =3D aslr_tool__new(&inject.tool); + if (!tool) { + ret =3D -ENOMEM; + goto out_close_output; + } + } + inject.session =3D __perf_session__new(&data, tool, /*trace_event_repipe=3D*/inject.output.is_pipe, /*host_env=3D*/NULL); =20 if (IS_ERR(inject.session)) { ret =3D PTR_ERR(inject.session); + if (inject.aslr) + aslr_tool__delete(tool); goto out_close_output; } =20 @@ -2922,6 +2961,8 @@ int cmd_inject(int argc, const char **argv) goto out_delete; =20 ret =3D __cmd_inject(&inject); + if (inject.aslr) + aslr_tool__strip_evlist(tool, inject.session->evlist); =20 guest_session__exit(&inject.guest_session); =20 @@ -2929,6 +2970,8 @@ int cmd_inject(int argc, const char **argv) strlist__delete(inject.known_build_ids); zstd_fini(&(inject.session->zstd_data)); perf_session__delete(inject.session); + if (inject.aslr) + aslr_tool__delete(tool); out_close_output: if (!inject.in_place_update) perf_data__close(&inject.output); diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 4bbc78b1f741..19994e026ae5 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -6,6 +6,7 @@ perf-util-y +=3D arm64-frame-pointer-unwind-support.o perf-util-y +=3D addr2line.o perf-util-y +=3D addr_location.o perf-util-y +=3D annotate.o +perf-util-y +=3D aslr.o perf-util-y +=3D blake2s.o perf-util-y +=3D block-info.o perf-util-y +=3D block-range.o diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c new file mode 100644 index 000000000000..e45f68c60493 --- /dev/null +++ b/tools/perf/util/aslr.c @@ -0,0 +1,825 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "aslr.h" + +#include "addr_location.h" +#include "debug.h" +#include "event.h" +#include "evsel.h" +#include "evlist.h" +#include "machine.h" +#include "map.h" +#include "thread.h" +#include "tool.h" +#include "session.h" +#include "data.h" +#include "dso.h" +#include "pmus.h" + +#include /* page_size */ +#include +#include +#include +#include + +/** + * struct remap_addresses_key - Key for mapping original addresses to rema= pped ones. + * @dso: Pointer to the DSO (Dynamic Shared Object) associated with the ma= pping. + * @invariant: Unique offset invariant within the VMA (Virtual Memory Area= ). + * Calculated as `start - pgoff`. This value remains constant = when + * perf's internal `maps__fixup_overlap_and_insert` splits a m= ap into + * fragmented VMA pieces due to overlapping events, allowing u= s to + * resolve split maps consistently back to the original VMA. + * @pid: Process ID associated with the mapping. + */ +struct remap_addresses_key { + struct machine *machine; + struct dso *dso; + u64 invariant; + pid_t pid; +}; + +struct aslr_mapping { + struct list_head node; + u64 orig_start; + u64 len; + u64 remap_start; +}; + +struct process_top_address { + u64 remapped_max; +}; +struct aslr_tool { + /** @tool: The tool implemented here and a pointer to a delegate to proce= ss the data. */ + struct delegate_tool tool; + /** @machines: The machines with the input, not remapped, virtual address= layout. */ + struct machines machines; + /** @event_copy: Buffer used to create an event to pass to the delegate. = */ + char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8); + /** @remap_addresses: mapping from remap_addresses_key to remapped addres= s. */ + struct hashmap remap_addresses; + /** @top_addresses: mapping from process to max remapped address. */ + struct hashmap top_addresses; +}; + +static const pid_t kernel_pid =3D -1; + +/* Start remapping user processes from a small non-zero offset. */ +static const u64 user_space_start =3D 0x200000; +static const u64 kernel_space_start_64 =3D 0xffff800010000000ULL; +static const u64 kernel_space_start_32 =3D 0x80000000ULL; + +static size_t remap_addresses__hash(long _key, void *ctx __maybe_unused) +{ + struct remap_addresses_key *key =3D (struct remap_addresses_key *)_key; + void *dso_ptr =3D key->dso ? RC_CHK_ACCESS(key->dso) : NULL; + + return (size_t)key->machine ^ (size_t)dso_ptr ^ key->invariant ^ key->pid; +} + +static bool remap_addresses__equal(long _key1, long _key2, void *ctx __may= be_unused) +{ + struct remap_addresses_key *key1 =3D (struct remap_addresses_key *)_key1; + struct remap_addresses_key *key2 =3D (struct remap_addresses_key *)_key2; + + return key1->machine =3D=3D key2->machine && + RC_CHK_EQUAL(key1->dso, key2->dso) && + key1->invariant =3D=3D key2->invariant && + key1->pid =3D=3D key2->pid; +} + +struct top_addresses_key { + struct machine *machine; + pid_t pid; +}; + +static size_t top_addresses__hash(long _key, void *ctx __maybe_unused) +{ + struct top_addresses_key *key =3D (struct top_addresses_key *)_key; + + return (size_t)key->machine ^ key->pid; +} + +static bool top_addresses__equal(long _key1, long _key2, void *ctx __maybe= _unused) +{ + struct top_addresses_key *key1 =3D (struct top_addresses_key *)_key1; + struct top_addresses_key *key2 =3D (struct top_addresses_key *)_key2; + + return key1->machine =3D=3D key2->machine && key1->pid =3D=3D key2->pid; +} + +static u64 round_up_to_page_size(u64 addr) +{ + return (addr + page_size - 1) & ~((u64)page_size - 1); +} + +struct aslr_machine_priv { + bool kernel_maps_loaded; +}; + +static int aslr_tool__preload_kernel_maps(struct machine *machine) +{ + struct aslr_machine_priv *mpriv =3D machine->priv; + + if (!mpriv) { + mpriv =3D zalloc(sizeof(*mpriv)); + if (!mpriv) + return -ENOMEM; + machine->priv =3D mpriv; + } + + if (!mpriv->kernel_maps_loaded) { + struct maps *kmaps =3D machine__kernel_maps(machine); + + if (kmaps) { + int err =3D maps__load_maps(kmaps); + + if (err < 0) { + pr_err("ASLR: Failed to preload kernel maps for machine pid %d\n", + machine->pid); + return err; + } + } + mpriv->kernel_maps_loaded =3D true; + } + return 0; +} + +static void aslr_tool__free_machine_priv(struct machine *machine) +{ + free(machine->priv); + machine->priv =3D NULL; +} + +static void aslr_tool__destroy_machines_priv(struct machines *machines) +{ + struct rb_node *nd; + + aslr_tool__free_machine_priv(&machines->host); + for (nd =3D rb_first_cached(&machines->guests); nd; nd =3D rb_next(nd)) { + struct machine *machine =3D rb_entry(nd, struct machine, rb_node); + + aslr_tool__free_machine_priv(machine); + } +} + +static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr, + struct machine *session_machine, + struct thread *aslr_thread, + u8 cpumode, u64 start, + u64 len, u64 pgoff) +{ + /* Address location for dso lookup. */ + struct addr_location al; + /* Original ASLR address based key for the remap table. */ + struct remap_addresses_key remap_key; + /* The address in the ASLR sanitized address space less pg_off. */ + u64 *remapped_invariant_ptr; + /* Key for the maximum address in a process. */ + struct top_addresses_key top_addr_key; + /* Value in top address table. */ + struct process_top_address *top =3D NULL; + /* Address in ASLR sanitized address space. */ + u64 remap_addr; + /* Potentially allocated remap table key. */ + struct remap_addresses_key *new_remap_key =3D NULL; + /* + * Potentially allocated remap table key. + * TODO: Avoid allocation necessary for perf 32-bit binary support. + */ + u64 *new_remap_val =3D NULL; + int err; + + if (!aslr_thread) + return 0; + + /* The key to look up an incoming address to the outgoing value. */ + addr_location__init(&al); + remap_key.machine =3D maps__machine(thread__maps(aslr_thread)); + remap_key.pid =3D (cpumode =3D=3D PERF_RECORD_MISC_KERNEL || + cpumode =3D=3D PERF_RECORD_MISC_GUEST_KERNEL) ? + kernel_pid : thread__pid(aslr_thread); + if (thread__find_map(aslr_thread, cpumode, start, &al)) { + struct dso *dso =3D map__dso(al.map); + const char *dso_name =3D dso ? dso__long_name(dso) : NULL; + + remap_key.dso =3D dso; + if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name)) + remap_key.invariant =3D map__start(al.map) - map__pgoff(al.map); + else + remap_key.invariant =3D map__start(al.map); + } else { + remap_key.dso =3D NULL; + remap_key.invariant =3D start; + } + + /* The key to look up top allocated address. */ + top_addr_key.machine =3D remap_key.machine; + top_addr_key.pid =3D remap_key.pid; + + if (hashmap__find(&aslr->remap_addresses, &remap_key, &remapped_invariant= _ptr)) { + /* Mmap already exists. */ + u64 calculated_max; + + if (al.map) { + /* + * The cached value is the base of the invariant. We add the + * offset into the VMA (start - map__start), plus the map's + * pgoff, to get the precise virtual address within this chunk. + */ + remap_addr =3D *remapped_invariant_ptr + map__pgoff(al.map) + + (start - map__start(al.map)); + } else { + /* + * For unmapped memory (e.g. kernel anonymous), the cached value + * was stored offset by pgoff. Adding pgoff yields the true remap_addr. + */ + remap_addr =3D *remapped_invariant_ptr + pgoff; + } + + calculated_max =3D remap_addr + len; + + /* See if top mapping was expanded. */ + if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) { + if (calculated_max > top->remapped_max) + top->remapped_max =3D calculated_max; + } + addr_location__exit(&al); + return remap_addr; + } + /* No mmap, create an entry from the top address. */ + if (hashmap__find(&aslr->top_addresses, &top_addr_key, &top)) { + struct addr_location prev_al; + bool is_contiguous =3D false; + + /* Current max allocated mmap address within the process. */ + remap_addr =3D top->remapped_max; + + addr_location__init(&prev_al); + if (thread__find_map(aslr_thread, cpumode, start - 1, &prev_al)) { + if (map__end(prev_al.map) =3D=3D start) + is_contiguous =3D true; + } + addr_location__exit(&prev_al); + + if (is_contiguous) { + /* Contiguous mapping, do not add 1 page gap! */ + remap_addr =3D round_up_to_page_size(remap_addr); + } else { + /* Give 1 page gap from current max page. */ + remap_addr =3D round_up_to_page_size(remap_addr); + remap_addr +=3D page_size; + } + if (remap_addr + len > top->remapped_max) + top->remapped_max =3D remap_addr + len; + } else { + /* First address of the process, allocate key and first top address. */ + struct top_addresses_key *tk; + struct process_top_address *top_val; + struct perf_env *env =3D session_machine ? session_machine->env : NULL; + bool is_64 =3D env ? perf_env__kernel_is_64_bit(env) : (sizeof(void *) = =3D=3D 8); + u64 kernel_start_addr =3D is_64 ? kernel_space_start_64 : kernel_space_s= tart_32; + + remap_addr =3D (cpumode =3D=3D PERF_RECORD_MISC_KERNEL || + cpumode =3D=3D PERF_RECORD_MISC_GUEST_KERNEL) ? + kernel_start_addr : user_space_start; + remap_addr =3D round_up_to_page_size(remap_addr); + + tk =3D malloc(sizeof(*tk)); + top_val =3D malloc(sizeof(*top_val)); + if (!tk || !top_val) { + err =3D -ENOMEM; + } else { + *tk =3D top_addr_key; + top_val->remapped_max =3D remap_addr + len; + err =3D hashmap__insert(&aslr->top_addresses, tk, top_val, + HASHMAP_ADD, NULL, NULL); + } + if (err) { + errno =3D -err; + pr_err("Failure to add ASLR process top address %m\n"); + free(tk); + free(top_val); + addr_location__exit(&al); + return 0; + } + } + /* Create rmeapping entry. */ + new_remap_key =3D malloc(sizeof(*new_remap_key)); + new_remap_val =3D malloc(sizeof(u64)); + if (!new_remap_key || !new_remap_val) { + err =3D -ENOMEM; + } else { + *new_remap_key =3D remap_key; + new_remap_key->dso =3D dso__get(remap_key.dso); + if (cpumode =3D=3D PERF_RECORD_MISC_KERNEL || + cpumode =3D=3D PERF_RECORD_MISC_GUEST_KERNEL) { + if (al.map) { + *new_remap_val =3D remap_addr - + (start - map__start(al.map)) - + map__pgoff(al.map); + } else { + /* + * Subtract pgoff from the base virtual address so that + * when the lookup path adds pgoff back, it perfectly + * cancels out and returns remap_addr. + */ + *new_remap_val =3D remap_addr - pgoff; + } + } else { + *new_remap_val =3D remap_addr - (al.map ? (start - map__start(al.map)) = + map__pgoff(al.map) : pgoff); + } + err =3D hashmap__add(&aslr->remap_addresses, new_remap_key, new_remap_va= l); + if (err) + dso__put(new_remap_key->dso); + } + if (err) { + errno =3D -err; + pr_err("Failure to add ASLR remapping %m\n"); + free(new_remap_key); + free(new_remap_val); + addr_location__exit(&al); + return 0; + } + addr_location__exit(&al); + return remap_addr; +} + +static int aslr_tool__process_mmap(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct perf_tool *delegate; + union perf_event *new_event; + u8 cpumode; + struct thread *thread; + struct machine *aslr_machine; + int err; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + delegate =3D aslr->tool.delegate; + new_event =3D (union perf_event *)aslr->event_copy; + cpumode =3D event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + + aslr_machine =3D machines__findnew(&aslr->machines, machine->pid); + if (!aslr_machine) + return -ENOMEM; + if (aslr_tool__preload_kernel_maps(aslr_machine) < 0) + return -ENOMEM; + + /* Create the thread, map, etc. in the ASLR before virtual address space.= */ + err =3D perf_event__process_mmap(tool, event, sample, aslr_machine); + if (err) + return err; + + thread =3D machine__findnew_thread(aslr_machine, event->mmap.pid, event->= mmap.tid); + if (!thread) + return -ENOMEM; + memcpy(&new_event->mmap, &event->mmap, event->mmap.header.size); + /* Remaps the mmap.start. */ + new_event->mmap.start =3D aslr_tool__findnew_mapping(aslr, machine, threa= d, cpumode, + event->mmap.start, + event->mmap.len, + event->mmap.pgoff); + /* + * For anonymous memory (and kernel maps), the kernel populates the + * event's pgoff field with the original un-obfuscated virtual address + * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT). + * We must overwrite pgoff with the new remapped byte address to prevent + * leaking the original ASLR layout. + */ + if (cpumode =3D=3D PERF_RECORD_MISC_KERNEL || cpumode =3D=3D PERF_RECORD_= MISC_GUEST_KERNEL || + is_anon_memory(event->mmap.filename) || is_no_dso_memory(event->mmap.= filename)) + new_event->mmap.pgoff =3D new_event->mmap.start; + err =3D delegate->mmap(delegate, new_event, sample, machine); + thread__put(thread); + return err; +} + +static int aslr_tool__process_mmap2(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct perf_tool *delegate; + union perf_event *new_event; + u8 cpumode; + struct thread *thread; + struct machine *aslr_machine; + int err; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + delegate =3D aslr->tool.delegate; + new_event =3D (union perf_event *)aslr->event_copy; + cpumode =3D event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + + aslr_machine =3D machines__findnew(&aslr->machines, machine->pid); + if (!aslr_machine) + return -ENOMEM; + if (aslr_tool__preload_kernel_maps(aslr_machine) < 0) + return -ENOMEM; + + /* Create the thread, map, etc. in the ASLR before virtual address space.= */ + err =3D perf_event__process_mmap2(tool, event, sample, aslr_machine); + if (err) + return err; + + thread =3D machine__findnew_thread(aslr_machine, event->mmap2.pid, event-= >mmap2.tid); + if (!thread) + return -ENOMEM; + memcpy(&new_event->mmap2, &event->mmap2, event->mmap2.header.size); + /* Remaps the mmap.start. */ + new_event->mmap2.start =3D aslr_tool__findnew_mapping(aslr, machine, thre= ad, cpumode, + event->mmap2.start, + event->mmap2.len, + event->mmap2.pgoff); + /* + * For anonymous memory (and kernel maps), the kernel populates the + * event's pgoff field with the original un-obfuscated virtual address + * in bytes (i.e. (addr >> PAGE_SHIFT) << PAGE_SHIFT). + * We must overwrite pgoff with the new remapped byte address to prevent + * leaking the original ASLR layout. + */ + if (cpumode =3D=3D PERF_RECORD_MISC_KERNEL || cpumode =3D=3D PERF_RECORD_= MISC_GUEST_KERNEL || + is_anon_memory(event->mmap2.filename) || is_no_dso_memory(event->mmap= 2.filename)) + new_event->mmap2.pgoff =3D new_event->mmap2.start; + err =3D delegate->mmap2(delegate, new_event, sample, machine); + thread__put(thread); + return err; +} + +static int aslr_tool__process_comm(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct perf_tool *delegate; + struct machine *aslr_machine; + int err; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + delegate =3D aslr->tool.delegate; + + aslr_machine =3D machines__findnew(&aslr->machines, machine->pid); + if (!aslr_machine) + return -ENOMEM; + if (aslr_tool__preload_kernel_maps(aslr_machine) < 0) + return -ENOMEM; + + /* Create the thread, map, etc. in the ASLR before virtual address space.= */ + err =3D perf_event__process_comm(tool, event, sample, aslr_machine); + if (err) + return err; + + return delegate->comm(delegate, event, sample, machine); +} + +static int aslr_tool__process_fork(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct perf_tool *delegate; + struct machine *aslr_machine; + int err; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + delegate =3D aslr->tool.delegate; + + aslr_machine =3D machines__findnew(&aslr->machines, machine->pid); + if (!aslr_machine) + return -ENOMEM; + if (aslr_tool__preload_kernel_maps(aslr_machine) < 0) + return -ENOMEM; + + /* Create the thread, map, etc. in the ASLR before virtual address space.= */ + err =3D perf_event__process_fork(tool, event, sample, aslr_machine); + if (err) + return err; + + return delegate->fork(delegate, event, sample, machine); +} + +static int aslr_tool__process_exit(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct perf_tool *delegate; + struct machine *aslr_machine; + int err; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + delegate =3D aslr->tool.delegate; + + aslr_machine =3D machines__findnew(&aslr->machines, machine->pid); + if (!aslr_machine) + return -ENOMEM; + if (aslr_tool__preload_kernel_maps(aslr_machine) < 0) + return -ENOMEM; + + /* Create the thread, map, etc. in the ASLR before virtual address space.= */ + err =3D 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 __may= be_unused, + union perf_event *event __maybe_unused, + struct perf_sample *sample __maybe_unused, + struct machine *machine __maybe_unused) +{ + /* Drop in case the instruction encodes an ASLR revealing address. */ + return 0; +} + +static int aslr_tool__process_ksymbol(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct perf_tool *delegate; + union perf_event *new_event; + struct thread *thread; + struct machine *aslr_machine; + int err; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + delegate =3D aslr->tool.delegate; + new_event =3D (union perf_event *)aslr->event_copy; + + aslr_machine =3D machines__findnew(&aslr->machines, machine->pid); + if (!aslr_machine) + return -ENOMEM; + if (aslr_tool__preload_kernel_maps(aslr_machine) < 0) + return -ENOMEM; + + thread =3D machine__findnew_thread(aslr_machine, kernel_pid, 0); + if (!thread) + return -ENOMEM; + + memcpy(&new_event->ksymbol, &event->ksymbol, event->ksymbol.header.size); + /* Remaps the ksymbol.start before process_ksymbol potentially deletes th= e map */ + new_event->ksymbol.addr =3D aslr_tool__findnew_mapping(aslr, machine, thr= ead, + PERF_RECORD_MISC_KERNEL, + event->ksymbol.addr, + event->ksymbol.len, + /*pgoff=3D*/0); + + err =3D perf_event__process_ksymbol(tool, event, sample, aslr_machine); + if (err) { + thread__put(thread); + return err; + } + + err =3D delegate->ksymbol(delegate, new_event, sample, machine); + thread__put(thread); + return err; +} + +static int aslr_tool__process_sample(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct delegate_tool *del_tool =3D container_of(tool, struct delegate_too= l, tool); + struct aslr_tool *aslr =3D container_of(del_tool, struct aslr_tool, tool); + struct perf_tool *delegate =3D aslr->tool.delegate; + + return delegate->sample(delegate, event, sample, machine); +} + +static int skipn(int fd, off_t n) +{ + char buf[4096]; + ssize_t ret; + + while (n > 0) { + ret =3D read(fd, buf, min_t(off_t, n, (off_t)sizeof(buf))); + if (ret <=3D 0) + return ret; + n -=3D ret; + } + + return 0; +} + +static s64 aslr_tool__process_auxtrace(const struct perf_tool *tool __mayb= e_unused, + struct perf_session *session, + union perf_event *event) +{ + pr_warning_once("ASLR: Dropping auxtrace data as it cannot be obfuscated.= \n"); + if (perf_data__is_pipe(session->data)) { + /* Copy behavior of the stub by reading all pipe data. */ + int err =3D skipn(perf_data__fd(session->data), event->auxtrace.size); + + if (err < 0) + return err; + } + return event->auxtrace.size; +} + +static int aslr_tool__process_auxtrace_info(const struct perf_tool *tool _= _maybe_unused, + struct perf_session *session __maybe_unused, + union perf_event *event __maybe_unused) +{ + return 0; +} + +static int aslr_tool__process_auxtrace_error(const struct perf_tool *tool = __maybe_unused, + struct perf_session *session __maybe_unused, + union perf_event *event __maybe_unused) +{ + return 0; +} + + +void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **= pevlist) +{ + struct evsel *evsel; + bool needs_swap =3D false; + + if (pevlist && *pevlist) { + evsel =3D evlist__last(*pevlist); + if (evsel) + needs_swap =3D evsel->needs_swap; + } + + if (event->header.size >=3D (offsetof(struct perf_record_header_attr, + attr.sample_type) + sizeof(u64))) { + u64 st =3D event->attr.attr.sample_type; + + if (needs_swap) + st =3D bswap_64(st); + + st &=3D ASLR_SUPPORTED_SAMPLE_TYPE; + + if (needs_swap) + st =3D bswap_64(st); + + event->attr.attr.sample_type =3D st; + } + + if (event->header.size >=3D (offsetof(struct perf_record_header_attr, + attr.type) + sizeof(u32))) { + u32 type =3D event->attr.attr.type; + + if (needs_swap) + type =3D bswap_32(type); + + if (type =3D=3D PERF_TYPE_BREAKPOINT && + event->header.size >=3D (offsetof(struct perf_record_header_attr, + attr.bp_addr) + sizeof(u64))) { + event->attr.attr.bp_addr =3D 0; + } else if (type >=3D PERF_TYPE_MAX) { + struct perf_pmu *pmu; + + pmu =3D perf_pmus__find_by_type(type); + if (pmu && (!strcmp(pmu->name, "kprobe") || + !strcmp(pmu->name, "uprobe"))) { + if (event->header.size >=3D + (offsetof(struct perf_record_header_attr, + attr.config1) + sizeof(u64))) + event->attr.attr.config1 =3D 0; + if (event->header.size >=3D + (offsetof(struct perf_record_header_attr, + attr.config2) + sizeof(u64))) + event->attr.attr.config2 =3D 0; + } + } + } +} + +void aslr_tool__strip_evlist(struct perf_tool *tool __maybe_unused, + struct evlist *evlist) +{ + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) { + evsel->core.attr.sample_type &=3D ASLR_SUPPORTED_SAMPLE_TYPE; + + if (evsel->core.attr.type =3D=3D PERF_TYPE_BREAKPOINT) + evsel->core.attr.bp_addr =3D 0; + else if (evsel->core.attr.type >=3D PERF_TYPE_MAX) { + struct perf_pmu *pmu =3D perf_pmus__find_by_type(evsel->core.attr.type); + + if (pmu && (!strcmp(pmu->name, "kprobe") || + !strcmp(pmu->name, "uprobe"))) { + evsel->core.attr.config1 =3D 0; + evsel->core.attr.config2 =3D 0; + } + } + } +} + +static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *dele= gate) +{ + delegate_tool__init(&aslr->tool, delegate); + aslr->tool.tool.ordered_events =3D true; + + machines__init(&aslr->machines); + + hashmap__init(&aslr->remap_addresses, + remap_addresses__hash, remap_addresses__equal, + /*ctx=3D*/NULL); + hashmap__init(&aslr->top_addresses, + top_addresses__hash, top_addresses__equal, + /*ctx=3D*/NULL); + + aslr->tool.tool.sample =3D aslr_tool__process_sample; + /* read - reads a counter, okay to delegate. */ + aslr->tool.tool.mmap =3D aslr_tool__process_mmap; + aslr->tool.tool.mmap2 =3D aslr_tool__process_mmap2; + aslr->tool.tool.comm =3D aslr_tool__process_comm; + aslr->tool.tool.fork =3D aslr_tool__process_fork; + aslr->tool.tool.exit =3D 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 =3D aslr_tool__process_ksymbol; + /* bpf - no virtual address. */ + aslr->tool.tool.text_poke =3D aslr_tool__process_text_poke; + /* + * event_update, tracing_data, finished_round, build_id, id_index, + * auxtrace_info, auxtrace_error, time_conv, thread_map, cpu_map, + * stat_config, stat, feature, finished_init, bpf_metadata, compressed, + * auxtrace - no virtual addresses. + */ + aslr->tool.tool.auxtrace =3D aslr_tool__process_auxtrace; + aslr->tool.tool.auxtrace_info =3D aslr_tool__process_auxtrace_info; + aslr->tool.tool.auxtrace_error =3D aslr_tool__process_auxtrace_error; +} + +struct perf_tool *aslr_tool__new(struct perf_tool *delegate) +{ + struct aslr_tool *aslr =3D zalloc(sizeof(*aslr)); + + if (!aslr) + return NULL; + + aslr_tool__init(aslr, delegate); + return &aslr->tool.tool; +} + +void aslr_tool__delete(struct perf_tool *tool) +{ + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct hashmap_entry *cur; + size_t bkt; + struct rb_node *nd; + + if (!tool) + return; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + + hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) { + struct remap_addresses_key *key =3D (struct remap_addresses_key *)cur->p= key; + + if (key) + dso__put(key->dso); + zfree(&cur->pkey); + zfree(&cur->pvalue); + } + hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) { + zfree(&cur->pkey); + zfree(&cur->pvalue); + } + + hashmap__clear(&aslr->remap_addresses); + hashmap__clear(&aslr->top_addresses); + aslr_tool__destroy_machines_priv(&aslr->machines); + machines__destroy_kernel_maps(&aslr->machines); + + while ((nd =3D rb_first_cached(&aslr->machines.guests)) !=3D NULL) { + struct machine *machine =3D rb_entry(nd, struct machine, rb_node); + + rb_erase_cached(nd, &aslr->machines.guests); + machine__delete(machine); + } + + machines__exit(&aslr->machines); + free(aslr); +} diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h new file mode 100644 index 000000000000..2b82f711bc67 --- /dev/null +++ b/tools/perf/util/aslr.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __PERF_ASLR_H +#define __PERF_ASLR_H + +#include + +#define ASLR_SUPPORTED_SAMPLE_TYPE ( \ + PERF_SAMPLE_IDENTIFIER | \ + PERF_SAMPLE_IP | \ + PERF_SAMPLE_TID | \ + PERF_SAMPLE_TIME | \ + PERF_SAMPLE_ADDR | \ + PERF_SAMPLE_ID | \ + PERF_SAMPLE_STREAM_ID | \ + PERF_SAMPLE_CPU | \ + PERF_SAMPLE_PERIOD | \ + PERF_SAMPLE_READ | \ + PERF_SAMPLE_CALLCHAIN | \ + PERF_SAMPLE_RAW | \ + PERF_SAMPLE_BRANCH_STACK | \ + PERF_SAMPLE_STACK_USER | \ + PERF_SAMPLE_WEIGHT_TYPE | \ + PERF_SAMPLE_DATA_SRC | \ + PERF_SAMPLE_TRANSACTION | \ + PERF_SAMPLE_PHYS_ADDR | \ + PERF_SAMPLE_CGROUP | \ + PERF_SAMPLE_DATA_PAGE_SIZE | \ + PERF_SAMPLE_CODE_PAGE_SIZE | \ + PERF_SAMPLE_AUX) + +struct perf_tool; +struct evsel; +struct evlist; +union perf_event; + +struct perf_tool *aslr_tool__new(struct perf_tool *delegate); +void aslr_tool__delete(struct perf_tool *aslr); +void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **= pevlist); +void aslr_tool__strip_evlist(struct perf_tool *tool, struct evlist *evlist= ); + +#endif /* __PERF_ASLR_H */ --=20 2.54.0.1032.g2f8565e1d1-goog From nobody Mon Jun 8 04:24:55 2026 Received: from mail-dy1-f201.google.com (mail-dy1-f201.google.com [74.125.82.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DB499333440 for ; Sun, 7 Jun 2026 21:37:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868238; cv=none; b=DtYSIR6ks4NqwmL2IzP4Lf13dyc9W2EmUVZ8Rr7PlPsWOqpePvnuVutYoFxlbP8daMYRb0vMzJTgdCuVcBNyju3sWdEZCBQcyRGEHMqOVap+wKlVkl3It4L9buXU/+eUNAdc5pA6GqCt7lfXjkVw007/WSITKR7DOIZlFUQzT2E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868238; c=relaxed/simple; bh=jP4Ha6vJ/71KcVa9BMN+pVZYk79RQ+5CkHKAGKsXY3I=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=aHTyysAtRuhZAuDHq80TQHz2Ys/RLt8atzHTb86Igt9+nIDX9e0Sm/S8PPLRMR8o82D7pMhlW+QV4UXT+d22fDz3CmX4q9SlPr15eL4R1fBDtzu7jmryT7FEBXY0EJh1QuS39jzhaW/v9uC+2i7gHdg8eVNipsyZvhrl5BoFacs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=segrUcCI; arc=none smtp.client-ip=74.125.82.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="segrUcCI" Received: by mail-dy1-f201.google.com with SMTP id 5a478bee46e88-304ec73b015so4628689eec.1 for ; Sun, 07 Jun 2026 14:37:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1780868236; x=1781473036; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=sZ1fQSTvG+XCFMvrevA412MGxq2C52tKuHYl+uFxsRs=; b=segrUcCIPIBz5DFmJ6DMJ+N6fRI5/Ss8uY8UHKRnDxThmSulzSJYHdhfL9rCizdQk/ YaHnm67QUokwOnRCv59QXufJD0wlDiYi4BiEMToSuTxENYJYhkcCvXR7HXhkf23wEr1U Sa2D9B3UUi05L+Y1GWdyrjBIP0ibbtPZEr5Yr3I+yh0FFujiuc9ZE+9RvIwch5ToUBci dLf+6PTgTxbbjHUKnpa7jM4Q7SeBhJWN76GUk4l/cUa80e9+N4aXeYeEOhD0aIl0Zrji 1O48zKAQUXFCcwSi2T1V4SMnGg8JMLNKaEaJvEh3LOI3giWUC+xCrqNmN5BfkzYLCGtT hofg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780868236; x=1781473036; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=sZ1fQSTvG+XCFMvrevA412MGxq2C52tKuHYl+uFxsRs=; b=IKWpcIYaPEks4Qs6HoroqVkuMCdJF8Do4l1klOTUye+iPOsjzBsDUlq1cfkx9cfi0B mf37M9bmevFcsf+g0mlvO/D5pzSip88snMbAp52aEk0Ek5QgGyuokfLFjCoM/UqtsJ2X bTkP95RwWIGAhcMgj2WS3pB7nw/J+q9I5pt6rpukahERkd07SEivkim/DFWqDdxjSDll hZopYcDlIz3iRy05LBe2PvWzJeqA7VsGD7r1iF2+9dEuJG65JlPVJCos5grLPtBHOOzS lU57aMhxWUgryD80tfBsXgZUqtfiIzDDn2w77Mw6wO3Qn66cVpiFEmDIzaMyyXf9Ldjn 7cZg== X-Forwarded-Encrypted: i=1; AFNElJ+piw+NWOspDTu3/bb1P5Ca8HSzOHhaFvjSJIM0mEQmBdtbIxPA8qldn8uSqEEM8V9Lx+q/8J4fdafN4UI=@vger.kernel.org X-Gm-Message-State: AOJu0Yz/lzUcbY79qvUHsCU2LWAP5Dv4AeIlbTYKPcacLETZuB2rP6kA tABZVPjRv81u8ZJJHKEAH8IoH7KT7Eep0XOA87RxhjIlqkmewCgeCBNcJKvoj1ZM3+XFxMs6LaU 9VFpUwyhnbQ== X-Received: from dyvp3.prod.google.com ([2002:a05:693c:8843:b0:2f7:767f:edc0]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7301:1e94:b0:307:26a3:75d8 with SMTP id 5a478bee46e88-3077b270c7emr7648330eec.1.1780868235892; Sun, 07 Jun 2026 14:37:15 -0700 (PDT) Date: Sun, 7 Jun 2026 14:36:58 -0700 In-Reply-To: <20260607213700.3563842-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260607060933.3274263-1-irogers@google.com> <20260607213700.3563842-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.1032.g2f8565e1d1-goog Message-ID: <20260607213700.3563842-4-irogers@google.com> Subject: [PATCH v18 3/5] perf inject/aslr: Implement sample address remapping From: Ian Rogers To: irogers@google.com, acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, gmx@google.com, james.clark@linaro.org, jolsa@kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the sample address remapping logic to the ASLR tool. This patch implements aslr_tool__process_sample, which parses sample events, remaps IPs, ADDRs, callchains, and branch stacks using the mappings collected from metadata events, and drops potentially leaking raw, register, stack, physical address, and aux samples. Also adds the aslr_tool__remap_address helper function. Co-developed-by: Gabriel Marin Signed-off-by: Gabriel Marin Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.1-pro --- tools/perf/util/aslr.c | 463 +++++++++++++++++++++++++++++++++++++++- tools/perf/util/evsel.c | 6 +- tools/perf/util/evsel.h | 10 +- 3 files changed, 470 insertions(+), 9 deletions(-) diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c index e45f68c60493..03944677ddfc 100644 --- a/tools/perf/util/aslr.c +++ b/tools/perf/util/aslr.c @@ -20,6 +20,7 @@ #include #include #include +#include =20 /** * struct remap_addresses_key - Key for mapping original addresses to rema= pped ones. @@ -112,6 +113,60 @@ static u64 round_up_to_page_size(u64 addr) return (addr + page_size - 1) & ~((u64)page_size - 1); } =20 +static u64 aslr_tool__remap_address(struct aslr_tool *aslr, + struct thread *aslr_thread, + u8 cpumode, + u64 addr) +{ + struct addr_location al; + struct remap_addresses_key key; + u64 *remapped_invariant_ptr =3D NULL; + u64 remap_addr =3D 0; + u8 effective_cpumode =3D 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 spa= ce + * to be robust against bad cpumode in samples. + */ + if (cpumode =3D=3D PERF_RECORD_MISC_KERNEL) + effective_cpumode =3D PERF_RECORD_MISC_USER; + else if (cpumode =3D=3D PERF_RECORD_MISC_USER) + effective_cpumode =3D PERF_RECORD_MISC_KERNEL; + else if (cpumode =3D=3D PERF_RECORD_MISC_GUEST_KERNEL) + effective_cpumode =3D PERF_RECORD_MISC_GUEST_USER; + else if (cpumode =3D=3D PERF_RECORD_MISC_GUEST_USER) + effective_cpumode =3D PERF_RECORD_MISC_GUEST_KERNEL; + + if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) { + addr_location__exit(&al); + return 0; /* No mmap. */ + } + } + + key.machine =3D maps__machine(thread__maps(aslr_thread)); + key.dso =3D map__dso(al.map); + key.invariant =3D map__start(al.map) - map__pgoff(al.map); + key.pid =3D (effective_cpumode =3D=3D PERF_RECORD_MISC_KERNEL || + effective_cpumode =3D=3D PERF_RECORD_MISC_GUEST_KERNEL) ? + kernel_pid : thread__pid(aslr_thread); + + if (hashmap__find(&aslr->remap_addresses, &key, &remapped_invariant_ptr))= { + remap_addr =3D *remapped_invariant_ptr + map__pgoff(al.map) + + (addr - map__start(al.map)); + } else { + pr_debug("Cannot find a remapped entry for address %lx in mapping %lx(%l= x) for pid=3D%d\n", + addr, map__start(al.map), map__size(al.map), key.pid); + } + + addr_location__exit(&al); + return remap_addr; +} + struct aslr_machine_priv { bool kernel_maps_loaded; }; @@ -602,13 +657,413 @@ static int aslr_tool__process_sample(const struct pe= rf_tool *tool, struct perf_sample *sample, struct machine *machine) { - struct delegate_tool *del_tool =3D container_of(tool, struct delegate_too= l, tool); - struct aslr_tool *aslr =3D container_of(del_tool, struct aslr_tool, tool); - struct perf_tool *delegate =3D aslr->tool.delegate; + struct evsel *evsel =3D sample->evsel; + struct delegate_tool *del_tool; + struct aslr_tool *aslr; + struct perf_tool *delegate; + int ret; + u64 sample_type; + struct thread *thread; + struct machine *aslr_machine; + __u64 max_i; + __u64 max_j; + union perf_event *new_event; + struct perf_sample new_sample; + __u64 *in_array, *out_array; + u8 cpumode; + u64 addr; + size_t i; + size_t j; + bool orig_needs_swap; + + del_tool =3D container_of(tool, struct delegate_tool, tool); + aslr =3D container_of(del_tool, struct aslr_tool, tool); + delegate =3D aslr->tool.delegate; + + orig_needs_swap =3D evsel->needs_swap; + + if (evsel__is_dummy_event(evsel)) + return delegate->sample(delegate, event, sample, machine); + + ret =3D -EFAULT; + sample_type =3D evsel->core.attr.sample_type; + max_i =3D (event->header.size - sizeof(struct perf_event_header)) / sizeo= f(__u64); + max_j =3D (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / siz= eof(__u64); + new_event =3D (union perf_event *)aslr->event_copy; + cpumode =3D sample->cpumode; + i =3D 0; + j =3D 0; + + aslr_machine =3D machines__findnew(&aslr->machines, machine->pid); + if (!aslr_machine) + return -ENOMEM; + if (aslr_tool__preload_kernel_maps(aslr_machine) < 0) + return -ENOMEM; + + thread =3D machine__findnew_thread(aslr_machine, sample->pid, sample->tid= ); + + if (!thread) + return -ENOMEM; + + if (max_i > PERF_SAMPLE_MAX_SIZE / sizeof(u64)) + goto out_put; + + new_event->sample.header =3D event->sample.header; + + in_array =3D &event->sample.array[0]; + out_array =3D &new_event->sample.array[0]; + +#define CHECK_BOUNDS(required_i, required_j) \ + (i + (required_i) > max_i || j + (required_j) > max_j) + +#define COPY_U64() \ + do { \ + if (CHECK_BOUNDS(1, 1)) { \ + ret =3D -EFAULT; \ + goto out_put; \ + } \ + out_array[j++] =3D in_array[i++]; \ + } while (0) + +#define REMAP_U64(addr_field) \ + do { \ + u64 remapped; \ + if (CHECK_BOUNDS(1, 1)) { \ + ret =3D -EFAULT; \ + goto out_put; \ + } \ + remapped =3D aslr_tool__remap_address(aslr, thread, cpumode, addr_field)= ; \ + if (orig_needs_swap) \ + remapped =3D bswap_64(remapped); \ + out_array[j++] =3D remapped; \ + i++; \ + } while (0) + + if (sample_type & PERF_SAMPLE_IDENTIFIER) + COPY_U64(); /* id */ + if (sample_type & PERF_SAMPLE_IP) + REMAP_U64(sample->ip); + if (sample_type & PERF_SAMPLE_TID) + COPY_U64(); /* pid, tid */ + if (sample_type & PERF_SAMPLE_TIME) + COPY_U64(); /* time */ + if (sample_type & PERF_SAMPLE_ADDR) + REMAP_U64(sample->addr); + if (sample_type & PERF_SAMPLE_ID) + COPY_U64(); /* id */ + if (sample_type & PERF_SAMPLE_STREAM_ID) + COPY_U64(); /* stream_id */ + if (sample_type & PERF_SAMPLE_CPU) + COPY_U64(); /* cpu, res */ + if (sample_type & PERF_SAMPLE_PERIOD) + COPY_U64(); /* period */ + if (sample_type & PERF_SAMPLE_READ) { + if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) =3D=3D 0) { + COPY_U64(); /* value */ + if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) + COPY_U64(); /* time_enabled */ + if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) + COPY_U64(); /* time_running */ + if (evsel->core.attr.read_format & PERF_FORMAT_ID) + COPY_U64(); /* id */ + if (evsel->core.attr.read_format & PERF_FORMAT_LOST) + COPY_U64(); /* lost */ + } else { + u64 nr; + + if (CHECK_BOUNDS(1, 1)) { + ret =3D -EFAULT; + goto out_put; + } + nr =3D in_array[i]; + COPY_U64(); + if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) + COPY_U64(); /* time_enabled */ + if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) + COPY_U64(); /* time_running */ + for (u64 cntr =3D 0; cntr < nr; cntr++) { + COPY_U64(); /* value */ + if (evsel->core.attr.read_format & PERF_FORMAT_ID) + COPY_U64(); /* id */ + if (evsel->core.attr.read_format & PERF_FORMAT_LOST) + COPY_U64(); /* lost */ + } + } + } + if (sample_type & PERF_SAMPLE_CALLCHAIN) { + u64 nr; + + if (CHECK_BOUNDS(1, 1)) { + ret =3D -EFAULT; + goto out_put; + } + nr =3D in_array[i]; + COPY_U64(); =20 - return delegate->sample(delegate, event, sample, machine); + for (u64 cntr =3D 0; cntr < nr; cntr++) { + if (CHECK_BOUNDS(1, 1)) { + ret =3D -EFAULT; + goto out_put; + } + addr =3D in_array[i++]; + if (addr >=3D PERF_CONTEXT_MAX) { + out_array[j++] =3D orig_needs_swap ? bswap_64(addr) : addr; + switch (addr) { + case PERF_CONTEXT_HV: + cpumode =3D PERF_RECORD_MISC_HYPERVISOR; + break; + case PERF_CONTEXT_KERNEL: + cpumode =3D PERF_RECORD_MISC_KERNEL; + break; + case PERF_CONTEXT_USER: + cpumode =3D PERF_RECORD_MISC_USER; + break; + case PERF_CONTEXT_GUEST: + cpumode =3D PERF_RECORD_MISC_GUEST_KERNEL; + break; + case PERF_CONTEXT_GUEST_KERNEL: + cpumode =3D PERF_RECORD_MISC_GUEST_KERNEL; + break; + case PERF_CONTEXT_GUEST_USER: + cpumode =3D PERF_RECORD_MISC_GUEST_USER; + break; + case PERF_CONTEXT_USER_DEFERRED: + if (cntr + 1 >=3D nr) { + pr_debug("Truncated callchain deferred cookie context\n"); + ret =3D 0; + goto out_put; + } + /* + * Immediately followed by a 64-bit + * stitching cookie. Skip/Copy it! + */ + if (CHECK_BOUNDS(1, 1)) { + ret =3D -EFAULT; + goto out_put; + } + out_array[j++] =3D in_array[i++]; + cntr++; + cpumode =3D PERF_RECORD_MISC_USER; + break; + default: + pr_debug("invalid callchain context: %"PRIx64"\n", addr); + ret =3D 0; + goto out_put; + } + continue; + } + addr =3D aslr_tool__remap_address(aslr, thread, cpumode, addr); + if (orig_needs_swap) + addr =3D bswap_64(addr); + out_array[j++] =3D addr; + } + } + if (sample_type & PERF_SAMPLE_RAW) { + size_t bytes =3D sizeof(u32) + sample->raw_size; + size_t u64_words =3D (bytes + 7) / 8; + + if (i + u64_words > max_i || j + u64_words > max_j) { + ret =3D -EFAULT; + goto out_put; + } + memcpy(&out_array[j], &in_array[i], bytes); + i +=3D u64_words; + j +=3D u64_words; + /* + * TODO: certain raw samples can be remapped, such as + * tracepoints by examining their fields. + */ + pr_debug("Dropping raw samples as possible ASLR leak\n"); + ret =3D 0; + goto out_put; + } + if (sample_type & PERF_SAMPLE_BRANCH_STACK) { + u64 nr; + + if (CHECK_BOUNDS(1, 1)) { + ret =3D -EFAULT; + goto out_put; + } + nr =3D in_array[i]; + COPY_U64(); + + if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_HW_INDEX) + COPY_U64(); /* hw_idx */ + + if (nr > (ULLONG_MAX / 3)) { + ret =3D -EFAULT; + goto out_put; + } + if (nr * 3 > max_i - i || nr * 3 > max_j - j) { + ret =3D -EFAULT; + goto out_put; + } + for (u64 cntr =3D 0; cntr < nr; cntr++) { + u64 from =3D in_array[i++]; + u64 to =3D in_array[i++]; + + if (orig_needs_swap) { + from =3D bswap_64(from); + to =3D bswap_64(to); + } + + from =3D aslr_tool__remap_address(aslr, thread, sample->cpumode, from); + to =3D aslr_tool__remap_address(aslr, thread, sample->cpumode, to); + + if (orig_needs_swap) { + from =3D bswap_64(from); + to =3D bswap_64(to); + } + + out_array[j++] =3D from; + out_array[j++] =3D to; + out_array[j++] =3D in_array[i++]; /* flags */ + } + if (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS) { + if (nr > max_i - i || nr > max_j - j) { + ret =3D -EFAULT; + goto out_put; + } + for (u64 cntr =3D 0; cntr < nr; cntr++) + COPY_U64(); + } + } + if (sample_type & PERF_SAMPLE_REGS_USER) { + if (CHECK_BOUNDS(1, 0)) { + ret =3D -EFAULT; + goto out_put; + } + /* abi */ + COPY_U64(); + /* TODO: can this be less conservative? */ + pr_debug("Dropping regs user sample as possible ASLR leak\n"); + ret =3D 0; + goto out_put; + } + if (sample_type & PERF_SAMPLE_STACK_USER) { + u64 size; + + if (CHECK_BOUNDS(1, 1)) { + ret =3D -EFAULT; + goto out_put; + } + size =3D in_array[i]; + COPY_U64(); + if (size > 0) { + size_t u64_words =3D size / 8 + (size % 8 ? 1 : 0); + + if (u64_words > max_i - i || u64_words > max_j - j) { + ret =3D -EFAULT; + goto out_put; + } + memcpy(&out_array[j], &in_array[i], size); + if (size % 8) { + size_t pad =3D 8 - (size % 8); + + memset(((char *)&out_array[j]) + size, 0, pad); + } + i +=3D u64_words; + j +=3D u64_words; + } + /* TODO: can this be less conservative? */ + pr_debug("Dropping stack user sample as possible ASLR leak\n"); + ret =3D 0; + goto out_put; + } + if (sample_type & PERF_SAMPLE_WEIGHT_TYPE) + COPY_U64(); /* perf_sample_weight */ + if (sample_type & PERF_SAMPLE_DATA_SRC) + COPY_U64(); /* data_src */ + if (sample_type & PERF_SAMPLE_TRANSACTION) + COPY_U64(); /* transaction */ + if (sample_type & PERF_SAMPLE_REGS_INTR) { + if (CHECK_BOUNDS(1, 0)) { + ret =3D -EFAULT; + goto out_put; + } + /* abi */ + COPY_U64(); + /* TODO: can this be less conservative? */ + pr_debug("Dropping interrupt register sample as possible ASLR leak\n"); + ret =3D 0; + goto out_put; + } + if (sample_type & PERF_SAMPLE_PHYS_ADDR) { + COPY_U64(); /* phys_addr */ + /* TODO: can this be less conservative? */ + pr_debug("Dropping physical address sample as possible ASLR leak\n"); + ret =3D 0; + goto out_put; + } + if (sample_type & PERF_SAMPLE_CGROUP) + COPY_U64(); /* cgroup */ + if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE) + COPY_U64(); /* data_page_size */ + if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE) + COPY_U64(); /* code_page_size */ + + if (sample_type & PERF_SAMPLE_AUX) { + u64 size; + + if (CHECK_BOUNDS(1, 1)) { + ret =3D -EFAULT; + goto out_put; + } + out_array[j] =3D in_array[i]; + size =3D out_array[j++]; + i++; + if (size > 0) { + size_t u64_words =3D size / 8 + (size % 8 ? 1 : 0); + + if (u64_words > max_i - i || u64_words > max_j - j) { + ret =3D -EFAULT; + goto out_put; + } + memcpy(&out_array[j], &in_array[i], size); + if (size % 8) { + size_t pad =3D 8 - (size % 8); + + memset(((char *)&out_array[j]) + size, 0, pad); + } + i +=3D u64_words; + j +=3D u64_words; + } + /* TODO: can this be less conservative? */ + pr_debug("Dropping aux sample as possible ASLR leak\n"); + ret =3D 0; + goto out_put; + } + + if (evsel__is_offcpu_event(evsel)) { + /* TODO: can this be less conservative? */ + pr_debug("Dropping off-CPU sample as possible ASLR leak\n"); + ret =3D 0; + goto out_put; + } + + new_event->sample.header.size =3D sizeof(struct perf_event_header) + j * = sizeof(u64); + + perf_sample__init(&new_sample, /*all=3D*/ true); + ret =3D __evsel__parse_sample(evsel, new_event, &new_sample, orig_needs_s= wap); + + if (ret) { + perf_sample__exit(&new_sample); + goto out_put; + } + + new_sample.evsel =3D evsel; + ret =3D delegate->sample(delegate, new_event, &new_sample, machine); + perf_sample__exit(&new_sample); + +out_put: + thread__put(thread); + return ret; } =20 +#undef CHECK_BOUNDS +#undef COPY_U64 +#undef REMAP_U64 + static int skipn(int fd, off_t n) { char buf[4096]; diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 34c03f47a913..05fa0010c858 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -3337,11 +3337,11 @@ static int __set_offcpu_sample(struct perf_sample *= data) return -EFAULT; } =20 -int evsel__parse_sample(struct evsel *evsel, union perf_event *event, - struct perf_sample *data) +int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, + struct perf_sample *data, bool needs_swap) { u64 type =3D evsel->core.attr.sample_type; - bool swapped =3D evsel->needs_swap; + bool swapped =3D needs_swap; const __u64 *array; u16 max_size =3D event->header.size; const void *endp =3D (void *)event + max_size; diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 8178858d168a..8009be22cc3f 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -432,8 +432,14 @@ static inline int evsel__read_on_cpu_scaled(struct evs= el *evsel, int cpu_map_idx return __evsel__read_on_cpu(evsel, cpu_map_idx, thread, true); } =20 -int evsel__parse_sample(struct evsel *evsel, union perf_event *event, - struct perf_sample *sample); +int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, + struct perf_sample *data, bool needs_swap); + +static inline int evsel__parse_sample(struct evsel *evsel, union perf_even= t *event, + struct perf_sample *data) +{ + return __evsel__parse_sample(evsel, event, data, evsel->needs_swap); +} =20 int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *e= vent, u64 *timestamp); --=20 2.54.0.1032.g2f8565e1d1-goog From nobody Mon Jun 8 04:24:55 2026 Received: from mail-dy1-f202.google.com (mail-dy1-f202.google.com [74.125.82.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BA46E64A8C for ; Sun, 7 Jun 2026 21:37:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868253; cv=none; b=g19OTOY9c8r9/mdeRI8Tq7Zt0E6wVwlMcm3WjkkA0g+IEOvrV98yZd3iiVyIrtld9xKaxBeDwpc1d545uN43Nz0S8RDZ9IdMzm9X5uUkW/RmcmzyKU3Qw8bLm57HpxcQ5N+cAK6RXrDRvbxyAYEmrArlDH7LYRK5wlSNHPBLgOs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868253; c=relaxed/simple; bh=nCtVIy3w8UK2KOB51RCLqWCLVO8a+Wyzy+f1nyKWTys=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=soFwLGiI6PygvX3T/z776zUggsldFbh73p/5j1onSjucR0fwPiaydAW0SSlXeWUeJeb4tQQj2U8ZMSRu/GJm/umT0YM7zI9rLnI2RNNrT7FnpmBXPAtUL7+ZkSxfIa+6FHm4c08YKJV9Oj4FrUYvFf6Q+Dv1B+D7k/lmwrARXSE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=XFR+tHQm; arc=none smtp.client-ip=74.125.82.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="XFR+tHQm" Received: by mail-dy1-f202.google.com with SMTP id 5a478bee46e88-304d8613efbso3538739eec.1 for ; Sun, 07 Jun 2026 14:37:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1780868251; x=1781473051; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=+o3mzwUeCFTCna4Gp2BkBjHvfNfPvhZavjjqHZIRfcY=; b=XFR+tHQmk9sbaqlo8r49JKqBSDLIUmJjU5i7TZHbvvC3nXlXwLXDfEKzMTojqwix4E WeSQqP3dVs5WwLO/cUN67F8S+NxCGEa+RfoJCm/bkhNGjKuF9fn+lZqFgImQEFe6CIXp s3gHfIGzXLv9Q0E5N1H+LDyFHD7tzltKAjVe8p00EVMQN0hV+sHknCX0bZDYMzwcwxB5 dpTqs6IY4YmNhUp9Orqtg8O4dpr4m3jD1qVUewICCszvVZwUbCjQbyqGOY6Byox/oLxu Nb0VYeaMz8wvdu+xr8OLeqg2hl6Mw7bllC3L2D1+VCr1etB+DJbNAbSHnxtZ9VFh/6so qccg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780868251; x=1781473051; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=+o3mzwUeCFTCna4Gp2BkBjHvfNfPvhZavjjqHZIRfcY=; b=fkLtMPD90vv+zKGs3Xebi6UjQ7AQlg1xJMyd+RvOANydE/vhoeHHKGcXMiEWZFPH/I qtoNujUE0/qYzR8BwihBjXb/ZJP2+7LEc6A8hiS50Hk6S+YyapAtmw1asI1nL37ULuBV ynD9ZFfIKgbmPRQVS6MjUnEQx1k3SrHL/kCQPqph2tyGAD0d3KjInx0EsuoZZ+2MwcZq Pi215cN0lxrpkvYkj5J6i+hMvdPfaokLTJf0Y6dMcQT5tthLx7zH0+koiAknbiqv25K3 octWnQHXX/V1YoYaokXthg5J4cym6M7zBTHgEDiB4YzHbySXlPANpC+JyWTaPL94Y0oU Iu6g== X-Forwarded-Encrypted: i=1; AFNElJ/yZbhlXn1ZJi0IbdrtK1I1SbwHClwWe+JNDL4V1oahZzlu/5KDgJcEpjJNG6MDMbUJFJckTd0IksTV4hY=@vger.kernel.org X-Gm-Message-State: AOJu0YwZyRu4qrYXfkDp2MMrrA1/E8kM4crNOVRUwMeb77JJgVbEDCXb SDrz6R29RnkfGS4zymRLwSmZAu0bGZh+xdSP9VHu90q8ra3RQzLkTLNL1OlPqaWTP+yKil0UtTZ xIAvCPb73Rw== X-Received: from dlbur17.prod.google.com ([2002:a05:7022:ea51:b0:135:d76a:aec7]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:238d:b0:135:578a:cde2 with SMTP id a92af1059eb24-13806733a3amr7052520c88.34.1780868250671; Sun, 07 Jun 2026 14:37:30 -0700 (PDT) Date: Sun, 7 Jun 2026 14:36:59 -0700 In-Reply-To: <20260607213700.3563842-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260607060933.3274263-1-irogers@google.com> <20260607213700.3563842-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.1032.g2f8565e1d1-goog Message-ID: <20260607213700.3563842-5-irogers@google.com> Subject: [PATCH v18 4/5] perf aslr: Strip sample registers From: Ian Rogers To: irogers@google.com, acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, gmx@google.com, james.clark@linaro.org, jolsa@kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Extend the ASLR tool stripping helpers to drop register dump payloads by masking out the relevant perf_event_attr fields (sample_regs_user, sample_regs_intr) when the delegated tool is handling the data. struct aslr_evsel_priv maintains the original perf_event_attr values and is looked up via the evsel_orig_attrs hashmap so that sample sizes can be properly parsed even when bits are stripped from the pipeline. This allows us to keep samples that would otherwise be dropped because they contain registers, while still obfuscating the registers. Co-developed-by: Gabriel Marin Signed-off-by: Gabriel Marin Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.1-pro --- tools/perf/builtin-inject.c | 28 +++- tools/perf/util/aslr.c | 292 +++++++++++++++++++++++++----------- tools/perf/util/aslr.h | 9 +- 3 files changed, 236 insertions(+), 93 deletions(-) diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 8bb37095e2de..6d6cce4765a7 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -248,7 +248,7 @@ static int perf_event__repipe_attr(const struct perf_to= ol *tool, if (!aslr_event) return -ENOMEM; memcpy(aslr_event, event, event->header.size); - aslr_tool__strip_attr_event(aslr_event, pevlist); + aslr_tool__strip_attr_event(aslr_event, *pevlist); event =3D aslr_event; } =20 @@ -297,6 +297,7 @@ static int perf_event__repipe_attr(const struct perf_to= ol *tool, attr.size =3D sizeof(struct perf_event_attr); attr.sample_type &=3D ~PERF_SAMPLE_AUX; =20 + if (inject->itrace_synth_opts.add_last_branch) { attr.sample_type |=3D PERF_SAMPLE_BRANCH_STACK; attr.branch_sample_type |=3D PERF_SAMPLE_BRANCH_HW_INDEX; @@ -2617,6 +2618,10 @@ static int __cmd_inject(struct perf_inject *inject) evsel->core.attr.exclude_callchain_user =3D 0; } } + + if (inject->aslr) + aslr_tool__strip_evlist(inject->session->tool, session->evlist); + session->header.data_offset =3D output_data_offset; session->header.data_size =3D inject->bytes_written; perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc, @@ -2875,6 +2880,18 @@ int cmd_inject(int argc, const char **argv) if (zstd_init(&(inject.session->zstd_data), 0) < 0) pr_warning("Decompression initialization failed.\n"); =20 + if (inject.aslr) { + struct evsel *evsel; + + evlist__for_each_entry(inject.session->evlist, evsel) { + ret =3D aslr_tool__cache_orig_attrs(tool, evsel); + if (ret) { + pr_err("Failed to cache original attributes: %d\n", ret); + goto out_delete; + } + } + } + /* Save original section info before feature bits change */ ret =3D save_section_info(&inject); if (ret) @@ -2893,10 +2910,17 @@ int cmd_inject(int argc, const char **argv) * the input. */ if (!data.is_pipe) { + if (inject.aslr) + aslr_tool__strip_evlist(tool, inject.session->evlist); + ret =3D perf_event__synthesize_for_pipe(&inject.tool, inject.session, &inject.output, perf_event__repipe); + + if (inject.aslr) + aslr_tool__restore_evlist(tool, inject.session->evlist); + if (ret < 0) goto out_delete; } @@ -2961,8 +2985,6 @@ int cmd_inject(int argc, const char **argv) goto out_delete; =20 ret =3D __cmd_inject(&inject); - if (inject.aslr) - aslr_tool__strip_evlist(tool, inject.session->evlist); =20 guest_session__exit(&inject.guest_session); =20 diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c index 03944677ddfc..912efd111bb3 100644 --- a/tools/perf/util/aslr.c +++ b/tools/perf/util/aslr.c @@ -18,6 +18,7 @@ #include /* page_size */ #include #include +#include #include #include #include @@ -46,6 +47,23 @@ struct aslr_mapping { u64 remap_start; }; =20 +struct aslr_evsel_priv { + u64 orig_sample_type; + u64 orig_sample_regs_user; + u64 orig_sample_regs_intr; + int orig_sample_size; +}; + +static size_t evsel_hash(long key, void *ctx __maybe_unused) +{ + return (size_t)key; +} + +static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused) +{ + return key1 =3D=3D key2; +} + struct process_top_address { u64 remapped_max; }; @@ -60,6 +78,11 @@ struct aslr_tool { struct hashmap remap_addresses; /** @top_addresses: mapping from process to max remapped address. */ struct hashmap top_addresses; + /** + * @evsel_orig_attrs: mapping from evsel pointer to its original + * unstripped sample_type and registers bitmasks. + */ + struct hashmap evsel_orig_attrs; }; =20 static const pid_t kernel_pid =3D -1; @@ -123,6 +146,8 @@ static u64 aslr_tool__remap_address(struct aslr_tool *a= slr, u64 *remapped_invariant_ptr =3D NULL; u64 remap_addr =3D 0; u8 effective_cpumode =3D cpumode; + struct dso *dso; + const char *dso_name; =20 if (!aslr_thread) return 0; /* No thread. */ @@ -148,9 +173,15 @@ static u64 aslr_tool__remap_address(struct aslr_tool *= aslr, } } =20 + dso =3D map__dso(al.map); + dso_name =3D dso ? dso__long_name(dso) : NULL; + key.machine =3D maps__machine(thread__maps(aslr_thread)); - key.dso =3D map__dso(al.map); - key.invariant =3D map__start(al.map) - map__pgoff(al.map); + key.dso =3D dso; + if (dso && !is_anon_memory(dso_name) && !is_no_dso_memory(dso_name)) + key.invariant =3D map__start(al.map) - map__pgoff(al.map); + else + key.invariant =3D map__start(al.map); key.pid =3D (effective_cpumode =3D=3D PERF_RECORD_MISC_KERNEL || effective_cpumode =3D=3D PERF_RECORD_MISC_GUEST_KERNEL) ? kernel_pid : thread__pid(aslr_thread); @@ -662,6 +693,7 @@ static int aslr_tool__process_sample(const struct perf_= tool *tool, struct aslr_tool *aslr; struct perf_tool *delegate; int ret; + int orig_sample_size; u64 sample_type; struct thread *thread; struct machine *aslr_machine; @@ -674,6 +706,10 @@ static int aslr_tool__process_sample(const struct perf= _tool *tool, u64 addr; size_t i; size_t j; + struct aslr_evsel_priv *priv =3D NULL; + u64 orig_sample_type; + u64 orig_regs_user; + u64 orig_regs_intr; bool orig_needs_swap; =20 del_tool =3D container_of(tool, struct delegate_tool, tool); @@ -686,7 +722,24 @@ static int aslr_tool__process_sample(const struct perf= _tool *tool, return delegate->sample(delegate, event, sample, machine); =20 ret =3D -EFAULT; - sample_type =3D evsel->core.attr.sample_type; + + if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) { + orig_sample_type =3D priv->orig_sample_type; + orig_regs_user =3D priv->orig_sample_regs_user; + orig_regs_intr =3D priv->orig_sample_regs_intr; + } else { + orig_sample_type =3D evsel->core.attr.sample_type; + orig_regs_user =3D evsel->core.attr.sample_regs_user; + orig_regs_intr =3D evsel->core.attr.sample_regs_intr; + } + + orig_sample_size =3D evsel->sample_size; + + sample_type =3D orig_sample_type; + sample_type &=3D ~PERF_SAMPLE_REGS_USER; + sample_type &=3D ~PERF_SAMPLE_REGS_INTR; + sample_type &=3D ASLR_SUPPORTED_SAMPLE_TYPE; + max_i =3D (event->header.size - sizeof(struct perf_event_header)) / sizeo= f(__u64); max_j =3D (PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_event_header)) / siz= eof(__u64); new_event =3D (union perf_event *)aslr->event_copy; @@ -739,25 +792,25 @@ static int aslr_tool__process_sample(const struct per= f_tool *tool, i++; \ } while (0) =20 - if (sample_type & PERF_SAMPLE_IDENTIFIER) + if (orig_sample_type & PERF_SAMPLE_IDENTIFIER) COPY_U64(); /* id */ - if (sample_type & PERF_SAMPLE_IP) + if (orig_sample_type & PERF_SAMPLE_IP) REMAP_U64(sample->ip); - if (sample_type & PERF_SAMPLE_TID) + if (orig_sample_type & PERF_SAMPLE_TID) COPY_U64(); /* pid, tid */ - if (sample_type & PERF_SAMPLE_TIME) + if (orig_sample_type & PERF_SAMPLE_TIME) COPY_U64(); /* time */ - if (sample_type & PERF_SAMPLE_ADDR) + if (orig_sample_type & PERF_SAMPLE_ADDR) REMAP_U64(sample->addr); - if (sample_type & PERF_SAMPLE_ID) + if (orig_sample_type & PERF_SAMPLE_ID) COPY_U64(); /* id */ - if (sample_type & PERF_SAMPLE_STREAM_ID) + if (orig_sample_type & PERF_SAMPLE_STREAM_ID) COPY_U64(); /* stream_id */ - if (sample_type & PERF_SAMPLE_CPU) + if (orig_sample_type & PERF_SAMPLE_CPU) COPY_U64(); /* cpu, res */ - if (sample_type & PERF_SAMPLE_PERIOD) + if (orig_sample_type & PERF_SAMPLE_PERIOD) COPY_U64(); /* period */ - if (sample_type & PERF_SAMPLE_READ) { + if (orig_sample_type & PERF_SAMPLE_READ) { if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) =3D=3D 0) { COPY_U64(); /* value */ if (evsel->core.attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) @@ -790,7 +843,7 @@ static int aslr_tool__process_sample(const struct perf_= tool *tool, } } } - if (sample_type & PERF_SAMPLE_CALLCHAIN) { + if (orig_sample_type & PERF_SAMPLE_CALLCHAIN) { u64 nr; =20 if (CHECK_BOUNDS(1, 1)) { @@ -858,7 +911,7 @@ static int aslr_tool__process_sample(const struct perf_= tool *tool, out_array[j++] =3D addr; } } - if (sample_type & PERF_SAMPLE_RAW) { + if (orig_sample_type & PERF_SAMPLE_RAW) { size_t bytes =3D sizeof(u32) + sample->raw_size; size_t u64_words =3D (bytes + 7) / 8; =20 @@ -877,7 +930,7 @@ static int aslr_tool__process_sample(const struct perf_= tool *tool, ret =3D 0; goto out_put; } - if (sample_type & PERF_SAMPLE_BRANCH_STACK) { + if (orig_sample_type & PERF_SAMPLE_BRANCH_STACK) { u64 nr; =20 if (CHECK_BOUNDS(1, 1)) { @@ -928,19 +981,25 @@ static int aslr_tool__process_sample(const struct per= f_tool *tool, COPY_U64(); } } - if (sample_type & PERF_SAMPLE_REGS_USER) { + if (orig_sample_type & PERF_SAMPLE_REGS_USER) { + u64 abi; + if (CHECK_BOUNDS(1, 0)) { ret =3D -EFAULT; goto out_put; } - /* abi */ - COPY_U64(); - /* TODO: can this be less conservative? */ - pr_debug("Dropping regs user sample as possible ASLR leak\n"); - ret =3D 0; - goto out_put; + abi =3D in_array[i++]; + if (abi !=3D PERF_SAMPLE_REGS_ABI_NONE) { + u64 nr =3D hweight64(orig_regs_user); + + if (nr > max_i - i) { + ret =3D -EFAULT; + goto out_put; + } + i +=3D nr; + } } - if (sample_type & PERF_SAMPLE_STACK_USER) { + if (orig_sample_type & PERF_SAMPLE_STACK_USER) { u64 size; =20 if (CHECK_BOUNDS(1, 1)) { @@ -970,39 +1029,45 @@ static int aslr_tool__process_sample(const struct pe= rf_tool *tool, ret =3D 0; goto out_put; } - if (sample_type & PERF_SAMPLE_WEIGHT_TYPE) + if (orig_sample_type & PERF_SAMPLE_WEIGHT_TYPE) COPY_U64(); /* perf_sample_weight */ - if (sample_type & PERF_SAMPLE_DATA_SRC) + if (orig_sample_type & PERF_SAMPLE_DATA_SRC) COPY_U64(); /* data_src */ - if (sample_type & PERF_SAMPLE_TRANSACTION) + if (orig_sample_type & PERF_SAMPLE_TRANSACTION) COPY_U64(); /* transaction */ - if (sample_type & PERF_SAMPLE_REGS_INTR) { + if (orig_sample_type & PERF_SAMPLE_REGS_INTR) { + u64 abi; + if (CHECK_BOUNDS(1, 0)) { ret =3D -EFAULT; goto out_put; } - /* abi */ - COPY_U64(); - /* TODO: can this be less conservative? */ - pr_debug("Dropping interrupt register sample as possible ASLR leak\n"); - ret =3D 0; - goto out_put; + abi =3D in_array[i++]; + if (abi !=3D PERF_SAMPLE_REGS_ABI_NONE) { + u64 nr =3D hweight64(orig_regs_intr); + + if (nr > max_i - i) { + ret =3D -EFAULT; + goto out_put; + } + i +=3D nr; + } } - if (sample_type & PERF_SAMPLE_PHYS_ADDR) { + if (orig_sample_type & PERF_SAMPLE_PHYS_ADDR) { COPY_U64(); /* phys_addr */ /* TODO: can this be less conservative? */ pr_debug("Dropping physical address sample as possible ASLR leak\n"); ret =3D 0; goto out_put; } - if (sample_type & PERF_SAMPLE_CGROUP) + if (orig_sample_type & PERF_SAMPLE_CGROUP) COPY_U64(); /* cgroup */ - if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE) + if (orig_sample_type & PERF_SAMPLE_DATA_PAGE_SIZE) COPY_U64(); /* data_page_size */ - if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE) + if (orig_sample_type & PERF_SAMPLE_CODE_PAGE_SIZE) COPY_U64(); /* code_page_size */ =20 - if (sample_type & PERF_SAMPLE_AUX) { + if (orig_sample_type & PERF_SAMPLE_AUX) { u64 size; =20 if (CHECK_BOUNDS(1, 1)) { @@ -1042,11 +1107,20 @@ static int aslr_tool__process_sample(const struct p= erf_tool *tool, } =20 new_event->sample.header.size =3D sizeof(struct perf_event_header) + j * = sizeof(u64); - + /* Temporarily override evsel attributes to match the stripped new_event = format! */ + evsel->sample_size =3D __evsel__sample_size(sample_type); + evsel->core.attr.sample_type =3D sample_type; + evsel->core.attr.sample_regs_user =3D 0; + evsel->core.attr.sample_regs_intr =3D 0; perf_sample__init(&new_sample, /*all=3D*/ true); ret =3D __evsel__parse_sample(evsel, new_event, &new_sample, orig_needs_s= wap); =20 if (ret) { + /* Restore original attributes immediately if parsing fails */ + evsel->sample_size =3D orig_sample_size; + evsel->core.attr.sample_type =3D orig_sample_type; + evsel->core.attr.sample_regs_user =3D orig_regs_user; + evsel->core.attr.sample_regs_intr =3D orig_regs_intr; perf_sample__exit(&new_sample); goto out_put; } @@ -1055,6 +1129,12 @@ static int aslr_tool__process_sample(const struct pe= rf_tool *tool, ret =3D delegate->sample(delegate, new_event, &new_sample, machine); perf_sample__exit(&new_sample); =20 + /* Restore original attributes so trace ingestion never desynchronizes! */ + evsel->sample_size =3D orig_sample_size; + evsel->core.attr.sample_type =3D orig_sample_type; + evsel->core.attr.sample_regs_user =3D orig_regs_user; + evsel->core.attr.sample_regs_intr =3D orig_regs_intr; + out_put: thread__put(thread); return ret; @@ -1108,43 +1188,30 @@ static int aslr_tool__process_auxtrace_error(const = struct perf_tool *tool __mayb return 0; } =20 - -void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **= pevlist) +void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *e= vlist) { - struct evsel *evsel; - bool needs_swap =3D false; - - if (pevlist && *pevlist) { - evsel =3D evlist__last(*pevlist); - if (evsel) - needs_swap =3D evsel->needs_swap; - } + if (!evlist) + return; =20 if (event->header.size >=3D (offsetof(struct perf_record_header_attr, attr.sample_type) + sizeof(u64))) { - u64 st =3D event->attr.attr.sample_type; - - if (needs_swap) - st =3D bswap_64(st); - - st &=3D ASLR_SUPPORTED_SAMPLE_TYPE; - - if (needs_swap) - st =3D bswap_64(st); - - event->attr.attr.sample_type =3D st; + event->attr.attr.sample_type &=3D ASLR_SUPPORTED_SAMPLE_TYPE; + + if (event->header.size >=3D + (offsetof(struct perf_record_header_attr, attr.sample_regs_user) + s= izeof(u64))) + event->attr.attr.sample_regs_user =3D 0; + if (event->header.size >=3D + (offsetof(struct perf_record_header_attr, attr.sample_regs_intr) + s= izeof(u64))) + event->attr.attr.sample_regs_intr =3D 0; } =20 if (event->header.size >=3D (offsetof(struct perf_record_header_attr, attr.type) + sizeof(u32))) { u32 type =3D event->attr.attr.type; =20 - if (needs_swap) - type =3D bswap_32(type); - if (type =3D=3D PERF_TYPE_BREAKPOINT && event->header.size >=3D (offsetof(struct perf_record_header_attr, - attr.bp_addr) + sizeof(u64))) { + attr.bp_addr) + sizeof(u64))) { event->attr.attr.bp_addr =3D 0; } else if (type >=3D PERF_TYPE_MAX) { struct perf_pmu *pmu; @@ -1165,28 +1232,6 @@ void aslr_tool__strip_attr_event(union perf_event *e= vent, struct evlist **pevlis } } =20 -void aslr_tool__strip_evlist(struct perf_tool *tool __maybe_unused, - struct evlist *evlist) -{ - struct evsel *evsel; - - evlist__for_each_entry(evlist, evsel) { - evsel->core.attr.sample_type &=3D ASLR_SUPPORTED_SAMPLE_TYPE; - - if (evsel->core.attr.type =3D=3D PERF_TYPE_BREAKPOINT) - evsel->core.attr.bp_addr =3D 0; - else if (evsel->core.attr.type >=3D PERF_TYPE_MAX) { - struct perf_pmu *pmu =3D perf_pmus__find_by_type(evsel->core.attr.type); - - if (pmu && (!strcmp(pmu->name, "kprobe") || - !strcmp(pmu->name, "uprobe"))) { - evsel->core.attr.config1 =3D 0; - evsel->core.attr.config2 =3D 0; - } - } - } -} - static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *dele= gate) { delegate_tool__init(&aslr->tool, delegate); @@ -1200,6 +1245,9 @@ static void aslr_tool__init(struct aslr_tool *aslr, s= truct perf_tool *delegate) hashmap__init(&aslr->top_addresses, top_addresses__hash, top_addresses__equal, /*ctx=3D*/NULL); + hashmap__init(&aslr->evsel_orig_attrs, + evsel_hash, evsel_equal, + /*ctx=3D*/NULL); =20 aslr->tool.tool.sample =3D aslr_tool__process_sample; /* read - reads a counter, okay to delegate. */ @@ -1262,9 +1310,13 @@ void aslr_tool__delete(struct perf_tool *tool) zfree(&cur->pkey); zfree(&cur->pvalue); } + hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) { + zfree(&cur->pvalue); + } =20 hashmap__clear(&aslr->remap_addresses); hashmap__clear(&aslr->top_addresses); + hashmap__clear(&aslr->evsel_orig_attrs); aslr_tool__destroy_machines_priv(&aslr->machines); machines__destroy_kernel_maps(&aslr->machines); =20 @@ -1278,3 +1330,69 @@ void aslr_tool__delete(struct perf_tool *tool) machines__exit(&aslr->machines); free(aslr); } + +int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evse= l) +{ + struct delegate_tool *del_tool =3D container_of(tool, struct delegate_too= l, tool); + struct aslr_tool *aslr =3D container_of(del_tool, struct aslr_tool, tool); + struct aslr_evsel_priv *priv =3D zalloc(sizeof(*priv)); + int err; + + if (!priv) + return -ENOMEM; + + priv->orig_sample_type =3D evsel->core.attr.sample_type; + priv->orig_sample_regs_user =3D evsel->core.attr.sample_regs_user; + priv->orig_sample_regs_intr =3D evsel->core.attr.sample_regs_intr; + priv->orig_sample_size =3D evsel->sample_size; + + err =3D hashmap__add(&aslr->evsel_orig_attrs, evsel, priv); + if (err) { + free(priv); + return err; + } + return 0; +} + +void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, = struct evlist *evlist) +{ + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) { + evsel->core.attr.sample_type &=3D ASLR_SUPPORTED_SAMPLE_TYPE; + evsel->core.attr.sample_regs_user =3D 0; + evsel->core.attr.sample_regs_intr =3D 0; + evsel->sample_size =3D __evsel__sample_size(evsel->core.attr.sample_type= ); + evsel__calc_id_pos(evsel); + + if (evsel->core.attr.type =3D=3D PERF_TYPE_BREAKPOINT) { + evsel->core.attr.bp_addr =3D 0; + } else if (evsel->core.attr.type >=3D PERF_TYPE_MAX) { + struct perf_pmu *pmu =3D perf_pmus__find_by_type(evsel->core.attr.type); + + if (pmu && (!strcmp(pmu->name, "kprobe") || + !strcmp(pmu->name, "uprobe"))) { + evsel->core.attr.config1 =3D 0; + evsel->core.attr.config2 =3D 0; + } + } + } +} + +void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist= *evlist) +{ + const struct delegate_tool *del_tool =3D container_of(tool, const struct = delegate_tool, tool); + const struct aslr_tool *aslr =3D container_of(del_tool, const struct aslr= _tool, tool); + struct evsel *evsel; + struct aslr_evsel_priv *priv; + + evlist__for_each_entry(evlist, evsel) { + if (hashmap__find(&aslr->evsel_orig_attrs, evsel, &priv)) { + evsel->core.attr.sample_type =3D priv->orig_sample_type; + evsel->core.attr.sample_regs_user =3D priv->orig_sample_regs_user; + evsel->core.attr.sample_regs_intr =3D priv->orig_sample_regs_intr; + evsel->sample_size =3D priv->orig_sample_size; + evsel__calc_id_pos(evsel); + } + } +} diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h index 2b82f711bc67..522e31c8e2c0 100644 --- a/tools/perf/util/aslr.h +++ b/tools/perf/util/aslr.h @@ -34,8 +34,11 @@ struct evlist; union perf_event; =20 struct perf_tool *aslr_tool__new(struct perf_tool *delegate); -void aslr_tool__delete(struct perf_tool *aslr); -void aslr_tool__strip_attr_event(union perf_event *event, struct evlist **= pevlist); -void aslr_tool__strip_evlist(struct perf_tool *tool, struct evlist *evlist= ); +void aslr_tool__delete(struct perf_tool *tool); + +void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *e= vlist); +int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evse= l); +void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *= evlist); +void aslr_tool__restore_evlist(const struct perf_tool *tool, struct evlist= *evlist); =20 #endif /* __PERF_ASLR_H */ --=20 2.54.0.1032.g2f8565e1d1-goog From nobody Mon Jun 8 04:24:55 2026 Received: from mail-dy1-f201.google.com (mail-dy1-f201.google.com [74.125.82.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5EA0E335081 for ; Sun, 7 Jun 2026 21:37:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868256; cv=none; b=R5yqafK0z6WGRaIxoEWX1pSJFx3Vi+5ymQSTWzRQe/jgY0WRHsKZfE580SA9zdpgTlqOqQ9dllsHUH7Uegxj2f4ynI9hSEBJ9NPAPeCLzmFhXg7GhB1yxaYnTFdipSaxL90Zrj6/SXXeGIH/payg7zGRCLEubpffEf4IhGoF8ts= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780868256; c=relaxed/simple; bh=Wmcm3H/mJCyXGIRZ85OP4srUgmQx4NJKygtKVBmbDiM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=iAdQwYPYGZfECoe/Ffi8+lCod5kq29Hv8mRKgDHtv4vIwblh+AzieAszpYHZDbWoaQs6NxAU90eK0+SS7RT5kq9jyjfp92qa9VIk106ZjQcvYQiXDBikneMvaOJ9wr8R7XqwX7AvHGDEOkQj3UtGFgAHB3zNxVfJsgr3tWKOAv0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=qK5rZN4I; arc=none smtp.client-ip=74.125.82.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="qK5rZN4I" Received: by mail-dy1-f201.google.com with SMTP id 5a478bee46e88-304df51ff3eso3261450eec.0 for ; Sun, 07 Jun 2026 14:37:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1780868253; x=1781473053; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=jTQ4vYwyEJHhDhMRznySM+GJYQn5EoWbwo1uwG9xDKg=; b=qK5rZN4IJfDUiL/VxA2D43kUZyYaDJIvcvdytH+4q+fq3mMQDSpmwrQxz20b5cEKl8 J1bfmdmgK7u0iivqChBwoBpch7INcqIFD7o0+4YOzz+o/nMQdViHaZN7fqeIUjxt9Hm5 fq5NPGbTnSV0rq4pV2xVmlHPvuev/fN1XBMR0I8jqd6tjKSXbzfc8FH7Vfl1Ct364U8H J3hw0sxZl/Gxeb3jm/pBbzKk/Iq/3hedwbATTVvhmnA0Ew5uKqMYa7aj+b9+rq2k2sJ3 MWmJIQzNnNZWGsjnCGlkOenVjII2Iu9HGOXO08imstyL3U/NUMwkEg60DO4yuciv9RKe D40Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780868253; x=1781473053; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=jTQ4vYwyEJHhDhMRznySM+GJYQn5EoWbwo1uwG9xDKg=; b=DxJGW7hsmZdqCe+LyvojVQNHBUj/loR3iAg5E/kQvPvrXJ7FpnlK0UHtT8iMbmMkw2 bPE2p+C/hST/zJjrlhl/OjrgddBG5jFpmyoNlLWwIgz82fO/D02DXV7c0yC8MrlHLLt6 hQSyjaLsPQjsv6nw0HQNI9rts5aFAHdV3EqeyE+XXGwPuXsTXxoz9BoIloG0dDkpFAwe wBDR7i5qv1hkO37xbHUznjREXpa4m4A133tTdyTweW1exPCtWEKuQIlY1eX+xoxUmv48 ar/ANG8FbgGuzswbbl5EmL8dBYFFEvtSn1QJwhvAI9F22lxW3AgCJtvN1t2hAMckjimj hklw== X-Forwarded-Encrypted: i=1; AFNElJ9aB1YwvCAPUWzxfU6pMNaVk/Vo5UuWWHk2FPQGUmDzTb9pVoTwnFOdyIE5r0R+aRz5AilEy/BNT9mn/lU=@vger.kernel.org X-Gm-Message-State: AOJu0YyPZeL1vb55B1D71jPf2u/4tbqUdX2AENBhfhwNbuSHBeEFo+JA HJytrJvI1cj4axfDyGHVorF+eUClsUASOM0INUFleygSf9aHAxwuZEcf9GKy6kom4HIlh4tfuO0 MoN17s+AktA== X-Received: from dyjf13.prod.google.com ([2002:a05:7300:680d:b0:304:e55d:6669]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7300:7fa3:b0:2ed:e12:376d with SMTP id 5a478bee46e88-3077b358a45mr8001496eec.35.1780868253230; Sun, 07 Jun 2026 14:37:33 -0700 (PDT) Date: Sun, 7 Jun 2026 14:37:00 -0700 In-Reply-To: <20260607213700.3563842-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260607060933.3274263-1-irogers@google.com> <20260607213700.3563842-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.1032.g2f8565e1d1-goog Message-ID: <20260607213700.3563842-6-irogers@google.com> Subject: [PATCH v18 5/5] perf test: Add inject ASLR test From: Ian Rogers To: irogers@google.com, acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, gmx@google.com, james.clark@linaro.org, jolsa@kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a new shell test to verify the feature. The test covers: - Basic address remapping for user space samples. - Pipe mode coverage for piped into. - Callchain address remapping. - Consistency of output before and after injection. - Pipe mode report consistency. - Dropping of samples that leak ASLR info (physical addresses). - Kernel address remapping (utilizing a dedicated kernel-intensive VFS dd workload to guarantee continuous timer interrupts sampling flow inside kernel privilege states). - Kernel report consistency with address normalization. The test suite is hardened with global 'set -o pipefail' assertions to catch pipeline failures, stream-consuming awk processors to handle SIGPIPE signals, and a dedicated pipe output scenario validating raw 'perf inject -o -' stdout streams. Note on kernel DSO normalization in the test script: The test script deliberately normalizes all kernel DSOs to a generic [kernel] tag before diffing, as obfuscating physical kernel addresses forces perf report to occasionally shift samples between individual modules and [kernel.kallsyms] due to the lack of valid host module boundary maps. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.1-pro --- tools/perf/tests/shell/inject_aslr.sh | 519 ++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100755 tools/perf/tests/shell/inject_aslr.sh diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell= /inject_aslr.sh new file mode 100755 index 000000000000..2e469f83675e --- /dev/null +++ b/tools/perf/tests/shell/inject_aslr.sh @@ -0,0 +1,519 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# perf inject --aslr test + +set -e +set -o pipefail + +shelldir=3D$(dirname "$0") +# shellcheck source=3Dlib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +sym=3D"noploop" + +skip_test_missing_symbol ${sym} + +# Create global temp directory +temp_dir=3D$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX) + +prog=3D"perf test -w noploop" +[ "$(uname -m)" =3D "s390x" ] && prog=3D"$prog 3" +err=3D0 +kprog=3D"dd if=3D/dev/urandom of=3D/dev/null bs=3D1M count=3D50" + +cleanup() { + local exit_code=3D${1:-$?} + trap - EXIT TERM INT + if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then + echo "Test failed! Preserving temp directory: ${temp_dir}" + return + fi + # Check if temp_dir is set and looks sane before removing + if [[ "${temp_dir}" =3D~ ^/tmp/perf-test-aslr\. ]]; then + rm -rf "${temp_dir}" + fi +} + +trap_cleanup() { + local exit_code=3D$? + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup ${exit_code} + exit ${exit_code} +} +trap trap_cleanup EXIT TERM INT + +get_noploop_addr() { + local file=3D$1 + perf script -i "$file" | awk ' + BEGIN { found=3D0 } + { + for (i=3D1; i<=3DNF; i++) { + if ($i ~ /noploop\+/) { + if (!found) { + print $(i-1) + found=3D1 + } + } + } + }' +} + +test_basic_aslr() { + echo "Test basic ASLR remapping" + local data + data=3D$(mktemp "${temp_dir}/perf.data.basic.XXXXXX") + local data2 + data2=3D$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX") + + perf record -e task-clock:u -o "${data}" ${prog} + perf inject -v --aslr -i "${data}" -o "${data2}" + + orig_addr=3D$(get_noploop_addr "${data}") + new_addr=3D$(get_noploop_addr "${data2}") + + echo "Basic ASLR: orig_addr=3D$orig_addr, new_addr=3D$new_addr" + + if [ -z "$orig_addr" ]; then + echo "Basic ASLR test [Failed - no noploop samples in original file]" + err=3D1 + elif [ -z "$new_addr" ]; then + echo "Basic ASLR test [Failed - could not find remapped address]" + err=3D1 + elif [ "$orig_addr" =3D "$new_addr" ]; then + echo "Basic ASLR test [Failed - addresses are not remapped]" + err=3D1 + else + echo "Basic ASLR test [Success]" + fi +} + +test_pipe_aslr() { + echo "Test pipe mode ASLR remapping" + local data + data=3D$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX") + local data2 + data2=3D$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX") + + # Use tee to save the original pipe data for comparison + perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject -= -aslr -o "${data2}" + + orig_addr=3D$(get_noploop_addr "${data}") + new_addr=3D$(get_noploop_addr "${data2}") + + echo "Pipe ASLR: orig_addr=3D$orig_addr, new_addr=3D$new_addr" + + if [ -z "$orig_addr" ]; then + echo "Pipe ASLR test [Failed - no noploop samples in original file]" + err=3D1 + elif [ -z "$new_addr" ]; then + echo "Pipe ASLR test [Failed - could not find remapped address]" + err=3D1 + elif [ "$orig_addr" =3D "$new_addr" ]; then + echo "Pipe ASLR test [Failed - addresses are not remapped]" + err=3D1 + else + echo "Pipe ASLR test [Success]" + fi +} + +test_callchain_aslr() { + echo "Test Callchain ASLR remapping" + local data + data=3D$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX") + local data2 + data2=3D$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX") + + perf record -g -e task-clock:u -o "${data}" ${prog} + perf inject --aslr -i "${data}" -o "${data2}" + + orig_addr=3D$(get_noploop_addr "${data}") + new_addr=3D$(get_noploop_addr "${data2}") + + echo "Callchain ASLR: orig_addr=3D$orig_addr, new_addr=3D$new_addr" + + if [ -z "$orig_addr" ]; then + echo "Callchain ASLR test [Failed - no noploop samples in original fil= e]" + err=3D1 + elif [ -z "$new_addr" ]; then + echo "Callchain ASLR test [Failed - could not find remapped address]" + err=3D1 + elif [ "$orig_addr" =3D "$new_addr" ]; then + echo "Callchain ASLR test [Failed - addresses are not remapped]" + err=3D1 + else + # Extract callchain addresses (indented lines starting with hex addres= ses) + orig_callchain=3D$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a= -f]+/ {print $1}') + new_callchain=3D$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a= -f]+/ {print $1}') + + if [ -z "$orig_callchain" ]; then + echo "Callchain ASLR test [Failed - no callchain samples in original= file]" + err=3D1 + elif [ -z "$new_callchain" ]; then + echo "Callchain ASLR test [Failed - callchain data was dropped]" + err=3D1 + elif [ "$orig_callchain" =3D "$new_callchain" ]; then + echo "Callchain ASLR test [Failed - callchain addresses were not rem= apped]" + err=3D1 + else + echo "Callchain ASLR test [Success]" + fi + fi +} + +test_report_aslr() { + echo "Test perf report consistency" + local data + data=3D$(mktemp "${temp_dir}/perf.data.report.XXXXXX") + local data2 + data2=3D$(mktemp "${temp_dir}/perf.data2.report.XXXXXX") + local data_clean + data_clean=3D$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") + + perf record -e task-clock:u -o "${data}" ${prog} + # Use -b to inject build-ids and force ordered events processing in both + perf inject -b -i "${data}" -o "${data_clean}" + perf inject -v -b --aslr -i "${data}" -o "${data2}" + + local report1=3D"${temp_dir}/report1_basic" + local report2=3D"${temp_dir}/report2_basic" + local report1_clean=3D"${temp_dir}/report1_basic.clean" + local report2_clean=3D"${temp_dir}/report2_basic.clean" + local diff_file=3D"${temp_dir}/diff_basic" + + perf report -i "${data_clean}" --stdio > "${report1}" + perf report -i "${data2}" --stdio > "${report2}" + + # Strip headers and compare lines with percentages + grep '%' "${report1}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|000000= 0000000000' | sort > "${report1_clean}" || true + grep '%' "${report2}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|000000= 0000000000' | sort > "${report2_clean}" || true + + diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true + + if [ ! -s "${report1_clean}" ]; then + echo "Report ASLR test [Failed - no samples captured]" + err=3D1 + elif [ -s "${diff_file}" ]; then + echo "Report ASLR test [Failed - reports differ]" + echo "Showing first 20 lines of diff:" + head -n 20 "${diff_file}" + err=3D1 + else + echo "Report ASLR test [Success]" + fi +} + +test_pipe_report_aslr() { + echo "Test pipe mode perf report consistency" + local data + data=3D$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX") + local data2 + data2=3D$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX") + local data_clean + data_clean=3D$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") + + # Use tee to save the original pipe data, then process it with inject -b + perf record -e task-clock:u -o - ${prog} | \ + tee "${data}" | \ + perf inject -b --aslr -o "${data2}" + perf inject -b -i "${data}" -o "${data_clean}" + + local report1=3D"${temp_dir}/report1_pipe" + local report2=3D"${temp_dir}/report2_pipe" + local report1_clean=3D"${temp_dir}/report1_pipe.clean" + local report2_clean=3D"${temp_dir}/report2_pipe.clean" + local diff_file=3D"${temp_dir}/diff_pipe" + + perf report -i "${data_clean}" --stdio > "${report1}" + perf report -i "${data2}" --stdio > "${report2}" + + # Strip headers and compare lines with percentages + grep '%' "${report1}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|000000= 0000000000' | sort > "${report1_clean}" || true + grep '%' "${report2}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|000000= 0000000000' | sort > "${report2_clean}" || true + + diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true + + if [ ! -s "${report1_clean}" ]; then + echo "Pipe Report ASLR test [Failed - no samples captured]" + err=3D1 + elif [ -s "${diff_file}" ]; then + echo "Pipe Report ASLR test [Failed - reports differ]" + echo "Showing first 20 lines of diff:" + head -n 20 "${diff_file}" + err=3D1 + else + echo "Pipe Report ASLR test [Success]" + fi +} + +test_pipe_out_report_aslr() { + echo "Test pipe output mode perf report consistency" + local data + data=3D$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX") + local data_clean + data_clean=3D$(mktemp "${temp_dir}/perf.data.clean.XXXXXX") + + perf record -e task-clock:u -o "${data}" ${prog} + perf inject -b -i "${data}" -o "${data_clean}" + + local report1=3D"${temp_dir}/report1_pipe_out" + local report2=3D"${temp_dir}/report2_pipe_out" + local report1_clean=3D"${temp_dir}/report1_pipe_out.clean" + local report2_clean=3D"${temp_dir}/report2_pipe_out.clean" + local diff_file=3D"${temp_dir}/diff_pipe_out" + + perf report -i "${data_clean}" --stdio > "${report1}" + perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${= report2}" + + # Strip headers and compare lines with percentages + grep '%' "${report1}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|000000= 0000000000' | sort > "${report1_clean}" || true + grep '%' "${report2}" | grep -v '^#' | grep -v -E '0x[0-9a-f]{8,}|000000= 0000000000' | sort > "${report2_clean}" || true + + diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true + + if [ ! -s "${report1_clean}" ]; then + echo "Pipe Output Report ASLR test [Failed - no samples captured]" + err=3D1 + elif [ -s "${diff_file}" ]; then + echo "Pipe Output Report ASLR test [Failed - reports differ]" + echo "Showing first 20 lines of diff:" + head -n 20 "${diff_file}" + err=3D1 + else + echo "Pipe Output Report ASLR test [Success]" + fi +} + +test_dropped_samples() { + echo "Test dropped samples (phys-data)" + local data + data=3D$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX") + local data2 + data2=3D$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX") + + # Check if --phys-data is supported by recording a short run + if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 >= /dev/null 2>&1; then + echo "Skipping dropped samples test as --phys-data is not supported" + return + fi + + perf record -e task-clock:u --phys-data -o "${data}" ${prog} + perf inject --aslr -i "${data}" -o "${data2}" + + # Verify that the original file actually contained samples! + orig_samples=3D$(perf script -i "${data}" | wc -l) + if [ "$orig_samples" -eq 0 ]; then + echo "Dropped samples test [Failed - no samples in original file]" + err=3D1 + else + # Verify that samples are dropped. + samples_count=3D$(perf script -i "${data2}" | wc -l) + + if [ "$samples_count" -gt 0 ]; then + echo "Dropped samples test [Failed - samples were not dropped]" + err=3D1 + else + echo "Dropped samples test [Success]" + fi + fi +} + +test_kernel_aslr() { + echo "Test kernel ASLR remapping" + local kdata + kdata=3D$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX") + local kdata2 + kdata2=3D$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX") + local log_file + log_file=3D$(mktemp "${temp_dir}/kernel_record.log.XXXXXX") + + # Try to record kernel samples + if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" = 2>&1; then + echo "Skipping kernel ASLR test as recording failed (maybe no permissi= ons)" + return + fi + + # Check for warning about kernel map restriction + if grep -q "Couldn't record kernel reference relocation symbol" "${log_f= ile}"; then + echo "Skipping kernel ASLR test as kernel map could not be recorded (p= ermissions restricted)" + return + fi + + perf inject -v --aslr -i "${kdata}" -o "${kdata2}" + + # Check if kernel addresses are remapped. + # Find the field that ends with :k: (the event name) and take the next f= ield! + orig_addr=3D$(perf script -i "${kdata}" | awk ' + BEGIN { found=3D0 } + { + for (i=3D1; i "${log_file}" = 2>&1; then + echo "Skipping kernel report test as recording failed (maybe no permis= sions)" + return + fi + + # Check for warning about kernel map restriction + if grep -q "Couldn't record kernel reference relocation symbol" "${log_f= ile}"; then + echo "Skipping kernel report test as kernel map could not be recorded = (permissions restricted)" + return + fi + + # Use -b to inject build-ids and force ordered events processing in both + perf inject -b -i "${kdata}" -o "${data_clean}" + perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}" + + local report1=3D"${temp_dir}/report_kernel1" + local report2=3D"${temp_dir}/report_kernel2" + local report1_clean=3D"${temp_dir}/report_kernel1.clean" + local report2_clean=3D"${temp_dir}/report_kernel2.clean" + + perf report -i "${data_clean}" --stdio > "${report1}" + perf report -i "${kdata2}" --stdio > "${report2}" + + # Strip headers and compare lines with percentages + grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true + grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true + + # Normalize kernel DSOs and addresses in clean reports + # This allows kernel modules to be either a module or kernel.kallsyms + local report1_norm=3D"${temp_dir}/report_kernel1.norm" + local report2_norm=3D"${temp_dir}/report_kernel2.norm" + local diff_file=3D"${temp_dir}/diff_kernel" + + grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \ + awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0)= ; print}' | \ + sort > "${report1_norm}" || true + grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \ + awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0)= ; print}' | \ + sort > "${report2_norm}" || true + + diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true + + if [ ! -s "${report1_norm}" ]; then + echo "Kernel Report ASLR test [Failed - no samples captured]" + err=3D1 + elif [ -s "${diff_file}" ]; then + echo "Kernel Report ASLR test [Failed - reports differ]" + echo "Showing first 20 lines of diff:" + head -n 20 "${diff_file}" + err=3D1 + else + echo "Kernel Report ASLR test [Success]" + fi +} + +test_regs_stripping() { + echo "Test user register stripping" + local rdata=3D"${temp_dir}/perf.data.regs" + local rdata2=3D"${temp_dir}/perf.data.regs.injected" + local rdata_clean=3D"${temp_dir}/perf.data.regs.clean" + + if ! perf record -e cycles:u --user-regs -o "${rdata}" ${prog} > /dev/nu= ll 2>&1; then + echo "Skipping user registers test as recording failed (unsupported fl= ag/platform)" + return + fi + + perf inject -b -i "${rdata}" -o "${rdata_clean}" + perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}" + + local report1=3D"${temp_dir}/report_regs1" + local report2=3D"${temp_dir}/report_regs2" + local report1_clean=3D"${temp_dir}/report_regs1.clean" + local report2_clean=3D"${temp_dir}/report_regs2.clean" + local diff_file=3D"${temp_dir}/diff_regs" + + perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || tr= ue + perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true + + grep '%' "${report1}" | grep -v '^#' | \ + grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \ + sort > "${report1_clean}" || true + grep '%' "${report2}" | grep -v '^#' | \ + grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \ + sort > "${report2_clean}" || true + + diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true + + if [ ! -s "${report1_clean}" ]; then + echo "User registers stripping test [Failed - profile trace starved/em= pty]" + err=3D1 + return + elif [ -s "${diff_file}" ]; then + echo "User registers stripping test [Failed - report parsing differs]" + echo "Showing first 20 lines of diff:" + head -n 20 "${diff_file}" + err=3D1 + return + fi + + local script_dump=3D"${temp_dir}/script_regs_dump" + perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true + if grep -q "user regs:" "${script_dump}"; then + echo "User registers stripping test [Failed - register dumps still pre= sent]" + err=3D1 + else + echo "User registers stripping test [Success]" + fi +} + +test_basic_aslr +test_pipe_aslr +test_callchain_aslr +test_report_aslr +test_pipe_report_aslr +test_pipe_out_report_aslr +test_dropped_samples +test_kernel_aslr +test_kernel_report_aslr +test_regs_stripping + +cleanup ${err} +exit $err --=20 2.54.0.1032.g2f8565e1d1-goog