git: 4617a6cb82a6 - main - nlist: Handle multiple symbol tables
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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);
}