From b35e1ec43e93cad1a606ca0babd9a7c6dd1c2206 Mon Sep 17 00:00:00 2001 From: Sayed Kaif Date: Sun, 28 Jun 2026 20:28:32 +0530 Subject: [PATCH] libelf: Fix out-of-bounds read in elf_getarsym for unterminated symbol table elf_getarsym parses the archive symbol table (the "/" or "/SYM64/" member). After validating only the size of the index, it walks the declared number of symbols and, for each one, computes a hash with _dl_elf_hash and advances to the next name with rawmemchr (or an open-coded loop on platforms without rawmemchr). Both of these scan for a terminating NUL byte without any bound. Nothing guarantees that the string area following the offset table actually contains that many NUL-terminated names, or any NUL at all. A crafted archive whose symbol-table string area is not NUL terminated makes the name scan run past the end of the heap buffer holding the string data (or past the mapped file) -- an out-of-bounds read. Remember the end of the string area and use a bounded memchr to locate each name terminator. If a name is not terminated within the string area the archive is malformed, so fail with ELF_E_INVALID_ARCHIVE instead of reading out of bounds. Reproduced under AddressSanitizer with a crafted archive whose string area contains no NUL byte: ==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1 #0 _dl_elf_hash dl-hash.h:45 #1 elf_getarsym elf_getarsym.c:294 0 bytes after 116-byte region ... allocated by elf_getarsym.c:215 The function is reachable from eu-nm -s and eu-readelf, which call elf_getarsym on untrusted .a files. Signed-off-by: Sayed Kaif --- libelf/elf_getarsym.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/libelf/elf_getarsym.c b/libelf/elf_getarsym.c index 281f0c1c5..ec05c684a 100644 --- a/libelf/elf_getarsym.c +++ b/libelf/elf_getarsym.c @@ -199,6 +199,7 @@ elf_getarsym (Elf *elf, size_t *ptr) { void *file_data; /* unit32_t[n] or uint64_t[n] */ char *str_data; + char *str_end; size_t sz = n * w; if (elf->map_address == NULL) @@ -238,6 +239,7 @@ elf_getarsym (Elf *elf, size_t *ptr) } str_data = (char *) new_str; + str_end = new_str + (index_size - sz); } else { @@ -254,6 +256,7 @@ elf_getarsym (Elf *elf, size_t *ptr) file_data = memcpy (temp_data, elf->map_address + off, sz); } str_data = (char *) (elf->map_address + off + sz); + str_end = (char *) (elf->map_address + off + index_size - w); } /* Now we can build the data structure. */ @@ -291,16 +294,25 @@ elf_getarsym (Elf *elf, size_t *ptr) else arsym[cnt].as_off = (*u32)[cnt]; + /* The symbol name must be NUL terminated within the string + table. Otherwise the archive symbol table is corrupt and + hashing or scanning the name would read out of bounds. */ + char *endp = (str_data < str_end + ? memchr (str_data, '\0', str_end - str_data) + : NULL); + if (unlikely (endp == NULL)) + { + if (elf->map_address == NULL) + { + free (elf->state.ar.ar_sym); + elf->state.ar.ar_sym = NULL; + } + __libelf_seterrno (ELF_E_INVALID_ARCHIVE); + goto out; + } + arsym[cnt].as_hash = _dl_elf_hash (str_data); -#if HAVE_DECL_RAWMEMCHR - str_data = rawmemchr (str_data, '\0') + 1; -#else - char c; - do { - c = *str_data; - str_data++; - } while (c); -#endif + str_data = endp + 1; } /* At the end a special entry. */ -- 2.47.3