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

From: Ian Rogers

Date: Fri Sep 25 2026 - 11:28:36 EST


On Fri, Sep 25, 2026 at 8:07 AM Arnaldo Carvalho de Melo
<acme@xxxxxxxxxx> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
>
> A DIE 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(), die_get_pointer_type() and
> die_get_typename_from_type() spin forever; 'perf report -s type' did
> that on the dwz compressed debug info of zlib-ng (libz.so.1).
>
> No sane typedef/qualifier chain is 32 DIEs long, so give up on the type
> with a pr_debug instead of hanging.
>
> Cc: Masami Hiramatsu <mhiramat@xxxxxxxxxx>
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>

Reviewed-by: Ian Rogers <irogers@xxxxxxxxxx>

Thanks,
Ian

> ---
> tools/perf/util/dwarf-aux.c | 84 ++++++++++++++++++++++++++++---------
> 1 file changed, 64 insertions(+), 20 deletions(-)
>
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> index 1560e721479a1ac2..2c135ebb1551fca4 100644
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c
> @@ -267,16 +267,27 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
> return NULL;
> }
>
> +/*
> + * A DIE parsed at an offset that is not the start of a DIE can refer
> + * back to itself via DW_AT_type: bound these chases.
> + */
> +#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 ||
> @@ -297,8 +308,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;
> @@ -315,7 +333,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);
> @@ -325,6 +343,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);
>
> @@ -1119,17 +1142,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 = "";
> @@ -1156,7 +1187,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)
> @@ -1167,6 +1203,19 @@ 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 a type DIE
> + * @type_die: a type DIE
> + * @buf: a strbuf for the result type name
> + *
> + * Stores the typedef name if possible, "*(function_type)" for function
> + * pointers. Returns 0 or -ENOENT.
> + */
> +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
> @@ -1179,12 +1228,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
>