diff --git a/elf/Makefile b/elf/Makefile
index 2dcd2b88e0..eeaecd4f30 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -186,7 +186,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \
tst-tlsalign tst-tlsalign-extern tst-nodelete-opened \
tst-nodelete2 tst-audit11 tst-audit12 tst-dlsym-error tst-noload \
tst-latepthread tst-tls-manydynamic tst-nodelete-dlclose \
- tst-debug1 tst-main1 tst-absolute-sym tst-big-note
+ tst-debug1 tst-main1 tst-absolute-sym tst-big-note tst-many-phdrs
# reldep9
tests-internal += loadtest unload unload2 circleload1 \
neededtest neededtest2 neededtest3 neededtest4 \
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 431236920f..a551d97b43 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -809,6 +809,8 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
const ElfW(Ehdr) *header;
const ElfW(Phdr) *phdr;
const ElfW(Phdr) *ph;
+ ElfW(Phdr) *phdr_malloced = NULL;
+ struct loadcmd *loadcmds_malloced = NULL;
size_t maplength;
int type;
/* Initialize to keep the compiler happy. */
@@ -827,6 +829,10 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
call_lose:
lose (errval, fd, name, realname, l, errstring,
make_consistent ? r : NULL, nsid);
+ free (phdr_malloced);
+ free (loadcmds_malloced);
+ phdr_malloced = NULL;
+ loadcmds_malloced = NULL;
}
/* Look again to see if the real name matched another already loaded. */
@@ -960,7 +966,18 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
phdr = (void *) (fbp->buf + header->e_phoff);
else
{
- phdr = alloca (maplength);
+ if (maplength < __MAX_ALLOCA_CUTOFF)
+ phdr = alloca (maplength);
+ else
+ {
+ phdr_malloced = malloc (maplength);
+ if (phdr_malloced == NULL)
+ {
+ errstring = N_("cannot allocate memory for program header");
+ goto call_lose_errno;
+ }
+ phdr = phdr_malloced;
+ }
__lseek (fd, header->e_phoff, SEEK_SET);
if ((size_t) __libc_read (fd, (void *) phdr, maplength) != maplength)
{
@@ -976,10 +993,24 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
{
/* Scan the program header table, collecting its load commands. */
- struct loadcmd loadcmds[l->l_phnum];
+ struct loadcmd *loadcmds;
size_t nloadcmds = 0;
+ const size_t loadcmds_size = sizeof (*loadcmds) * l->l_phnum;
bool has_holes = false;
+ if (loadcmds_size < __MAX_ALLOCA_CUTOFF)
+ loadcmds = alloca (loadcmds_size);
+ else
+ {
+ loadcmds_malloced = malloc (loadcmds_size);
+ if (loadcmds_malloced == NULL)
+ {
+ errstring = N_("cannot allocate memory for program header");
+ goto call_lose_errno;
+ }
+ loadcmds = loadcmds_malloced;
+ }
+
/* The struct is initialized to zero so this is not necessary:
l->l_ld = 0;
l->l_phdr = 0;
@@ -1126,6 +1157,11 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
maplength, has_holes, loader);
if (__glibc_unlikely (errstring != NULL))
goto call_lose;
+
+ free (phdr_malloced);
+ free (loadcmds_malloced);
+ phdr_malloced = NULL;
+ loadcmds_malloced = NULL;
}
if (l->l_ld == 0)
@@ -1460,7 +1496,7 @@ open_verify (const char *name, int fd,
if (fd != -1)
{
ElfW(Ehdr) *ehdr;
- ElfW(Phdr) *phdr, *ph;
+ ElfW(Phdr) *phdr, *ph, *phdr_malloced = NULL;
ElfW(Word) *abi_note;
ElfW(Word) *abi_note_malloced = NULL;
unsigned int osversion;
@@ -1596,7 +1632,18 @@ open_verify (const char *name, int fd,
phdr = (void *) (fbp->buf + ehdr->e_phoff);
else
{
- phdr = alloca (maplength);
+ if (maplength < __MAX_ALLOCA_CUTOFF)
+ phdr = alloca (maplength);
+ else
+ {
+ phdr_malloced = malloc (maplength);
+ if (phdr_malloced == NULL)
+ {
+ errstring = N_("cannot allocate memory for program header");
+ goto call_lose;
+ }
+ phdr = phdr_malloced;
+ }
__lseek (fd, ehdr->e_phoff, SEEK_SET);
if ((size_t) __libc_read (fd, (void *) phdr, maplength) != maplength)
{
@@ -1687,6 +1734,7 @@ open_verify (const char *name, int fd,
break;
}
+ free (phdr_malloced);
free (abi_note_malloced);
}
diff --git a/elf/tst-many-phdrs.c b/elf/tst-many-phdrs.c
new file mode 100644
index 0000000000..ea8b0a4ec5
--- /dev/null
+++ b/elf/tst-many-phdrs.c
@@ -0,0 +1,170 @@
+/* Copyright (C) 2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ . */
+
+/* A test that generates DSO with 65535 Phdr[]s, and asks ld.so
+ to list its dependencies (after adjusting stack limits).
+
+ This is used to test for stack overflow in dl-load.c */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static const char *
+get_ld_so (void)
+{
+ struct link_map *lm = _r_debug.r_map;
+ while (lm != NULL)
+ {
+ if (lm->l_name != NULL && strstr (lm->l_name, "elf/ld") != NULL)
+ return lm->l_name;
+ lm = lm->l_next;
+ }
+ FAIL_EXIT1 ("Did not find ld.so");
+}
+
+static const char *
+write_dso (void)
+{
+ ElfW(Ehdr) ehdr;
+ ElfW(Phdr) phdr;
+ ElfW(Dyn) dyn;
+ const char *const ld_so = get_ld_so ();
+
+ FILE *fp = fopen (ld_so, "rb");
+ if (fp == NULL)
+ FAIL_EXIT1 ("fopen %s: %m", ld_so);
+
+ /* Use Ehdr from ld.so so we don't have to depend on specific e_machine
+ and e_ident[EI_DATA] -- we simply use the same values as ld.so. */
+ if (fread (&ehdr, sizeof (ehdr), 1, fp) != 1)
+ FAIL_EXIT1 ("fread: %m");
+ fclose (fp);
+
+ static char fname[] = "/tmp/tst-many-phdrs-XXXXXX";
+ int fd = mkstemp (fname);
+ if (fd == -1)
+ FAIL_EXIT1 ("mkstemp failed: %m");
+
+ fp = fdopen (fd, "wb");
+ if (fp == NULL)
+ FAIL_EXIT1 ("fdopen failed: %m");
+
+ ehdr.e_phoff = sizeof (ehdr) + sizeof (dyn);
+ ehdr.e_phentsize = sizeof (phdr);
+ ehdr.e_phnum = 0xFFFF; /* Max possible number of Phdr[]s. */
+
+ if (1 != fwrite (&ehdr, sizeof (ehdr), 1, fp))
+ FAIL_EXIT1 ("fwrite ehdr: %m");
+
+ /* Write a single Dyn DT_NUL entry. That isn't enough to make this a
+ valid DSO (dynstr and dynsym and hash are also necessary), but is enough
+ to trigger stack overflow when listing its dependencies. */
+ memset (&dyn, 0, sizeof (dyn));
+ if (1 != fwrite (&dyn, sizeof (dyn), 1, fp))
+ FAIL_EXIT1 ("fwrite dyn: %m");
+
+ /* Add PT_DYNAMIC pointing to the Dyn entry we just wrote. */
+ memset (&phdr, 0, sizeof (phdr));
+ phdr.p_offset = sizeof (ehdr);
+ phdr.p_vaddr = sizeof (ehdr);
+ phdr.p_paddr = sizeof (ehdr);
+ phdr.p_filesz = sizeof (dyn);
+ phdr.p_memsz = sizeof (dyn);
+ phdr.p_flags = PF_R;
+ phdr.p_type = PT_DYNAMIC;
+
+ if (1 != fwrite (&phdr, sizeof (phdr), 1, fp))
+ FAIL_EXIT1 ("fwrite phdr: %m");
+
+ /* Now the rest of Phdr[]s. */
+ memset (&phdr, 0, sizeof (phdr));
+ phdr.p_offset = 0;
+ phdr.p_filesz = sizeof (ehdr);
+ phdr.p_memsz = sizeof (ehdr);
+ phdr.p_flags = PF_R;
+
+ for (int j = 1; j < ehdr.e_phnum; j++)
+ {
+ /* At least one PT_LOAD is required. */
+ phdr.p_type = j == 1 ? PT_LOAD : PT_NOTE;
+ if (1 != fwrite (&phdr, sizeof (phdr), 1, fp))
+ FAIL_EXIT1 ("fwrite phdr: %m");
+ }
+
+ fclose (fp);
+ return fname;
+}
+
+static int
+do_test (int argc, char *argv[])
+{
+ const char *const dso = write_dso ();
+ const char *const ld_so = get_ld_so ();
+ struct rlimit rl;
+
+ if (getrlimit (RLIMIT_STACK, &rl) == -1)
+ FAIL_EXIT1 ("getrlimit (RLIMIT_STACK): %m");
+
+ char cmd[4 * 4096];
+ int n = snprintf (cmd, sizeof (cmd), "%s --list %s", ld_so, dso);
+ if (n >= sizeof (cmd))
+ FAIL_EXIT1 ("buffer too small: need %d bytes", n);
+
+ printf ("cmd: %s\n", cmd);
+
+ if (rl.rlim_cur == RLIM_INFINITY)
+ {
+ printf ("Capping stack at 8MiB\n");
+ rl.rlim_cur = 8 << 20;
+ }
+
+ while (rl.rlim_cur >= 128 * 1024)
+ {
+ printf ("Stack size: %zu\n", (size_t) rl.rlim_cur);
+ int rc = system (cmd);
+
+ if (rc != 0)
+ {
+ /* Clean up. Comment this out to debug ld.so. */
+ unlink (dso);
+
+ if (WIFEXITED (rc))
+ FAIL_EXIT1 ("child exited with %d", WEXITSTATUS (rc));
+ else if (WIFSIGNALED (rc))
+ FAIL_EXIT1 ("child exited with signal %d", WTERMSIG (rc));
+ else
+ FAIL_EXIT1 ("child exited with rc %d", rc);
+ }
+
+ rl.rlim_cur /= 2;
+ if (setrlimit (RLIMIT_STACK, &rl) == -1)
+ FAIL_EXIT1 ("setrlimit: %m");
+ }
+
+ unlink (dso);
+ return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include