public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
@ 2026-05-02  6:48 Ian Rogers
  2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  0 siblings, 1 reply; 8+ messages in thread
From: Ian Rogers @ 2026-05-02  6:48 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

A segmentation fault was observed in `libdw` when running `perf kmem`
with `--page stat` on some workloads. The crash occurred deep inside
`libdw` (specifically in `dwarf_child` and `dwarf_diename`) when
processing DWARF information.

There were two separate issues contributing to this crash:

1. Dangling pointers from `dwarf_getfuncs`:
`die_find_realfunc` uses `dwarf_getfuncs` to iterate over all functions
in a Compile Unit (CU) to find the one enclosing a given address.
`dwarf_getfuncs` passes temporary `Dwarf_Die` structures to its
callback. Copying these via `memcpy` leads to dangling internal
pointers (such as to `Dwarf_Abbrev` structures) once `dwarf_getfuncs`
returns and cleans up its temporary state. Dereferencing these dangling
pointers in subsequent calls like `dwarf_child` causes a SIGSEGV.

To fix this, use `dwarf_cu_getdwarf(cu_die->cu)` to obtain the `Dwarf`
session pointer, and then use `dwarf_offdie` to securely reconstruct
and cache the `Dwarf_Die` from its offset. This ensures all internal
pointers remain valid and persistent.

2. Uninitialized memory access in `cu_walk_functions_at`:
A logic bug in the `for` loop of `cu_walk_functions_at` attempted to
avoid in-place modifications by using a separate `next_die` buffer.
However, it performed a `memcpy(&die_mem, &next_die)` at the end of the
loop body *before* `next_die` was actually initialized by
`die_find_child` in the loop increment step. This resulted in copying
uninitialized memory into `die_mem` on the first iteration, leading to
a crash on the subsequent step.

Rewrite the loop as a standard `while` loop to ensure that
`die_find_child` fills `next_die` *before* any data is copied into
`die_mem` for the next iteration.

Assisted-by: Gemini:gemini-3.1-pro-preview
Fixes: 221d061182b8 ("perf probe: Support inline function call-site tracing")
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/dwarf-aux.c | 44 +++++++++++++++++++++++--------------
 1 file changed, 28 insertions(+), 16 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 92db2fccc788..52fdf6d49d3b 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -156,22 +156,25 @@ static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data);
 int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
 		    int (*callback)(Dwarf_Die *, void *), void *data)
 {
-	Dwarf_Die die_mem;
+	Dwarf_Die die_mem, next_die;
 	Dwarf_Die *sc_die;
 	int ret = -ENOENT;
 
 	/* Inlined function could be recursive. Trace it until fail */
-	for (sc_die = die_find_realfunc(cu_die, addr, &die_mem);
-	     sc_die != NULL;
-	     sc_die = die_find_child(sc_die, __die_find_inline_cb, &addr,
-				     &die_mem)) {
+	sc_die = die_find_realfunc(cu_die, addr, &die_mem);
+	while (sc_die != NULL) {
 		ret = callback(sc_die, data);
 		if (ret)
 			break;
+
+		sc_die = die_find_child(sc_die, __die_find_inline_cb, &addr, &next_die);
+		if (sc_die) {
+			memcpy(&die_mem, &next_die, sizeof(Dwarf_Die));
+			sc_die = &die_mem;
+		}
 	}
 
 	return ret;
-
 }
 
 /**
@@ -561,7 +564,7 @@ Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
 			  int (*callback)(Dwarf_Die *, void *),
 			  void *data, Dwarf_Die *die_mem)
 {
-	Dwarf_Die child_die;
+	Dwarf_Die child_die, sibling_die;
 	int ret;
 
 	ret = dwarf_child(rt_die, die_mem);
@@ -579,7 +582,8 @@ Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
 			return die_mem;
 		}
 	} while ((ret & DIE_FIND_CB_SIBLING) &&
-		 dwarf_siblingof(die_mem, die_mem) == 0);
+		 dwarf_siblingof(die_mem, &sibling_die) == 0 &&
+		 (memcpy(die_mem, &sibling_die, sizeof(Dwarf_Die)), 1));
 
 	return NULL;
 }
@@ -622,10 +626,14 @@ Dwarf_Die *die_find_tailfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	/* dwarf_getscopes can't find subprogram. */
 	if (!dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0))
 		return NULL;
-	else
-		return die_mem;
+
+	if (dwarf_offdie(dwarf_cu_getdwarf(cu_die->cu), dwarf_dieoffset(die_mem), die_mem) == NULL)
+		return NULL;
+
+	return die_mem;
 }
 
+
 /* die_find callback for non-inlined function search */
 static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
 {
@@ -647,6 +655,7 @@ static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
  * die_find_realfunc - Search a non-inlined function at given address
  * @cu_die: a CU DIE which including @addr
  * @addr: target address
+ * @dbg: Dwarf session
  * @die_mem: a buffer for result DIE
  *
  * Search a non-inlined function DIE which includes @addr. Stores the
@@ -661,8 +670,11 @@ Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	/* dwarf_getscopes can't find subprogram. */
 	if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
 		return NULL;
-	else
-		return die_mem;
+
+	if (dwarf_offdie(dwarf_cu_getdwarf(cu_die->cu), dwarf_dieoffset(die_mem), die_mem) == NULL)
+		return NULL;
+
+	return die_mem;
 }
 
 /* die_find callback for inline function search */
@@ -710,15 +722,15 @@ Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
 {
 	Dwarf_Die tmp_die;
 
-	sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, &tmp_die);
+	sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, die_mem);
 	if (!sp_die)
 		return NULL;
 
 	/* Inlined function could be recursive. Trace it until fail */
 	while (sp_die) {
-		memcpy(die_mem, sp_die, sizeof(Dwarf_Die));
-		sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr,
-					&tmp_die);
+		sp_die = die_find_child(die_mem, __die_find_inline_cb, &addr, &tmp_die);
+		if (sp_die)
+			memcpy(die_mem, &tmp_die, sizeof(Dwarf_Die));
 	}
 
 	return die_mem;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes
  2026-05-02  6:48 [PATCH v1] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
@ 2026-05-02 15:56 ` Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
                     ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Ian Rogers @ 2026-05-02 15:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

This patch series addresses a number of issues related to improper or
incomplete error handling when interacting with the `libdw` and
`libdwfl` APIs in the `perf` DWARF processing code.
                                                                                                 
The first patch fixes a real segmentation fault observed in `perf
kmem` (via `dwarf_child` and `dwarf_diename`) caused by
`dwarf_getfuncs` returning `-1` on error, which was not caught by the
previous `if (!dwarf_getfuncs(...))` check. This allowed uninitialized
stack memory to be returned and later dereferenced.
  
Following this discovery, a comprehensive audit of `libdw` API usage
was performed across `tools/perf/`. The remaining patches proactively
fix 28 additional instances of ignored return values or unchecked
pointers that could lead to similar uninitialized memory propagation,
crashes (e.g., via `strdup(NULL)` or `strcmp(NULL)`), or silent logic
failures.

v1:
https://lore.kernel.org/linux-perf-users/20260502064839.282422-1-irogers@google.com/

Ian Rogers (6):
  perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  perf dwarf-aux: Fix libdw API contract violations
  perf libdw: Fix libdw API contract violations
  perf probe-finder: Fix libdw API contract violations
  perf annotate-data: Fix libdw API contract violations
  perf debuginfo: Fix libdw API contract violations

 tools/perf/util/annotate-data.c | 17 ++++---
 tools/perf/util/debuginfo.c     |  5 +-
 tools/perf/util/dwarf-aux.c     | 41 ++++++++++------
 tools/perf/util/libdw.c         | 24 +++++++---
 tools/perf/util/probe-finder.c  | 84 ++++++++++++++++++++++-----------
 5 files changed, 113 insertions(+), 58 deletions(-)

-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
@ 2026-05-02 15:56   ` Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Ian Rogers @ 2026-05-02 15:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

A segmentation fault was observed in `libdw` when running `perf kmem`
with `--page stat` on some workloads. The crash occurred deep inside
`libdw` (specifically in `dwarf_child` and `dwarf_diename`) when
processing DWARF information.

The root cause was improper error handling of `dwarf_getfuncs` in
`die_find_realfunc` and `die_find_tailfunc`.

`dwarf_getfuncs` returns:
 - `0` on success (when all functions have been processed).
 - A positive offset if the callback aborts early (e.g., via
   `DWARF_CB_ABORT` when a match is found).
 - `-1` on error.

The original code used `if (!dwarf_getfuncs(...)) return NULL;`. On
error (`-1`), `!-1` evaluates to `0` (false), bypassing the error
check. Execution then proceeded as if a match was found, returning
uninitialized stack memory (`die_mem`) to the caller
(`cu_walk_functions_at`). When `cu_walk_functions_at` passed this
uninitialized memory to `libdw` via `dwarf_diename`, it caused a
segmentation fault.

Fix this by correcting the error check to `if (dwarf_getfuncs(...) <= 0)`.

Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: d4c537e6bf86 ("perf probe: Ignore tail calls to probed functions")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/dwarf-aux.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 92db2fccc788..6752adca8ee8 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -171,7 +171,6 @@ int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	}
 
 	return ret;
-
 }
 
 /**
@@ -620,12 +619,13 @@ Dwarf_Die *die_find_tailfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
 }
 
+
 /* die_find callback for non-inlined function search */
 static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
 {
@@ -647,6 +647,7 @@ static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
  * die_find_realfunc - Search a non-inlined function at given address
  * @cu_die: a CU DIE which including @addr
  * @addr: target address
+ * @dbg: Dwarf session
  * @die_mem: a buffer for result DIE
  *
  * Search a non-inlined function DIE which includes @addr. Stores the
@@ -659,7 +660,7 @@ Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 2/6] perf dwarf-aux: Fix libdw API contract violations
  2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
@ 2026-05-02 15:56   ` Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 3/6] perf libdw: " Ian Rogers
                     ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Ian Rogers @ 2026-05-02 15:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_decl_line`, `dwarf_getfuncs`, and
`dwarf_lineaddr` to prevent using uninitialized stack variables or
incorrectly reporting success on failure.

Fixes: 57f95bf5f882 ("perf probe: Show correct statement line number by perf probe -l")
Fixes: 3f4460a28fb2 ("perf probe: Filter out redundant inline-instances")
Fixes: 75186a9b09e4 ("perf probe: Fix to show lines of sys_ functions correctly")
Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: 6243b9dc4c99 ("perf probe: Move dwarf specific functions to dwarf-aux.c")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/dwarf-aux.c | 34 ++++++++++++++++++++++------------
 1 file changed, 22 insertions(+), 12 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 6752adca8ee8..0b75b4af2b47 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -125,7 +125,8 @@ int cu_find_lineinfo(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	    && die_entrypc(&die_mem, &faddr) == 0 &&
 	    faddr == addr) {
 		*fname = die_get_decl_file(&die_mem);
-		dwarf_decl_line(&die_mem, lineno);
+		if (dwarf_decl_line(&die_mem, lineno) != 0)
+			return -ENOENT;
 		goto out;
 	}
 
@@ -797,8 +798,8 @@ static int __die_walk_instances_cb(Dwarf_Die *inst, void *data)
 
 	/* Ignore redundant instances */
 	if (dwarf_tag(inst) == DW_TAG_inlined_subroutine) {
-		dwarf_decl_line(origin, &tmp);
-		if (die_get_call_lineno(inst) == tmp) {
+		if (dwarf_decl_line(origin, &tmp) == 0 &&
+		    die_get_call_lineno(inst) == tmp) {
 			tmp = die_get_decl_fileno(origin);
 			if (die_get_call_fileno(inst) == tmp)
 				return DIE_FIND_CB_CONTINUE;
@@ -950,7 +951,11 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 	/* Get the CU die */
 	if (dwarf_tag(rt_die) != DW_TAG_compile_unit) {
 		cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL);
-		dwarf_decl_line(rt_die, &decl);
+		if (dwarf_decl_line(rt_die, &decl) != 0) {
+			pr_debug2("Failed to get the declared line number of %s\n",
+				  dwarf_diename(rt_die));
+			return -EINVAL;
+		}
 		decf = die_get_decl_file(rt_die);
 		if (!decf) {
 			pr_debug2("Failed to get the declared file name of %s\n",
@@ -1004,8 +1009,8 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 				    die_get_call_lineno(&die_mem) == lineno)
 					goto found;
 
-				dwarf_decl_line(&die_mem, &inl);
-				if (inl != decl ||
+				if (dwarf_decl_line(&die_mem, &inl) != 0 ||
+				    inl != decl ||
 				    decf != die_get_decl_file(&die_mem))
 					continue;
 			}
@@ -1036,8 +1041,10 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 			.data = data,
 			.retval = 0,
 		};
-		dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0);
-		ret = param.retval;
+		if (dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0) < 0)
+			ret = -EINVAL;
+		else
+			ret = param.retval;
 	}
 
 	return ret;
@@ -1941,10 +1948,13 @@ static bool die_get_postprologue_addr(unsigned long entrypc_idx,
 			break;
 	}
 
-	dwarf_lineaddr(line, postprologue_addr);
-	if (*postprologue_addr >= highpc)
-		dwarf_lineaddr(dwarf_onesrcline(lines, i - 1),
-			       postprologue_addr);
+	if (dwarf_lineaddr(line, postprologue_addr) != 0)
+		return false;
+	if (*postprologue_addr >= highpc) {
+		if (dwarf_lineaddr(dwarf_onesrcline(lines, i - 1),
+				   postprologue_addr) != 0)
+			return false;
+	}
 
 	return true;
 }
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 3/6] perf libdw: Fix libdw API contract violations
  2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
@ 2026-05-02 15:56   ` Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 4/6] perf probe-finder: " Ian Rogers
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Ian Rogers @ 2026-05-02 15:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwfl_report_end` and `dwfl_module_addrdie`.
Validate `dwarf_diename` result before passing to `new_inline_sym` (avoid
potential `strdup(NULL)` crash) and check `die_get_call_lineno` for
errors.

Fixes: b7a2b011e962 ("perf powerpc: Unify the skip-callchain-idx libdw with that for addr2line")
Fixes: 88c51002d06f ("perf addr2line: Add a libdw implementation")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/libdw.c | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index 216977884103..e37f3b22699d 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -60,7 +60,11 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso)
 		return NULL;
 	}
 
-	dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL);
+	if (dwfl_report_end(dwfl, NULL, NULL) != 0) {
+		dwfl_end(dwfl);
+		return NULL;
+	}
+
 	dso__set_libdw(dso, dwfl);
 
 	return dwfl;
@@ -72,22 +76,27 @@ struct libdw_a2l_cb_args {
 	struct inline_node *node;
 	char *leaf_srcline;
 	bool leaf_srcline_used;
+	int err;
 };
 
 static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 {
 	struct libdw_a2l_cb_args *args  = _args;
-	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
+	const char *name = dwarf_diename(die);
+	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, name ?: "unknown");
 	const char *call_fname = die_get_call_file(die);
+	int call_lineno = die_get_call_lineno(die);
 	char *call_srcline = srcline__unknown;
 	struct inline_list *ilist;
 
-	if (!inline_sym)
-		return -ENOMEM;
+	if (!inline_sym) {
+		args->err = -ENOMEM;
+		return DWARF_CB_ABORT;
+	}
 
 	/* Assign caller information to the parent. */
-	if (call_fname)
-		call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
+	if (call_fname && call_lineno > 0)
+		call_srcline = srcline_from_fileline(call_fname, call_lineno);
 
 	list_for_each_entry(ilist, &args->node->val, list) {
 		if (args->leaf_srcline == ilist->srcline)
@@ -163,7 +172,8 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 		};
 
 		/* Walk from the parent down to the leaf. */
-		cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
+		if (cudie)
+			cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
 
 		if (!args.leaf_srcline_used)
 			free(args.leaf_srcline);
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 4/6] perf probe-finder: Fix libdw API contract violations
  2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                     ` (2 preceding siblings ...)
  2026-05-02 15:56   ` [PATCH v2 3/6] perf libdw: " Ian Rogers
@ 2026-05-02 15:56   ` Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 5/6] perf annotate-data: " Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 6/6] perf debuginfo: " Ian Rogers
  5 siblings, 0 replies; 8+ messages in thread
From: Ian Rogers @ 2026-05-02 15:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_formsdata`, `dwarf_entrypc`,
`dwarf_highpc`, `dwarf_bytesize`, `dwarf_attr`, `dwarf_decl_line`,
`dwarf_getfuncs`, and `dwarf_formref_die`. Validate `dwarf_diename` and
`dwarf_diecu` results to prevent potential crashes. Fix C90 mixed
declarations.

Fixes: 66f69b219716 ("perf probe: Support DW_AT_const_value constant value")
Fixes: 3d918a12a1b3 ("perf probe: Find fentry mcount fuzzed parameter location")
Fixes: bcfc082150c6 ("perf probe: Remove redundant dwarf functions")
Fixes: 221d061182b8 ("perf probe: Fix to search local variables in appropriate scope")
Fixes: b55a87ade383 ("perf probe: Remove die() from probe-finder code")
Fixes: 4c859351226c ("perf probe: Support glob wildcards for function name")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/probe-finder.c | 84 ++++++++++++++++++++++------------
 1 file changed, 56 insertions(+), 28 deletions(-)

diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c
index 64328abeef8b..069f0d83d0b7 100644
--- a/tools/perf/util/probe-finder.c
+++ b/tools/perf/util/probe-finder.c
@@ -93,7 +93,8 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 		if (!tvar)
 			return 0;
 
-		dwarf_formsdata(&attr, &snum);
+		if (dwarf_formsdata(&attr, &snum) != 0)
+			return -ENOENT;
 		ret = asprintf(&tvar->value, "\\%ld", (long)snum);
 
 		return ret < 0 ? -ENOMEM : 0;
@@ -103,8 +104,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 	if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL)
 		return -EINVAL;	/* Broken DIE ? */
 	if (dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0) {
-		ret = dwarf_entrypc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_entrypc(sp_die, &tmp) != 0)
 			return -ENOENT;
 
 		if (probe_conf.show_location_range &&
@@ -115,8 +115,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 			return -ENOENT;
 		}
 
-		ret = dwarf_highpc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_highpc(sp_die, &tmp) != 0)
 			return -ENOENT;
 		/*
 		 * This is fuzzed by fentry mcount. We try to find the
@@ -138,15 +137,21 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 static_var:
 		if (!tvar)
 			return ret2;
-		/* Static variables on memory (not stack), make @varname */
-		ret = strlen(dwarf_diename(vr_die));
-		tvar->value = zalloc(ret + 2);
-		if (tvar->value == NULL)
-			return -ENOMEM;
-		snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die));
-		tvar->ref = alloc_trace_arg_ref((long)offs);
-		if (tvar->ref == NULL)
-			return -ENOMEM;
+		{
+			/* Static variables on memory (not stack), make @varname */
+			const char *name = dwarf_diename(vr_die);
+
+			if (!name)
+				return -ENOENT;
+			ret = strlen(name);
+			tvar->value = zalloc(ret + 2);
+			if (tvar->value == NULL)
+				return -ENOMEM;
+			snprintf(tvar->value, ret + 2, "@%s", name);
+			tvar->ref = alloc_trace_arg_ref((long)offs);
+			if (tvar->ref == NULL)
+				return -ENOMEM;
+		}
 		return ret2;
 	}
 
@@ -234,8 +239,9 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 	}
 
 	if (die_get_real_type(vr_die, &type) == NULL) {
+		const char *name = dwarf_diename(vr_die);
 		pr_warning("Failed to get a type information of %s.\n",
-			   dwarf_diename(vr_die));
+			   name ? name : "<unknown>");
 		return -ENOENT;
 	}
 
@@ -291,7 +297,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 			 probe_type_is_available(PROBE_TYPE_X) ? 'x' : 'u';
 
 	ret = dwarf_bytesize(&type);
-	if (ret <= 0)
+	if (ret < 0)
 		/* No size ... try to use default type */
 		return 0;
 	ret = BYTES_TO_BITS(ret);
@@ -357,7 +363,13 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 			else
 				*ref_ptr = ref;
 		}
-		ref->offset += dwarf_bytesize(&type) * field->index;
+		{
+			int bsize = dwarf_bytesize(&type);
+
+			if (bsize < 0)
+				return -EINVAL;
+			ref->offset += bsize * field->index;
+		}
 		ref->user_access = user_access;
 		goto next;
 	} else if (tag == DW_TAG_pointer_type) {
@@ -611,10 +623,16 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf)
 		memcpy(&pf->sp_die, sc_die, sizeof(Dwarf_Die));
 
 	/* Get the frame base attribute/ops from subprogram */
-	dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr);
-	ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
-	if (ret <= 0 || nops == 0) {
+	if (dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr) == NULL) {
 		pf->fb_ops = NULL;
+	} else {
+		ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
+		if (ret <= 0 || nops == 0)
+			pf->fb_ops = NULL;
+	}
+
+	if (pf->fb_ops == NULL) {
+		/* Not supported */
 	} else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa &&
 		   (pf->cfi_eh != NULL || pf->cfi_dbg != NULL)) {
 		if ((dwarf_cfi_addrframe(pf->cfi_eh, pf->addr, &frame) != 0 &&
@@ -667,8 +685,8 @@ static int find_best_scope_cb(Dwarf_Die *fn_die, void *data)
 		}
 	} else {
 		/* With the line number, find the nearest declared DIE */
-		dwarf_decl_line(fn_die, &lno);
-		if (lno < fsp->line && fsp->diff > fsp->line - lno) {
+		if (dwarf_decl_line(fn_die, &lno) == 0 &&
+		    lno < fsp->line && fsp->diff > fsp->line - lno) {
 			/* Keep a candidate and continue */
 			fsp->diff = fsp->line - lno;
 			memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die));
@@ -1018,7 +1036,8 @@ static int find_probe_point_by_func(struct probe_finder *pf)
 {
 	struct dwarf_callback_param _param = {.data = (void *)pf,
 					      .retval = 0};
-	dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0);
+	if (dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0) < 0)
+		return -ENOENT;
 	return _param.retval;
 }
 
@@ -1207,7 +1226,8 @@ static int copy_variables_cb(Dwarf_Die *die_mem, void *data)
 		 * points to correct die.
 		 */
 		if (dwarf_attr(die_mem, DW_AT_abstract_origin, &attr)) {
-			dwarf_formref_die(&attr, &var_die);
+			if (dwarf_formref_die(&attr, &var_die) == NULL)
+				goto out;
 			if (pf->abstrace_dieoffset != dwarf_dieoffset(&var_die))
 				goto out;
 		}
@@ -1270,6 +1290,8 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
 	struct probe_trace_event *tev;
 	struct perf_probe_arg *args = NULL;
 	int ret, i;
+	const char *realname;
+	Dwarf_Die cu_die_mem;
 
 	/*
 	 * For some reason (e.g. different column assigned to same address)
@@ -1293,13 +1315,17 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
 	if (ret < 0)
 		goto end;
 
-	tev->point.realname = strdup(dwarf_diename(sc_die));
+	realname = dwarf_diename(sc_die);
+	tev->point.realname = strdup(realname ?: "unknown");
 	if (!tev->point.realname) {
 		ret = -ENOMEM;
 		goto end;
 	}
 
-	tev->lang = dwarf_srclang(dwarf_diecu(sc_die, &pf->cu_die, NULL, NULL));
+	if (dwarf_diecu(sc_die, &cu_die_mem, NULL, NULL) != NULL)
+		tev->lang = dwarf_srclang(&cu_die_mem);
+	else
+		tev->lang = DW_LANG_C; // Fallback
 
 	pr_debug("Probe point found: %s+%lu\n", tev->point.symbol,
 		 tev->point.offset);
@@ -1794,7 +1820,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 
 	if (die_match_name(sp_die, lr->function) && die_is_func_def(sp_die)) {
 		lf->fname = die_get_decl_file(sp_die);
-		dwarf_decl_line(sp_die, &lr->offset);
+		if (dwarf_decl_line(sp_die, &lr->offset) != 0)
+			return DWARF_CB_OK; // Skip if no line info
 		pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset);
 		lf->lno_s = lr->offset + lr->start;
 		if (lf->lno_s < 0)	/* Overflow */
@@ -1818,7 +1845,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 static int find_line_range_by_func(struct line_finder *lf)
 {
 	struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0};
-	dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0);
+	if (dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0) < 0)
+		return -ENOENT;
 	return param.retval;
 }
 
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 5/6] perf annotate-data: Fix libdw API contract violations
  2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                     ` (3 preceding siblings ...)
  2026-05-02 15:56   ` [PATCH v2 4/6] perf probe-finder: " Ian Rogers
@ 2026-05-02 15:56   ` Ian Rogers
  2026-05-02 15:56   ` [PATCH v2 6/6] perf debuginfo: " Ian Rogers
  5 siblings, 0 replies; 8+ messages in thread
From: Ian Rogers @ 2026-05-02 15:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_aggregate_size` and `dwarf_formudata`.
Validate `dwarf_diename` before `strdup` to prevent potential crashes.

Fixes: 2bc3cf575a16 ("perf annotate-data: Improve debug message with location info")
Fixes: 4a111cadac85 ("perf annotate-data: Add member field in the data type")
Fixes: 8b1042c425f6 ("perf annotate-data: Set bitfield member offset and size properly")
Fixes: fc044c53b99f ("perf annotate-data: Add dso->data_types tree")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/annotate-data.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 1eff0a27237d..e881a40a4885 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -74,7 +74,8 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind)
 		break;
 	}
 
-	dwarf_aggregate_size(die, &size);
+	if (dwarf_aggregate_size(die, &size) != 0)
+		size = 0;
 
 	strbuf_init(&sb, 32);
 	die_get_typename_from_type(die, &sb);
@@ -250,9 +251,10 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 	if (dwarf_aggregate_size(&die_mem, &size) < 0)
 		size = 0;
 
-	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr))
-		dwarf_formudata(&attr, &loc);
-	else {
+	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr)) {
+		if (dwarf_formudata(&attr, &loc) != 0)
+			loc = 0;
+	} else {
 		/* bitfield member */
 		if (dwarf_attr_integrate(die, DW_AT_data_bit_offset, &attr) &&
 		    dwarf_formudata(&attr, &loc) == 0)
@@ -273,7 +275,9 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 				     dwarf_diename(die), (long)bit_size) < 0)
 				member->var_name = NULL;
 		} else {
-			member->var_name = strdup(dwarf_diename(die));
+			const char *name = dwarf_diename(die);
+
+			member->var_name = strdup(name ?: "unknown");
 		}
 
 		if (member->var_name == NULL) {
@@ -370,7 +374,8 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
 	if (dwarf_tag(type_die) == DW_TAG_typedef)
 		die_get_real_type(type_die, type_die);
 
-	dwarf_aggregate_size(type_die, &size);
+	if (dwarf_aggregate_size(type_die, &size) != 0)
+		size = 0;
 
 	/* Check existing nodes in dso->data_types tree */
 	key.self.type_name = type_name;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 6/6] perf debuginfo: Fix libdw API contract violations
  2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                     ` (4 preceding siblings ...)
  2026-05-02 15:56   ` [PATCH v2 5/6] perf annotate-data: " Ian Rogers
@ 2026-05-02 15:56   ` Ian Rogers
  5 siblings, 0 replies; 8+ messages in thread
From: Ian Rogers @ 2026-05-02 15:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return value of `dwfl_report_end` during offline initialization.
Validate `dwfl_module_relocation_info` result before passing to `strcmp`
to avoid potential segmentation faults.

Fixes: 6f1b6291cf73 ("perf tools: Add util/debuginfo.[ch] files")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/debuginfo.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c
index 0e35c13abd04..49cb7f9b715d 100644
--- a/tools/perf/util/debuginfo.c
+++ b/tools/perf/util/debuginfo.c
@@ -62,7 +62,8 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 
 	dwfl_module_build_id(dbg->mod, &dbg->build_id, &dummy);
 
-	dwfl_report_end(dbg->dwfl, NULL, NULL);
+	if (dwfl_report_end(dbg->dwfl, NULL, NULL) != 0)
+		goto error;
 
 	return 0;
 error:
@@ -167,7 +168,7 @@ int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs,
 	/* Search the relocation related .text section */
 	for (i = 0; i < n; i++) {
 		p = dwfl_module_relocation_info(dbg->mod, i, &shndx);
-		if (strcmp(p, ".text") == 0) {
+		if (p && strcmp(p, ".text") == 0) {
 			/* OK, get the section header */
 			scn = elf_getscn(elf, shndx);
 			if (!scn)
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-05-02 15:57 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-02  6:48 [PATCH v1] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
2026-05-02 15:56 ` [PATCH v2 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
2026-05-02 15:56   ` [PATCH v2 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
2026-05-02 15:56   ` [PATCH v2 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
2026-05-02 15:56   ` [PATCH v2 3/6] perf libdw: " Ian Rogers
2026-05-02 15:56   ` [PATCH v2 4/6] perf probe-finder: " Ian Rogers
2026-05-02 15:56   ` [PATCH v2 5/6] perf annotate-data: " Ian Rogers
2026-05-02 15:56   ` [PATCH v2 6/6] perf debuginfo: " Ian Rogers

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox