Hi, The attached patch fixes a heap out-of-bounds read in libdw when an inline DW_FORM_string attribute is not null-terminated inside its unit.
dwarf_formstring returns attrp->valp for DW_FORM_string without checking for a terminating NUL. dwarf_getattrs passes each attribute to its callback before __libdw_form_val_len validates the value, and dwarf_attr (through __libdw_find_attr) returns the matching attribute without computing its length, so a DW_FORM_string that runs to the end of its unit is returned unterminated and callers read past the section data. Commit fda09f5f added this guarantee for the .debug_str and .debug_line_str forms; the patch extends it to DW_FORM_string by requiring a NUL before cu->endp, which covers every caller. Reproducible with eu-readelf --debug-dump=info on a crafted ELF whose zlib-compressed .debug_info ends in an unterminated DW_FORM_string; ASAN reports a heap-buffer-overflow READ just past the decompressed section buffer. The same overread happens in a plain dwarf_diename caller and inside libdw in dwarf_getsrcfiles (read_srcfiles, unterminated DW_AT_comp_dir). The patch (git format-patch, applies on HEAD 10b728d8) is attached. I have small reproducer files for all three cases and can send them, or add them as a test case, if that helps. Thanks, Sujal Tuladhar
From 95295523d58ca3ba65b56fb38a98755fe01485dc Mon Sep 17 00:00:00 2001 From: Sujal Tuladhar <[email protected]> Date: Thu, 5 Nov 2026 08:08:23 +0000 Subject: [PATCH] libdw: check that DW_FORM_string values are null-terminated dwarf_formstring returns the value pointer of a DW_FORM_string attribute as is: if (attrp->form == DW_FORM_string) /* A simple inlined string. */ return (const char *) attrp->valp; The terminating NUL is only checked by __libdw_form_val_len, but dwarf_getattrs calls its callback before it computes the length of the current attribute, and dwarf_attr (through __libdw_find_attr) returns the matching attribute without computing its length either. So when the last attribute of a DIE is a DW_FORM_string that runs to the end of its unit without a terminating NUL, callers get a string that is not null-terminated and read past the end of the section data. Commit fda09f5f ("libdw: check that DWARF strings are null-terminated") added this guarantee for the .debug_str and .debug_line_str forms. Extend it to DW_FORM_string by requiring a NUL between the value and the end of its unit, so every caller of dwarf_formstring is covered. Reproducible with a crafted ELF whose zlib-compressed .debug_info ends in an unterminated DW_FORM_string. AddressSanitizer reports a heap-buffer-overflow READ just past the decompressed section buffer in eu-readelf --debug-dump=info, in a plain dwarf_diename caller, and inside libdw itself in dwarf_getsrcfiles (read_srcfiles reading an unterminated DW_AT_comp_dir). * libdw/dwarf_formstring.c (dwarf_formstring): Check that a DW_FORM_string is null-terminated inside its unit. Signed-off-by: Sujal Tuladhar <[email protected]> --- libdw/dwarf_formstring.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libdw/dwarf_formstring.c b/libdw/dwarf_formstring.c index 65f03a5e..7450c056 100644 --- a/libdw/dwarf_formstring.c +++ b/libdw/dwarf_formstring.c @@ -43,8 +43,20 @@ dwarf_formstring (Dwarf_Attribute *attrp) /* We found it. Now determine where the string is stored. */ if (attrp->form == DW_FORM_string) - /* A simple inlined string. */ - return (const char *) attrp->valp; + { + /* A simple inlined string. dwarf_getattrs and dwarf_attr hand out + the attribute before its value length is checked, so make sure the + string is null-terminated inside its unit. */ + const unsigned char *endp = attrp->cu->endp; + if (unlikely (attrp->valp >= endp + || memchr (attrp->valp, '\0', + (size_t) (endp - attrp->valp)) == NULL)) + { + __libdw_seterrno (DWARF_E_INVALID_DWARF); + return NULL; + } + return (const char *) attrp->valp; + } Dwarf_CU *cu = attrp->cu; Dwarf *dbg = cu->dbg; -- 2.43.0
