From patchwork Mon Jul 19 19:33:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jordan Abrahams X-Patchwork-Id: 44425 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id CA493396E47F for ; Mon, 19 Jul 2021 19:34:42 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org CA493396E47F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1626723282; bh=qT1QkVWcWm93woUG8C4yxcfohtqOam5ZcgQD6PF1sAM=; h=Date:Subject:To:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:Cc:From; b=uZNm2yhNxoARa6qxMsdpePNoc8rxDk4MIHiBw0QvMyN8SNd8J3E9gMrPXozReQUj+ mgn42uVRi9nrq9zdPNje/i9nDjT61q+EkA+7QYiMQFd+jHW0AHx1R/TLLc4sYguCDh ZwDUShrpx0w/ixfdH8B2BZFTaM/oDDD1owNDZLoM= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by sourceware.org (Postfix) with ESMTPS id 9CE5B3857418 for ; Mon, 19 Jul 2021 19:34:20 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 9CE5B3857418 Received: by mail-yb1-xb49.google.com with SMTP id v184-20020a257ac10000b02904f84a5c5297so26907884ybc.16 for ; Mon, 19 Jul 2021 12:34:20 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:message-id:mime-version:subject:from:to:cc :content-transfer-encoding; bh=qT1QkVWcWm93woUG8C4yxcfohtqOam5ZcgQD6PF1sAM=; b=G6JuBksf09UpKncIKRHOz5kiP+BV0r2HMGbGIHjx/WKCXOAUHQMVUm13ZrfsylwgKD jLyaB68ZcPYQWusDvWrvkEd1t0LME+Mm0fI4KjRiIDZWX2i9jPvsh/jz/MrH97BVJmsH iYYv+WjPkem82xgVev8az0DDlWwpIKtXVq3bwXMABL6Iz1YvOLsXV1Pg2TIu7Wd0dlG/ y8xPwesIN1gdDka3ZVzXickYStj6RNvl0aPLPNp3uJYaomDQSt9QnFajUpbxra+jATZn cx02qdULPDCN02d8aMFeJeBGHXxu8faZ5AxwYRNOFJ+JUr3ugHMZiDCRd9Bt+WLxMWtM 2L6Q== X-Gm-Message-State: AOAM533sfmlJCeTjcjv9vUnJZvu7H048Lmr2JrlWAQM+hk9aMhocz3yq SBz/0dWWbY4rfBUKMrd+Uo2jTGTt/g9ZkUX8cfPm44bk2DUl7OIO/EpAXEUoAzoJ/F/BIRTCWyf sytAjtd3z3McSgziBa9Sbn2q7PTZArZnm3ETGUKUYH2Z/rO3ycYYKgf7I70RcJGJXACkPv58= X-Google-Smtp-Source: ABdhPJw4cEFstrRZdmgMtpcVAVHXwXb1odfv1b0bTAi2xHBDFkrDFh0sIUpH7VaFUxyJKqO/IZv3oLZ++ZzlRA== X-Received: from chrysalis.c.googlers.com ([fda3:e722:ac3:cc00:7f:e700:c0a8:182b]) (user=ajordanr job=sendgmr) by 2002:a25:2b47:: with SMTP id r68mr33440951ybr.206.1626723260101; Mon, 19 Jul 2021 12:34:20 -0700 (PDT) Date: Mon, 19 Jul 2021 19:33:43 +0000 Message-Id: <20210719193343.208149-1-ajordanr@google.com> Mime-Version: 1.0 X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH] Deny preload of files on NOEXEC mounts To: libc-alpha@sourceware.org X-Spam-Status: No, score=-21.9 required=5.0 tests=BAYES_00, DKIMWL_WL_MED, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP, USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Jordan R Abrahams via Libc-alpha From: Jordan Abrahams Reply-To: Jordan R Abrahams Cc: llozano@google.com, ajordanr@google.com, manojgupta@google.com Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" I'm from Google's ChromeOS Toolchain team, and I have a security hardening patch to port upstream. I've confirmed with our security team that this patch can be public, and I'm looking for review and feedback. I'm a new contributor, but Google already has a CLA on file for the FSF, so it should not be an issue. If I'm missing any contribution info, please do let me know. Best regards, ~Jordan R Abrahams -- >8 -- This commit hardens a security flaw in dl-load.c. At present, one could dynamically load any shared object files even if they resided on a NOEXEC mount partition. This introduces an exploit where an attacker may get around this NOEXEC requirement by preloading a malicious shared library. For example, from shell this can look like: $ LD_PRELOAD='/tmp/malicious_lib.so' id Hello world from malicious_lib! Which will work even if /tmp/ is a noexec mount. A proof of concept of this exploit can be found here at https://bugs.chromium.org/p/chromium/issues/detail?id=1182687 The bug thread is restricted, but we are willing to add interested parties with the approval of our security team. This patch hardens against the exploit by checking the file descriptor if the file lies in a NOEXEC mount partition via an fstatvfs call. The error string is set, and we jump to the `lose` cleanup code. With this patch, the above example instead becomes: $ LD_PRELOAD='/tmp/malicious_lib.so' id /usr/bin/id: error while loading shared libraries: /tmp/malicious_lib.so: file not located on exec mount # <... normal stdout for id ...> Signed-off-by: Jordan R Abrahams --- elf/dl-load.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/elf/dl-load.c b/elf/dl-load.c index 650e4edc35..81ca08c38f 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -1572,6 +1573,20 @@ print_search_path (struct r_search_path_elem **list, _dl_debug_printf_c ("\t\t(%s)\n", what); } +/* Check if a the passed in file descriptor points to file on an executable mount. */ +static int +check_exec (int fd) +{ + struct statvfs buf; + int stated = fstatvfs (fd, &buf); + if (stated == 0) + { + return !(buf.f_flag & ST_NOEXEC); + } + /* Could not fstat the file. */ + return false; +} + /* Open a file and verify it is an ELF file for this architecture. We ignore only ELF files for other architectures. Non-ELF files and ELF files with different header information cause fatal errors since @@ -1650,8 +1665,8 @@ open_verify (const char *name, int fd, #endif if (fd == -1) - /* Open the file. We always open files read-only. */ - fd = __open64_nocancel (name, O_RDONLY | O_CLOEXEC); + /* Open the file. We always open files read-only. */ + fd = __open64_nocancel (name, O_RDONLY | O_CLOEXEC); if (fd != -1) { @@ -1667,6 +1682,14 @@ open_verify (const char *name, int fd, __set_errno (0); fbp->len = 0; assert (sizeof (fbp->buf) > sizeof (ElfW(Ehdr))); + + /* Before we read in the file, check if the file is in an exec mount */ + if (__glibc_unlikely (!check_exec(fd))) + { + errstring = N_("file not located on exec mount"); + goto call_lose; + } + /* Read in the header. */ do {