Received: by 2002:a25:c593:0:0:0:0:0 with SMTP id v141csp1145047ybe; Wed, 4 Sep 2019 13:23:57 -0700 (PDT) X-Google-Smtp-Source: APXvYqyPQDmHKnw9nAZFgHMSP1ghNIAlqS86GxR9wpeJBr8BD3oNvamuZsmy+HiVs7/cVCnWhAR6 X-Received: by 2002:a17:902:9a8f:: with SMTP id w15mr14092444plp.221.1567628636954; Wed, 04 Sep 2019 13:23:56 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1567628636; cv=none; d=google.com; s=arc-20160816; b=R8ytnnRp16ss+FWDuSnKl/XBpT6VxVU3xzWi1RdhbzQUpQyf7gCpoaMsrs3fmpSY9o W/5kKger6JX3VKjAfIdZ+FTUaD8wCpsDVNoMzVnpnPzV5JK4uZ21R1d0ey6T/A92U2oa uc3IATwEjpaH75NrHgCD7XDaw1OBPlnVhaz30loPFGlK79rckKxIUPHgeXxleONrzOzp Yz1krDkgNEt9DCXa0n2+FoYbgXcuqD/JRd0x0EMkwrNK4PDn497Eyi5a62kUuRJMrQFg xdq4bLvLfidiR7um6om37DuzQn1b3C/FTBmAYqu9XWOhyjkh8PsKXtf8WrCmn99iaUpp FfeA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=YmkRvKCkF9gRGsmDIF9TzYSt+ar/I7LPzjxSh0G/0W0=; b=AYUvIDX66NJHa7ANFD1OPeRwRYFyIZE2XPqvRJZ1iTTmG7TM94biWqhcop4y7dgdf9 Cr8F7c1CnL6DpOeOHEm4hu+yO6QzMn+7vDFz5UIyAV+RL1wSq6qxtJ61it8Ceqi35Oy2 tJWhVDmHCKoREPboCTvJJ03XAmozPrkkPS/jH/HH0St7f3xU2S3oSEgTSUSMMpE9A+dW bS5McZE5VV3ZARLOM7cOXUBdTfniX0h+a0n1e3tWzMoNSA4Bxrg2LnMO8mxsTFuZfF6i U+WPg0jqM9h/kAE9IXp3PJCtNMqz3Smg88uCh9JfxCp4sSe8ZKemKn0wME4hlreqimj6 itgg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id t18si74146pjr.102.2019.09.04.13.23.40; Wed, 04 Sep 2019 13:23:56 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730684AbfIDUWz (ORCPT + 99 others); Wed, 4 Sep 2019 16:22:55 -0400 Received: from mx2.mailbox.org ([80.241.60.215]:11172 "EHLO mx2.mailbox.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729803AbfIDUWx (ORCPT ); Wed, 4 Sep 2019 16:22:53 -0400 Received: from smtp2.mailbox.org (smtp2.mailbox.org [80.241.60.241]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by mx2.mailbox.org (Postfix) with ESMTPS id 72978A1578; Wed, 4 Sep 2019 22:22:48 +0200 (CEST) X-Virus-Scanned: amavisd-new at heinlein-support.de Received: from smtp2.mailbox.org ([80.241.60.241]) by spamfilter05.heinlein-hosting.de (spamfilter05.heinlein-hosting.de [80.241.56.123]) (amavisd-new, port 10030) with ESMTP id 7Ji_x5Jhefr0; Wed, 4 Sep 2019 22:22:44 +0200 (CEST) From: Aleksa Sarai To: Al Viro , Jeff Layton , "J. Bruce Fields" , Arnd Bergmann , David Howells , Shuah Khan , Shuah Khan , Ingo Molnar , Peter Zijlstra , Christian Brauner Cc: Aleksa Sarai , Eric Biederman , Andy Lutomirski , Andrew Morton , Alexei Starovoitov , Kees Cook , Jann Horn , Tycho Andersen , David Drysdale , Chanho Min , Oleg Nesterov , Rasmus Villemoes , Alexander Shishkin , Jiri Olsa , Namhyung Kim , Aleksa Sarai , Linus Torvalds , containers@lists.linux-foundation.org, linux-alpha@vger.kernel.org, linux-api@vger.kernel.org, linux-arch@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-fsdevel@vger.kernel.org, linux-ia64@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-m68k@lists.linux-m68k.org, linux-mips@vger.kernel.org, linux-parisc@vger.kernel.org, linuxppc-dev@lists.ozlabs.org, linux-s390@vger.kernel.org, linux-sh@vger.kernel.org, linux-xtensa@linux-xtensa.org, sparclinux@vger.kernel.org Subject: [PATCH v12 09/12] namei: LOOKUP_IN_ROOT: chroot-like path resolution Date: Thu, 5 Sep 2019 06:19:30 +1000 Message-Id: <20190904201933.10736-10-cyphar@cyphar.com> In-Reply-To: <20190904201933.10736-1-cyphar@cyphar.com> References: <20190904201933.10736-1-cyphar@cyphar.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The primary motivation for the need for this flag is container runtimes which have to interact with malicious root filesystems in the host namespaces. One of the first requirements for a container runtime to be secure against a malicious rootfs is that they correctly scope symlinks (that is, they should be scoped as though they are chroot(2)ed into the container's rootfs) and ".."-style paths[*]. The already-existing LOOKUP_NO_XDEV and LOOKUP_NO_MAGICLINKS help defend against other potential attacks in a malicious rootfs scenario. Currently most container runtimes try to do this resolution in userspace[1], causing many potential race conditions. In addition, the "obvious" alternative (actually performing a {ch,pivot_}root(2)) requires a fork+exec (for some runtimes) which is *very* costly if necessary for every filesystem operation involving a container. [*] At the moment, ".." and magic-link jumping are disallowed for the same reason it is disabled for LOOKUP_BENEATH -- currently it is not safe to allow it. Future patches may enable it unconditionally once we have resolved the possible races (for "..") and semantics (for magic-link jumping). The most significant *at(2) semantic change with LOOKUP_IN_ROOT is that absolute pathnames no longer cause the dirfd to be ignored completely. The rationale is that LOOKUP_IN_ROOT must necessarily chroot-scope symlinks with absolute paths to dirfd, and so doing it for the base path seems to be the most consistent behaviour (and also avoids foot-gunning users who want to scope paths that are absolute). [1]: https://github.com/cyphar/filepath-securejoin Signed-off-by: Aleksa Sarai --- fs/namei.c | 41 +++++++++++++++++++++++++++++++---------- include/linux/namei.h | 1 + 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 2e18ce5a313e..0352d275bd13 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -676,7 +676,7 @@ static int unlazy_walk(struct nameidata *nd) goto out1; if (!nd->root.mnt) { /* Restart from path_init() if nd->root was cleared. */ - if (nd->flags & LOOKUP_BENEATH) + if (nd->flags & (LOOKUP_BENEATH | LOOKUP_IN_ROOT)) goto out; } else if (!(nd->flags & LOOKUP_ROOT)) { if (unlikely(!legitimize_path(nd, &nd->root, nd->root_seq))) @@ -809,10 +809,18 @@ static int complete_walk(struct nameidata *nd) return status; } -static void set_root(struct nameidata *nd) +static int set_root(struct nameidata *nd) { struct fs_struct *fs = current->fs; + /* + * Jumping to the real root as part of LOOKUP_IN_ROOT is a BUG in namei, + * but we still have to ensure it doesn't happen because it will cause a + * breakout from the dirfd. + */ + if (WARN_ON(nd->flags & LOOKUP_IN_ROOT)) + return -ENOTRECOVERABLE; + if (nd->flags & LOOKUP_RCU) { unsigned seq; @@ -824,6 +832,7 @@ static void set_root(struct nameidata *nd) } else { get_fs_root(fs, &nd->root); } + return 0; } static void path_put_conditional(struct path *path, struct nameidata *nd) @@ -854,6 +863,11 @@ static int nd_jump_root(struct nameidata *nd) if (nd->path.mnt != NULL && nd->path.mnt != nd->root.mnt) return -EXDEV; } + if (!nd->root.mnt) { + int error = set_root(nd); + if (error) + return error; + } if (nd->flags & LOOKUP_RCU) { struct dentry *d; nd->path = nd->root; @@ -1100,15 +1114,13 @@ const char *get_link(struct nameidata *nd) if (unlikely(nd->flags & LOOKUP_NO_MAGICLINKS)) return ERR_PTR(-ELOOP); /* Not currently safe. */ - if (unlikely(nd->flags & LOOKUP_BENEATH)) + if (unlikely(nd->flags & (LOOKUP_BENEATH | LOOKUP_IN_ROOT))) return ERR_PTR(-EXDEV); } if (IS_ERR_OR_NULL(res)) return res; } if (*res == '/') { - if (!nd->root.mnt) - set_root(nd); error = nd_jump_root(nd); if (unlikely(error)) return ERR_PTR(error); @@ -1744,15 +1756,20 @@ static inline int may_lookup(struct nameidata *nd) static inline int handle_dots(struct nameidata *nd, int type) { if (type == LAST_DOTDOT) { + int error = 0; + /* * LOOKUP_BENEATH resolving ".." is not currently safe -- races * can cause our parent to have moved outside of the root and * us to skip over it. */ - if (unlikely(nd->flags & LOOKUP_BENEATH)) + if (unlikely(nd->flags & (LOOKUP_BENEATH | LOOKUP_IN_ROOT))) return -EXDEV; - if (!nd->root.mnt) - set_root(nd); + if (!nd->root.mnt) { + error = set_root(nd); + if (error) + return error; + } if (nd->flags & LOOKUP_RCU) { return follow_dotdot_rcu(nd); } else @@ -2251,9 +2268,13 @@ static const char *path_init(struct nameidata *nd, unsigned flags) nd->m_seq = read_seqbegin(&mount_lock); + /* LOOKUP_IN_ROOT treats absolute paths as being relative-to-dirfd. */ + if (flags & LOOKUP_IN_ROOT) + while (*s == '/') + s++; + /* Figure out the starting path and root (if needed). */ if (*s == '/') { - set_root(nd); error = nd_jump_root(nd); if (unlikely(error)) return ERR_PTR(error); @@ -2298,7 +2319,7 @@ static const char *path_init(struct nameidata *nd, unsigned flags) fdput(f); } /* For scoped-lookups we need to set the root to the dirfd as well. */ - if (flags & LOOKUP_BENEATH) { + if (flags & (LOOKUP_BENEATH | LOOKUP_IN_ROOT)) { nd->root = nd->path; if (flags & LOOKUP_RCU) nd->root_seq = nd->seq; diff --git a/include/linux/namei.h b/include/linux/namei.h index be407415c28a..ec2c6c588ea7 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -57,6 +57,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; #define LOOKUP_NO_MAGICLINKS 0x080000 /* No /proc/$pid/fd/ "symlink" crossing. */ #define LOOKUP_NO_SYMLINKS 0x100000 /* No symlink crossing *at all*. Implies LOOKUP_NO_MAGICLINKS. */ +#define LOOKUP_IN_ROOT 0x200000 /* Treat dirfd as %current->fs->root. */ extern int path_pts(struct path *path); -- 2.23.0