From: sashiko-bot@kernel.org
To: "Tudor-Stefan Magirescu" <tudor.magirescu@gmail.com>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [RFC PATCH v2 1/1] perf scripts flamegraph: Add --asm option
Date: Thu, 27 Aug 2026 15:37:34 +0000 [thread overview]
Message-ID: <20260827153734.ADB001F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260827152354.1513813-2-tudor.magirescu@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Medium] Failure to handle arguments in the `annotate.objdump` configuration.
- [Medium] The DSO cache relies solely on the DSO path string, ignoring the build ID (`dso_bid`). This causes cache collisions and incorrect instruction resolution for different binaries that share the same path.
Pre-existing issues:
- [High] Inline JSON injection in HTML output allows Cross-Site Scripting (XSS).
--
commit a1b6c61e23ce511b72ebb5b70907e6c0f8aea1bb
Author: Tudor-Stefan Magirescu <tudor.magirescu@gmail.com>
perf scripts flamegraph: Add --asm option
This commit adds an `--asm` option to the perf flamegraph script to generate
more fine-grained flame graphs. When enabled, instruction-level information
appears as leaves in the call stacks by disassembling the sampled addresses.
> diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py
> index ad735990c5bec..60ace98e73add 100755
> --- a/tools/perf/scripts/python/flamegraph.py
> +++ b/tools/perf/scripts/python/flamegraph.py
[ ... ]
> @@ -67,10 +71,146 @@ class Node:
> }
>
>
> +@dataclass
> +class Symbol:
> + start: int
> + size: int
[ ... ]
> + @staticmethod
> + def default_objdump() -> Optional[str]:
> + config = perf_config_get("annotate.objdump")
> + cmd = config if config else "objdump"
> + try:
> + subprocess.run([cmd, "--version"], capture_output=True, check=False)
[Severity: Medium]
In default_objdump(), will this fail if the `annotate.objdump` configuration
contains arguments?
If `cmd` contains spaces (e.g. `objdump -M intel`), passing it as a single
string to `subprocess.run()` treats the entire string as the executable name.
This will result in a `FileNotFoundError`, crashing the script and disabling the
`--asm` annotation entirely.
> + except FileNotFoundError as err:
> + print(f"Error running objdump: {err}; "
> + "instruction annotations will be skipped", file=sys.stderr)
> + return None
> + return cmd
> +
> + def load_symbols(self, resolved_dso: str) -> dict[str, Symbol]:
> + result = subprocess.run(
> + [self.objdump, "--demangle", "-t", resolved_dso],
In load_symbols(), if `self.objdump` contained spaces and bypassed the version
check, this direct invocation without tokenizing arguments would also fail.
> + capture_output=True, text=True, check=False
> + )
[ ... ]
> + def lookup_instruction(self, dso: str, dso_bid: str, func: str,
> + off: int) -> Optional[tuple[int, Optional[str]]]:
> + if self.objdump is None:
> + return None
> +
> + if dso not in self.resolved_dsos:
> + self.resolved_dsos[dso] = ResolvedDso(
> + symtab_file=self.resolve_dso(dso_bid, ('debug', 'elf')),
> + code_file=self.resolve_dso(dso_bid, ('elf', 'debug')),
> + )
[Severity: Medium]
In lookup_instruction(), does this cache logic handle different binaries with
the same path correctly?
The cache key uses only `dso` (the path string) to store and retrieve binary
paths, ignoring the `dso_bid` (build ID) on subsequent lookups. If a `perf.data`
file contains events from distinct binaries that share the same path (e.g.,
different containers with `/lib/x86_64-linux-gnu/libc.so.6`), won't this cause
cache collisions and resolve instructions from the wrong binary?
> +
> + resolved_dso = self.resolved_dsos[dso]
> + if resolved_dso.symtab_file is None or resolved_dso.code_file is None:
> + return None
> +
> + if (dso, func) in self.instructions:
Similarly, the instruction cache omits `dso_bid` from its compound key, which
could retrieve cached instructions from the wrong binary on collisions.
> + addr = self.symbols[dso][func].start + off
> + return (addr, self.instructions[(dso, func)].get(addr))
> +
> + if dso not in self.symbols:
> + self.symbols[dso] = self.load_symbols(resolved_dso.symtab_file)
This symbol cache also uses only `dso` as the key, loading symbols from the wrong
file if collisions occur.
> +
> + sym = self.symbols[dso].get(func)
> + if sym is None:
> + return None
> +
> + self.instructions[(dso, func)] = self.load_instructions(resolved_dso.code_file, sym)
> + addr = sym.start + off
> + return (addr, self.instructions[(dso, func)].get(addr))
[ ... ]
> @@ -119,6 +259,23 @@ class FlameGraphCLI:
> name = event.get("symbol", "[unknown]")
> libtype = self.get_libtype_from_dso(event.get("dso"))
> node = self.find_or_create_node(node, name, libtype)
> +
> + if self.args.asm:
> + # use the sample IP directly rather than callchain[0], since with
> + # precise event recording (i.e. :pp) the top callchain entry may
> + # point to the next instruction rather than the sampled IP
> + sym_name = event.get("symbol")
> + sym_off = event.get("symoff")
> + dso = event.get("dso")
> + dso_bid = event.get("dso_bid")
> +
> + if sym_name and sym_off is not None and dso and dso_bid:
> + found = self.instructions.lookup_instruction(dso, dso_bid, sym_name, sym_off)
> + if found is not None:
> + addr, instruction = found
> + name = f"{instruction} [0x{addr:x}]" if instruction else f"[0x{addr:x}]"
[Severity: High]
This is a pre-existing issue, but does assigning untrusted strings to the node
name in process_event() create a Cross-Site Scripting (XSS) vulnerability?
Untrusted strings originating from binary disassembly (and previously, symbol
names) are assigned here as the node's name. Later in `trace_end()`, the JSON
structure is serialized via `json.dumps()` without escaping HTML control
characters.
The unescaped JSON string is then written directly into an HTML `<script>` block
in tools/perf/scripts/python/flamegraph.py. If an attacker provides a crafted
`perf.data` trace containing `</script>` in the instruction output or symbol
name, they could break out and execute arbitrary JavaScript when the user opens
the resulting HTML report.
> + libtype = self.get_libtype_from_dso(dso)
> + node = self.find_or_create_node(node, name, libtype)
> node.value += 1
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827152354.1513813-2-tudor.magirescu@gmail.com?part=1
prev parent reply other threads:[~2026-08-27 15:37 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-21 8:50 [RFC PATCH 0/1] perf scripts flamegraph: Add --asm option Tudor-Stefan Magirescu
2026-08-21 8:50 ` [RFC PATCH 1/1] " Tudor-Stefan Magirescu
2026-08-21 9:05 ` sashiko-bot
2026-08-27 15:23 ` [RFC PATCH v2 0/1] " Tudor-Stefan Magirescu
2026-08-27 15:23 ` [RFC PATCH v2 1/1] " Tudor-Stefan Magirescu
2026-08-27 15:37 ` sashiko-bot [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260827153734.ADB001F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tudor.magirescu@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox