Received: by 2002:ac0:a582:0:0:0:0:0 with SMTP id m2-v6csp1313808imm; Thu, 4 Oct 2018 11:28:30 -0700 (PDT) X-Google-Smtp-Source: ACcGV607oAnDbuwYrVIhloyjOYXjOCTbXRmcHldTPqM2z5YTbcX3M+ItM06DPYrIbMN+0mCDUye0 X-Received: by 2002:a62:8559:: with SMTP id u86-v6mr8080512pfd.32.1538677710879; Thu, 04 Oct 2018 11:28:30 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1538677710; cv=none; d=google.com; s=arc-20160816; b=MuL/k3QCIyDr85fxAoVI4nXrLGvydvj1ZkTQb8MQ4YaLAl64MzF0HkIhTRZTnycaIN qkjv5CqvwvvGDjI37m80ZHQhkuaaieirZ41VQwTxrhbgnvM7j0cRkXbX2DFzSVUGhRFQ ZqHyvp0D9/dvdNUGaX5aMIsYiNOxlW3Z6wieKHwQ1HS9UO0Szy9lfiwMwO/N2Jx/F2bT YwN26HBTMFZNSPrU8f8T934VBp17yKYettnfwp/Q2F+uT937WvDUF/j7sP8cSkhRJaGV 8H9FUl47tTOddYLdcF4cNc12jhfXbVSazj5/TIHtnsTBWE8X/8q4ZBle73fcxpCrbn6W Fw6A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=IwrixsSDq41OQMOHGvtswGHKDpXaAnLPe7qKHutg+OA=; b=F1sDFj9Z7S3y5dDj9urn0m4w5Sfp98p+GunDEYTwEAHZp7vT+H0cp6MBqq79hjlazH KR6VHTpr5KjY3n4akDG8QYl22POIQALtAcOxnZJc0nXt/WxouPIlR9KpvxPZM5YWNlVH 8Q7mOe64zzTJ1ZRm5ECETolhhNoR4ld69QHQmxkS22snZr/gRM5/CkWZbpWt6yLnqBEI c7TPaqJH1L+ydXHGVtpNpPdfhuXPc7gpWkioIopp1kCy8lQ6jxFjOXUTEaXojQLN2Bjk ZDMQ3F/jWIvi8Dj80FcHfGkDFS4OEM+h21I86t253ub0fesXPuZBeQgnHPPIVsAIfzW+ zhdw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=BzP2W5kf; 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; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id a71-v6si6144050pfa.109.2018.10.04.11.28.13; Thu, 04 Oct 2018 11:28:30 -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; dkim=pass header.i=@google.com header.s=20161025 header.b=BzP2W5kf; 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; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728028AbeJEBVQ (ORCPT + 99 others); Thu, 4 Oct 2018 21:21:16 -0400 Received: from mail-ot1-f68.google.com ([209.85.210.68]:36045 "EHLO mail-ot1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727407AbeJEBVP (ORCPT ); Thu, 4 Oct 2018 21:21:15 -0400 Received: by mail-ot1-f68.google.com with SMTP id c18-v6so10182471otm.3 for ; Thu, 04 Oct 2018 11:26:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=IwrixsSDq41OQMOHGvtswGHKDpXaAnLPe7qKHutg+OA=; b=BzP2W5kfotBEkyRYzqTWKjqHV78B2YVqlC15zAUecdOIpSXGZtjgEHqqqMbgo9G4U+ b2nCqeMz2ra0hM/SwZs6CFNikhRqyUKeUnoVVSitkE6bsHPAUUrqIjoPdsVKigQJmx0f yrQh/156+Ygt3X6A4WBUARbXbr9rwekDx380SPODeCfrNqwYYd520tADP3wvxL52hQqP YdF3HWSNheQkQC8KNCaTKs7xDWWtpXJs2F+lBgsgctJaNoKXhtBN6/d8vyJx2SbqHQHQ wsp7515VvrNRTrOvVNDjDzp4EfURunjNeR/e/5Lm7sj98c+yonsr2WL5tMWBb1hAUEc5 /baw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=IwrixsSDq41OQMOHGvtswGHKDpXaAnLPe7qKHutg+OA=; b=WYqSXgpmAVAuz+m5riLQbllhqAVPki3iZpWJCS2bKX542qd4+eebQ41pR/bQpi/PaO ZpIYjpXIpmhIRkwXU8/fVEik0Rw4K9j9yoK4HHZwpmoih0ToEmuQ0x7UNiKHDN4tCufe uLfT0M6yQa+23SLpR/ADtz6UX7Q8nQ071kNBCm0PFs2IN5ubL1sfWtp7+MePntd9/w+0 WgWi+ZWFTcf2Exn1KonjfcZkg0No8KVRm6PDugYqhk+Y66JhwB7ciMyPzz1eT1OSvFeZ YsbbolsnUyBSIuk67dQUtSelJEQl4Yz3BfnHP4Y7loo4uwnfpg6q85rXBzJD+8/LcVMV 3eDg== X-Gm-Message-State: ABuFfoitW936SQwyvg2oIkSzSyMN30ynRK7s2cq1i7UCXi6wDnF6L1Zt EPv2bfYFhz8DY5PFK7vLLNN4hhcI1GgVpNO9iWHu3A== X-Received: by 2002:a9d:2a38:: with SMTP id t53-v6mr4572776ota.35.1538677606820; Thu, 04 Oct 2018 11:26:46 -0700 (PDT) MIME-Version: 1.0 References: <20180929103453.12025-1-cyphar@cyphar.com> <20180929131534.24472-1-cyphar@cyphar.com> <20181004162611.vdlujbdguvagalpt@ryuk> In-Reply-To: <20181004162611.vdlujbdguvagalpt@ryuk> From: Jann Horn Date: Thu, 4 Oct 2018 20:26:20 +0200 Message-ID: Subject: Re: [PATCH 2/3] namei: implement AT_THIS_ROOT chroot-like path resolution To: cyphar@cyphar.com Cc: "Eric W. Biederman" , jlayton@kernel.org, Bruce Fields , Al Viro , Arnd Bergmann , shuah@kernel.org, David Howells , Andy Lutomirski , christian@brauner.io, Tycho Andersen , kernel list , linux-fsdevel@vger.kernel.org, linux-arch , linux-kselftest@vger.kernel.org, dev@opencontainers.org, containers@lists.linux-foundation.org, Linux API Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Thu, Oct 4, 2018 at 6:26 PM Aleksa Sarai wrote: > On 2018-09-29, Jann Horn wrote: > > You attempt to open "C/../../etc/passwd" under the root "/A/B". > > Something else concurrently moves /A/B/C to /A/C. This can result in > > the following: > > > > 1. You start the path walk and reach /A/B/C. > > 2. The other process moves /A/B/C to /A/C. Your path walk is now at /A/C. > > 3. Your path walk follows the first ".." up into /A. This is outside > > the process root, but you never actually encountered the process root, > > so you don't notice. > > 4. Your path walk follows the second ".." up to /. Again, this is > > outside the process root, but you don't notice. > > 5. Your path walk walks down to /etc/passwd, and the open completes > > successfully. You now have an fd pointing outside your chroot. > > I've been playing with this and I have the following patch, which > according to my testing protects against attacks where ".." skips over > nd->root. It abuses __d_path to figure out if nd->path can be resolved > from nd->root (obviously a proper version of this patch would refactor > __d_path so it could be used like this -- and would not return > -EMULTIHOP). > > I've also attached my reproducer. With it, I was seeing fairly constant > breakouts before this patch and after it I didn't see a single breakout > after running it overnight. Obviously this is not conclusive, but I'm > hoping that it can show what my idea for protecting against ".." was. > > Does this patch make sense? Or is there something wrong with it that I'm > not seeing? > > --8<------------------------------------------------------------------- > > There is a fairly easy-to-exploit race condition with chroot(2) (and > thus by extension AT_THIS_ROOT and AT_BENEATH) where a rename(2) of a > path can be used to "skip over" nd->root and thus escape to the > filesystem above nd->root. > > thread1 [attacker]: > for (;;) > renameat2(AT_FDCWD, "/a/b/c", AT_FDCWD, "/a/d", RENAME_EXCHANGE); > thread2 [victim]: > for (;;) > openat(dirb, "b/c/../../etc/shadow", O_THISROOT); > > With fairly significant regularity, thread2 will resolve to > "/etc/shadow" rather than "/a/b/etc/shadow". With this patch, such cases > will be detected during ".." resolution (which is the weak point of > chroot(2) -- since walking *into* a subdirectory tautologically cannot > result in you walking *outside* nd->root). > > The use of __d_path here might seem suspect, however we don't mind if a > path is moved from within the chroot to outside the chroot and we > incorrectly decide it is safe (because at that point we are still within > the set of files which were accessible at the beginning of resolution). > However, we can fail resolution on the next path component if it remains > outside of the root. A path which has always been outside nd->root > during resolution will never be resolveable from nd->root and thus will > always be blocked. > > DO NOT MERGE: Currently this code returns -EMULTIHOP in this case, > purely as a debugging measure (so that you can see that > the protection actually does something). Obviously in the > proper patch this will return -EXDEV. > > Signed-off-by: Aleksa Sarai > --- > fs/namei.c | 32 ++++++++++++++++++++++++++++++-- > 1 file changed, 30 insertions(+), 2 deletions(-) > > diff --git a/fs/namei.c b/fs/namei.c > index 6f995e6de6b1..c8349693d47b 100644 > --- a/fs/namei.c > +++ b/fs/namei.c > @@ -53,8 +53,8 @@ > * The new code replaces the old recursive symlink resolution with > * an iterative one (in case of non-nested symlink chains). It does > * this with calls to _follow_link(). > - * As a side effect, dir_namei(), _namei() and follow_link() are now > - * replaced with a single function lookup_dentry() that can handle all > + * As a side effect, dir_namei(), _namei() and follow_link() are now > + * replaced with a single function lookup_dentry() that can handle all > * the special cases of the former code. > * > * With the new dcache, the pathname is stored at each inode, at least as > @@ -1375,6 +1375,20 @@ static int follow_dotdot_rcu(struct nameidata *nd) > return -EXDEV; > break; > } > + if (unlikely(nd->flags & (LOOKUP_BENEATH | LOOKUP_CHROOT))) { > + char *pathbuf, *pathptr; > + > + pathbuf = kmalloc(PATH_MAX, GFP_ATOMIC); > + if (!pathbuf) > + return -ECHILD; > + pathptr = __d_path(&nd->path, &nd->root, pathbuf, PATH_MAX); > + kfree(pathbuf); > + if (IS_ERR_OR_NULL(pathptr)) { > + if (!pathptr) > + pathptr = ERR_PTR(-EMULTIHOP); > + return PTR_ERR(pathptr); > + } > + } One somewhat problematic thing about this approach is that if someone tries to lookup "a/a/a/a/a/a/a/a/a/a/[...]/../../../../../../../../../.." for some reason, you'll have quadratic runtime: For each "..", you'll have to walk up to the root.