[PATCH 1/7] perf dwarf-aux: Bound the type chases for broken debug info

From: Arnaldo Carvalho de Melo

Date: Sat Sep 19 2026 - 16:41:20 EST


From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>

A DIE that is not what it looks like, e.g. one parsed at an offset that
is not the start of a DIE, can have a DW_AT_type that refers back to
itself, making the typedef/qualifier chases in die_get_real_type() and
die_get_pointer_type() spin forever, and the same for the type name
recursion in die_get_typename_from_type(); 'perf report -s type' did
exactly that on the dwz compressed debug info of zlib-ng (libz.so.1).

No sane chain of typedefs and qualifiers is 32 DIEs long, so give up on
the type with a pr_debug instead of hanging.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
---
tools/perf/util/dwarf-aux.c | 88 ++++++++++++++++++++++++++++---------
1 file changed, 68 insertions(+), 20 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index d7160f87ac7d7ab3..b5ffeea54446408d 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -266,16 +266,29 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
return NULL;
}

+/*
+ * A DIE that is not what it looks like, e.g. one parsed at an offset
+ * that is not the start of a DIE, can have a DW_AT_type that refers
+ * back to itself, making these chases spin forever: bound them and
+ * report, instead of hanging.
+ */
+#define MAX_TYPE_CHASE 32
+
/* Get a type die, but skip qualifiers */
Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
{
- int tag;
+ int tag, chase = 0;

do {
vr_die = die_get_type(vr_die, die_mem);
if (!vr_die)
- break;
+ return NULL;
tag = dwarf_tag(vr_die);
+ if (++chase > MAX_TYPE_CHASE) {
+ pr_debug("DWARF: qualifier chase limit reached at DIE 0x%lx\n",
+ (unsigned long)dwarf_dieoffset(vr_die));
+ return NULL;
+ }
} while (tag == DW_TAG_const_type ||
tag == DW_TAG_restrict_type ||
tag == DW_TAG_volatile_type ||
@@ -296,8 +309,15 @@ Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
*/
Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
{
+ int chase = 0;
+
do {
vr_die = __die_get_real_type(vr_die, die_mem);
+ if (++chase > MAX_TYPE_CHASE) {
+ pr_debug("DWARF: typedef chase limit reached at DIE 0x%lx\n",
+ vr_die ? (unsigned long)dwarf_dieoffset(vr_die) : 0);
+ return NULL;
+ }
} while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef);

return vr_die;
@@ -314,7 +334,7 @@ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
*/
Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem)
{
- int tag;
+ int tag, chase = 0;

do {
tag = dwarf_tag(type_die);
@@ -324,6 +344,11 @@ Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem)
tag != DW_TAG_restrict_type && tag != DW_TAG_volatile_type &&
tag != DW_TAG_shared_type)
return NULL;
+ if (++chase > MAX_TYPE_CHASE) {
+ pr_debug("DWARF: pointer type chase limit reached at DIE 0x%lx\n",
+ (unsigned long)dwarf_dieoffset(type_die));
+ return NULL;
+ }
type_die = die_get_type(type_die, die_mem);
} while (type_die);

@@ -1118,17 +1143,25 @@ Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
die_mem);
}

-/**
- * die_get_typename_from_type - Get the name of given type DIE
- * @type_die: a type DIE
- * @buf: a strbuf for result type name
- *
- * Get the name of @type_die and stores it to @buf. Return 0 if succeeded.
- * and Return -ENOENT if failed to find type name.
- * Note that the result will stores typedef name if possible, and stores
- * "*(function_type)" if the type is a function pointer.
+/*
+ * The name follows DW_AT_type, so a self-referring DIE makes this
+ * recurse forever: bound it like the chases above.
*/
-int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
+static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf,
+ int depth);
+
+static int __die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf, int depth)
+{
+ Dwarf_Die type;
+
+ if (__die_get_real_type(vr_die, &type) == NULL)
+ return -ENOENT;
+
+ return __die_get_typename_from_type(&type, buf, depth);
+}
+
+static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf,
+ int depth)
{
int tag, ret;
const char *tmp = "";
@@ -1155,7 +1188,12 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
/* Write a base name */
return strbuf_addf(buf, "%s%s", tmp, name ?: "");
}
- ret = die_get_typename(type_die, buf);
+ if (depth >= MAX_TYPE_CHASE) {
+ pr_debug("DWARF: type name recursion limit reached at DIE 0x%lx\n",
+ (unsigned long)dwarf_dieoffset(type_die));
+ return -ENOENT;
+ }
+ ret = __die_get_typename(type_die, buf, depth + 1);
if (ret < 0) {
/* void pointer has no type attribute */
if (tag == DW_TAG_pointer_type && ret == -ENOENT)
@@ -1166,6 +1204,21 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
return strbuf_addstr(buf, tmp);
}

+/**
+ * die_get_typename_from_type - Get the name of given type DIE
+ * @type_die: a type DIE
+ * @buf: a strbuf for result type name
+ *
+ * Get the name of @type_die and stores it to @buf. Return 0 if succeeded.
+ * and Return -ENOENT if failed to find type name.
+ * Note that the result will stores typedef name if possible, and stores
+ * "*(function_type)" if the type is a function pointer.
+ */
+int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
+{
+ return __die_get_typename_from_type(type_die, buf, 0);
+}
+
/**
* die_get_typename - Get the name of given variable DIE
* @vr_die: a variable DIE
@@ -1178,12 +1231,7 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
*/
int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf)
{
- Dwarf_Die type;
-
- if (__die_get_real_type(vr_die, &type) == NULL)
- return -ENOENT;
-
- return die_get_typename_from_type(&type, buf);
+ return __die_get_typename(vr_die, buf, 0);
}

/**
--
2.53.0