From: Yaseen <[email protected]>

resolve_symbol() in relocate.c validates the symbol name offset with
st_name >= d_size, then forms a pointer into the string table and
passes it to strcmp().  Nothing checks that a NUL byte actually exists
between st_name and the end of the string table.  A crafted ET_REL
object whose .strtab has the referenced name at the final bytes with
no terminating NUL makes strcmp() read past the string table -- an
out-of-bounds read.

dwfl_module_getsym() has the same pattern: it checks the offset and
returns the pointer to the caller without verifying termination.  Any
caller that passes the result to strlen, strcmp, printf %s, etc. will
read out of bounds on a malformed file.

eu-unstrip already does this correctly at unstrip.c:888 using memrchr
to verify the name is terminated.  The libdwfl paths were missed.

The path is reachable from dwfl_module_getdwarf() on an ET_REL module:

  dwfl_module_getdwarf
    -> load_dw
    -> __libdwfl_relocate
    -> relocate_section
    -> resolve_symbol
    -> strcmp(name, n)        <-- unterminated name, reads past .strtab

Reproduced under AddressSanitizer with a crafted ET_REL whose .strtab
contains a symbol name with no NUL within the declared section size.
The referrer and a provider module are loaded through dwfl_report_elf
and dwfl_module_getdwarf.  The elf_memory path (which gives ASan a
heap redzone at the exact section boundary) shows:

  ==ERROR: AddressSanitizer: heap-buffer-overflow
  READ of size 11 at 0x... thread T0
    #0 ... in strcmp
    #1 ... in relocate
    #2 ... in relocate_section
    #3 ... in __libdwfl_relocate_section
  0x... is located 0 bytes after 1166-byte region

A valid control object with the terminating NUL added exits cleanly.

Add memchr checks after the offset validation.  If no NUL is found
within the remaining string table, return DWFL_E_BADSTROFF.

* libdwfl/relocate.c (resolve_symbol): Check that the referrer
symbol name is NUL terminated within the string table before
using it in strcmp.
(resolve_symbol): Check the same for the provider symbol name.
* libdwfl/dwfl_module_getsym.c (__libdwfl_getsym): Check that the
symbol name is NUL terminated before returning it to the caller.

Signed-off-by: Yaseen <[email protected]>
---
 libdwfl/dwfl_module_getsym.c | 4 +++-
 libdwfl/relocate.c           | 6 ++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/libdwfl/dwfl_module_getsym.c b/libdwfl/dwfl_module_getsym.c
index 3a364a7..1631ae1 100644
--- a/libdwfl/dwfl_module_getsym.c
+++ b/libdwfl/dwfl_module_getsym.c
@@ -190,7 +190,9 @@ __libdwfl_getsym (Dwfl_Module *mod, int ndx, GElf_Sym *sym, 
GElf_Addr *addr,
   if (addr != NULL)
     *addr = st_value;
 
-  if (unlikely (sym->st_name >= symstrdata->d_size))
+  if (unlikely (sym->st_name >= symstrdata->d_size)
+      || unlikely (memchr (symstrdata->d_buf + sym->st_name, '\0',
+                          symstrdata->d_size - sym->st_name) == NULL))
     {
       __libdwfl_seterrno (DWFL_E_BADSTROFF);
       return NULL;
diff --git a/libdwfl/relocate.c b/libdwfl/relocate.c
index dc9f45e..b8df09d 100644
--- a/libdwfl/relocate.c
+++ b/libdwfl/relocate.c
@@ -285,6 +285,9 @@ resolve_symbol (Dwfl_Module *referer, struct 
reloc_symtab_cache *symtab,
        }
       if (unlikely (sym->st_name >= symtab->symstrdata->d_size))
        return DWFL_E_BADSTROFF;
+      if (unlikely (memchr (symtab->symstrdata->d_buf + sym->st_name, '\0',
+                           symtab->symstrdata->d_size - sym->st_name) == NULL))
+       return DWFL_E_BADSTROFF;
 
       const char *name = symtab->symstrdata->d_buf;
       name += sym->st_name;
@@ -319,6 +322,9 @@ resolve_symbol (Dwfl_Module *referer, struct 
reloc_symtab_cache *symtab,
                /* Get this candidate symbol's name.  */
                if (unlikely (sym->st_name >= m->symstrdata->d_size))
                  return DWFL_E_BADSTROFF;
+               if (unlikely (memchr (m->symstrdata->d_buf + sym->st_name, '\0',
+                                     m->symstrdata->d_size - sym->st_name) == 
NULL))
+                 return DWFL_E_BADSTROFF;
                const char *n = m->symstrdata->d_buf;
                n += sym->st_name;
 
-- 
2.43.0

Reply via email to