From patchwork Mon May 21 20:34:39 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Pluzhnikov X-Patchwork-Id: 27372 Received: (qmail 119713 invoked by alias); 21 May 2018 20:35:12 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 119704 invoked by uid 89); 21 May 2018 20:35:11 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-32.6 required=5.0 tests=AWL, BAYES_00, ENV_AND_HDR_SPF_MATCH, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_PASS, USER_IN_DEF_SPF_WL autolearn=ham version=3.3.2 spammy= X-HELO: mail-wm0-f51.google.com X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=geW63eQNzfxY4aW5y0vhbz/MHCUXGmornwqK+907ZBE=; b=gtU7d1K/MzKX2NCsOsPVw59p1eFmNHGnsxyySBBnJ7ZxJT7lkrTuGmx38yf5qEzbLM l97hBCBoti9QkyueoT8KwsX3irNwF/6Ugp28S0cPFtFvwUD024eBI1Exd99bIuMG/bFs ZTZztrluTIOtEYUHDVhMiLPiscMDJK7fSpiAAPAVH9CdrPwdKNXWyW9FY/nx5UAVmaC+ ZY3IivehUBzXhpVjXg90Jcg9BAW4BXp908BaFE31dXDLI4EH0rbO8dzcCESJjcs3sDIZ qzR0RdWyyEVfYe/PUXp7vqyKZcLfGh/s0XleOXwMBOze0U4zPbMN5/zSqHJAe4mOMutK U0xg== X-Gm-Message-State: ALKqPwfNIEkg/eYkww5xiTkn9RZfFPg/URzobQ6ShqswAIUXd8kuAh6h NlRaHSx+dw/1Obno9YMujUH/VtJsN9fIYU3josuCPnFZW08= X-Google-Smtp-Source: AB8JxZqaAiWrNafI3GJFlOwjd46Cuofq6Jbs1PlWhzn/8gyxi44D76FHF81XJzle4ohzAG1+pOCXGFFkvsHIjKiD4B4= X-Received: by 2002:a1c:5c11:: with SMTP id q17-v6mr235482wmb.24.1526934906040; Mon, 21 May 2018 13:35:06 -0700 (PDT) MIME-Version: 1.0 From: Paul Pluzhnikov Date: Mon, 21 May 2018 13:34:39 -0700 Message-ID: Subject: [patch] Fix BZ 23187 -- stack overflow for many Phdr[]s To: GLIBC Devel Greetings, Attached patch fixes several instances of unconstrained stack allocation, which causes ld.so to overflow stack and crash while processing DSO with 1000s of Phdr[]s, and adds a test case. 2018-05-21 Paul Pluzhnikov [BZ #23187] * elf/Makefile (tst-many-phdrs): New test. * elf/tst-many-phdrs.c: New. * elf/dl-load.c (_dl_map_object_from_fd): Constrain stack allocation. (open_verify): Likewise. 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