git: 4617a6cb82a6 - main - nlist: Handle multiple symbol tables

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Tue, 19 May 2026 06:53:38 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=4617a6cb82a673b02257257c1f5f8a3c8d2bb943

commit 4617a6cb82a673b02257257c1f5f8a3c8d2bb943
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-05-19 06:52:53 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-05-19 06:53:35 +0000

    nlist: Handle multiple symbol tables
    
    * Instead of looking for and stopping at the first SHT_SYMTAB section,
      iterate over all SHT_DYNSYM and SHT_SYMTAB sections until we've either
      found all our symbols or run out.
    
    * Perform bounds checks on section and string table offsets and sizes
      before attempting to mmap() the string table.
    
    * Perform bounds checks on individual symbol table entries before
      attempting to access the corresponding strings.
    
    * Stop treating _Foo and Foo as the same symbol.
    
    This unbreaks OpenSSH which uses nlist(3) to verify PKCS#11 providers.
    
    PR:             295336
    MFC after:      1 week
    Fixes:          77909f597881 ("Initial elf nlist support [...]")
    Fixes:          644b4646c7ac ("OpenSSH: Update to 10.1p1")
    Reviewed by:    kib, emaste
    Differential Revision:  https://reviews.freebsd.org/D57034
---
 lib/libc/gen/nlist.3 |   8 +--
 lib/libc/gen/nlist.c | 150 ++++++++++++++++++++++++++++-----------------------
 2 files changed, 87 insertions(+), 71 deletions(-)

diff --git a/lib/libc/gen/nlist.3 b/lib/libc/gen/nlist.3
index 6aefd95e3b51..4912180e9bcd 100644
--- a/lib/libc/gen/nlist.3
+++ b/lib/libc/gen/nlist.3
@@ -25,7 +25,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 18, 2026
+.Dd May 19, 2026
 .Dt NLIST 3
 .Os
 .Sh NAME
@@ -45,9 +45,11 @@ Its use is discouraged.
 The
 .Fn nlist
 function
-retrieves name list entries from the
+retrieves name list entries from
 .Xr elf 5
-section with type
+sections with type
+.Dv SHT_DYNSYM
+or
 .Dv SHT_SYMTAB
 in an ELF object (for example, an executable file or shared library).
 The argument
diff --git a/lib/libc/gen/nlist.c b/lib/libc/gen/nlist.c
index ebf4ae92641a..06034dc8ee1a 100644
--- a/lib/libc/gen/nlist.c
+++ b/lib/libc/gen/nlist.c
@@ -36,8 +36,9 @@
 #include <sys/file.h>
 #include <arpa/inet.h>
 
-#include <errno.h>
 #include <a.out.h>
+#include <errno.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
@@ -85,6 +86,8 @@ __fdnlist(int fd, struct nlist *list)
 
 #define	ISLAST(p)	(p->n_un.n_name == 0 || p->n_un.n_name[0] == 0)
 
+static int elf_scan_symtab(Elf_Shdr *, int, int, off_t, size_t, char *, size_t,
+    struct nlist *, int);
 static void elf_sym_to_nlist(struct nlist *, Elf_Sym *, Elf_Shdr *, int);
 
 /*
@@ -121,22 +124,19 @@ int
 __elf_fdnlist(int fd, struct nlist *list)
 {
 	struct nlist *p;
-	Elf_Off symoff = 0, symstroff = 0;
-	Elf_Size symsize = 0, symstrsize = 0;
-	Elf_Ssize cc, i;
+	Elf_Off symoff = 0, stroff = 0;
+	Elf_Size symsize = 0, strsize = 0;
+	Elf_Ssize i;
 	int nent = -1;
 	int errsave;
-	Elf_Sym sbuf[1024];
-	Elf_Sym *s;
 	Elf_Ehdr ehdr;
-	char *strtab = NULL;
-	Elf_Shdr *shdr = NULL;
+	Elf_Shdr *shdr;
 	Elf_Size shdr_size;
 	void *base;
 	struct stat st;
 
 	/* Make sure obj is OK */
-	if (lseek(fd, (off_t)0, SEEK_SET) == -1 ||
+	if (lseek(fd, 0, SEEK_SET) == -1 ||
 	    _read(fd, &ehdr, sizeof(Elf_Ehdr)) != sizeof(Elf_Ehdr) ||
 	    !__elf_is_okay__(&ehdr) ||
 	    _fstat(fd, &st) < 0)
@@ -158,39 +158,6 @@ __elf_fdnlist(int fd, struct nlist *list)
 		return (-1);
 	shdr = (Elf_Shdr *)base;
 
-	/*
-	 * Find the symbol table entry and it's corresponding
-	 * string table entry.	Version 1.1 of the ABI states
-	 * that there is only one symbol table but that this
-	 * could change in the future.
-	 */
-	for (i = 0; i < ehdr.e_shnum; i++) {
-		if (shdr[i].sh_type == SHT_SYMTAB) {
-			symoff = shdr[i].sh_offset;
-			symsize = shdr[i].sh_size;
-			symstroff = shdr[shdr[i].sh_link].sh_offset;
-			symstrsize = shdr[shdr[i].sh_link].sh_size;
-			break;
-		}
-	}
-
-	/* Check for files too large to mmap. */
-	if (symstrsize > SIZE_T_MAX) {
-		errno = EFBIG;
-		goto done;
-	}
-	/*
-	 * Map string table into our address space.  This gives us
-	 * an easy way to randomly access all the strings, without
-	 * making the memory allocation permanent as with malloc/free
-	 * (i.e., munmap will return it to the system).
-	 */
-	base = mmap(NULL, (size_t)symstrsize, PROT_READ, MAP_PRIVATE, fd,
-	    (off_t)symstroff);
-	if (base == MAP_FAILED)
-		goto done;
-	strtab = (char *)base;
-
 	/*
 	 * clean out any left-over information for all valid entries.
 	 * Type and value defined to be 0 if not found; historical
@@ -210,46 +177,93 @@ __elf_fdnlist(int fd, struct nlist *list)
 		++nent;
 	}
 
-	/* Don't process any further if object is stripped. */
-	if (symoff == 0)
-		goto done;
-		
-	if (lseek(fd, (off_t) symoff, SEEK_SET) == -1) {
-		nent = -1;
-		goto done;
+	/*
+	 * Find the symbol table entry and it's corresponding
+	 * string table entry.	Version 1.1 of the ABI states
+	 * that there is only one symbol table but that this
+	 * could change in the future.
+	 */
+	for (i = 0; nent > 0 && i < ehdr.e_shnum; i++) {
+		if (shdr[i].sh_type != SHT_SYMTAB &&
+		    shdr[i].sh_type != SHT_DYNSYM)
+			continue;
+		symoff = shdr[i].sh_offset;
+		symsize = shdr[i].sh_size;
+		stroff = shdr[shdr[i].sh_link].sh_offset;
+		strsize = shdr[shdr[i].sh_link].sh_size;
+
+		/*
+		 * Skip this section if it or its string table is empty or
+		 * extends beyond the end of the file, or if the string
+		 * table is too large to map into memory.
+		 */
+		if (symoff == 0 || symsize == 0 ||
+		    symsize > SIZE_MAX - symoff ||
+		    symoff + symsize > st.st_size ||
+		    stroff == 0 || strsize == 0 ||
+		    strsize > SIZE_MAX - stroff ||
+		    stroff + strsize > st.st_size) {
+			errno = ENOENT;
+			continue;
+		}
+
+		/*
+		 * Map string table into our address space.  This gives us
+		 * an easy way to randomly access all the strings, without
+		 * making the memory allocation permanent as with
+		 * malloc/free (i.e., munmap will return it to the
+		 * system).
+		 */
+		base = mmap(NULL, (size_t)strsize, PROT_READ,
+		    MAP_PRIVATE, fd, (off_t)stroff);
+		if (base == MAP_FAILED)
+			continue;
+
+		nent = elf_scan_symtab(shdr, ehdr.e_shnum, fd, symoff, symsize,
+		    base, strsize, list, nent);
+
+		errsave = errno;
+		munmap(base, strsize);
+		errno = errsave;
 	}
+	errsave = errno;
+	munmap(shdr, shdr_size);
+	errno = errsave;
+	return (nent);
+}
+
+static int
+elf_scan_symtab(Elf_Shdr *shdr, int shnum, int fd, off_t symoff, size_t symsize,
+    char *strtab, size_t strsize, struct nlist *list, int nent)
+{
+	Elf_Sym sbuf[1024];
+	Elf_Sym *s;
+	char *name;
+	struct nlist *p;
+	Elf_Ssize cc;
+	size_t slen;
 
+	if (lseek(fd, symoff, SEEK_SET) == -1)
+		return (-1);
 	while (symsize > 0 && nent > 0) {
 		cc = MIN(symsize, sizeof(sbuf));
 		if (_read(fd, sbuf, cc) != cc)
 			break;
 		symsize -= cc;
 		for (s = sbuf; cc > 0 && nent > 0; ++s, cc -= sizeof(*s)) {
-			char *name;
-			struct nlist *p;
-
+			if (s->st_name >= strsize)
+				continue;
 			name = strtab + s->st_name;
 			if (name[0] == '\0')
 				continue;
-			for (p = list; !ISLAST(p); p++) {
-				if ((p->n_un.n_name[0] == '_' &&
-				    strcmp(name, p->n_un.n_name+1) == 0)
-				    || strcmp(name, p->n_un.n_name) == 0) {
-					elf_sym_to_nlist(p, s, shdr,
-					    ehdr.e_shnum);
-					if (--nent <= 0)
-						break;
-				}
+			slen = strnlen(name, strsize - s->st_name);
+			for (p = list; nent > 0 && !ISLAST(p); p++) {
+				if (strncmp(name, p->n_un.n_name, slen) == 0 &&
+				    p->n_un.n_name[slen] == '\0')
+					elf_sym_to_nlist(p, s, shdr, shnum);
 			}
 		}
 	}
-  done:
-	errsave = errno;
-	if (strtab != NULL)
-		munmap(strtab, symstrsize);
-	if (shdr != NULL)
-		munmap(shdr, shdr_size);
-	errno = errsave;
 	return (nent);
 }