Received: by 2002:ab2:1347:0:b0:1f4:ac9d:b246 with SMTP id g7csp99756lqg; Wed, 10 Apr 2024 17:23:05 -0700 (PDT) X-Forwarded-Encrypted: i=3; AJvYcCVVbBnOiWQJEKSkuFT0SA+Ac8Z91s5marStPGlkIt0CUv6VDnWrYXd0J3JUvUvPME7n0qMFzUuPlwm8k1F8zVBjJHV5ODjoSLKEq7iV/A== X-Google-Smtp-Source: AGHT+IH1MCb42UG3Zl23OeHSjItRjjV84cqlykO26bw0RtkH1vBqK+Fd+gqgd8uMP45TX8/pvAUd X-Received: by 2002:a05:6512:4cc:b0:516:cdfa:17f6 with SMTP id w12-20020a05651204cc00b00516cdfa17f6mr2177710lfq.67.1712794985068; Wed, 10 Apr 2024 17:23:05 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1712794985; cv=pass; d=google.com; s=arc-20160816; b=1GhXA6SztMZENvhvZQ1Ly75Wsxk5kJaEjjAQZEtWGbmBAowPGdIwUNNHcN40UNtABA CZpIu7O3hdjTQsYwXRqn4CihPwFrtcemzHP8SFHga63ObodhaqBhT4fsmQlTwWyd16S4 U79zKgR8OWMd4mp9b/Ivrg2oMDU2aEmHNFX3F0BfocBkwEnkLA5i0i8v3Ei8kwAk7Qua XVNgcYbeI6WDRd4lL5Tpe5n4KgP6CS0Xuww6Q8VGanNsKwLoDO4XJwoGlYBtAt7DfOiv viE/Vl7rsU6pc764s9Matl3FT/n+HE84aGZfkVeZPLZv65x6k8lKUsU5E/gE+sF2TXFb H9pA== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=content-transfer-encoding:mime-version:list-unsubscribe :list-subscribe:list-id:precedence:message-id:date:subject:cc:to :from:dkim-signature; bh=XjmFVPwN8RNehcRWDqajvazfWoIYtrimDNqTwqKIlCk=; fh=Ic369aR78Z30G9WnZnExsC02Y8FyZdt9YUogtEs3HKQ=; b=S9RwZ/FcqWjJ3ZRlYTq+osQMf9IsrnOkHimFBCLLNgu3yPIUgwxaEEPMDu+fsm7GFF 4KrS5sBz2rNVH/7w2nqgnzTEuY4Zf2p966PxWLfsPe4otCo/EtoDiSvnTLRhyv2jZI8r 7gKVPSKc9INsTrE59czPpel+zPJwU/SggvXcH5Ci9w+QDWWJgfiyeJUyugohvndtCZyp 7kD7A67XINyPB9yPTQX0iwszA/a1HtGRtmiP8EGuGIJ7JTN0n300fhB/IIs4ZQ737rtd ZOwT4y4as0ilNIBT/kH0RLH+GWZfksplvkvC8QKSA5yEhyON8mYi/FKR3cqE4gayxyB6 xkfg==; dara=google.com ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@linux-foundation.org header.s=korg header.b=KwuOKFgk; arc=pass (i=1 dkim=pass dkdomain=linux-foundation.org); spf=pass (google.com: domain of linux-kernel+bounces-139581-linux.lists.archive=gmail.com@vger.kernel.org designates 147.75.80.249 as permitted sender) smtp.mailfrom="linux-kernel+bounces-139581-linux.lists.archive=gmail.com@vger.kernel.org" Return-Path: Received: from am.mirrors.kernel.org (am.mirrors.kernel.org. [147.75.80.249]) by mx.google.com with ESMTPS id p14-20020a1709060dce00b00a51e455bd5asi170161eji.354.2024.04.10.17.23.04 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 10 Apr 2024 17:23:05 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel+bounces-139581-linux.lists.archive=gmail.com@vger.kernel.org designates 147.75.80.249 as permitted sender) client-ip=147.75.80.249; Authentication-Results: mx.google.com; dkim=pass header.i=@linux-foundation.org header.s=korg header.b=KwuOKFgk; arc=pass (i=1 dkim=pass dkdomain=linux-foundation.org); spf=pass (google.com: domain of linux-kernel+bounces-139581-linux.lists.archive=gmail.com@vger.kernel.org designates 147.75.80.249 as permitted sender) smtp.mailfrom="linux-kernel+bounces-139581-linux.lists.archive=gmail.com@vger.kernel.org" Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by am.mirrors.kernel.org (Postfix) with ESMTPS id 6D37B1F24B25 for ; Thu, 11 Apr 2024 00:17:21 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 09A34F9DE; Thu, 11 Apr 2024 00:10:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux-foundation.org header.i=@linux-foundation.org header.b="KwuOKFgk" Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CC72B2C80; Thu, 11 Apr 2024 00:10:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1712794242; cv=none; b=HyqiqvdGbeFv4WyuARBmxh+jDQc44FDmsFUaOk+eZqOd+3q8Ksccscw6TQzRuPwh3kWJdIr2G6HyBKrGWXoqBldrxCfhizGF0rm/kN6HYwqaVb9GEEOefzAarngZEWPbjxtBrGNpWBHK/TX+radyTnzdDVpGc2kSCVHvUHCidPg= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1712794242; c=relaxed/simple; bh=sGbIuL6+Qheb+6D9rz95VwEJtWh1KSDnG3kudzXCjRo=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type; b=E2LU9pTk/QMUSy5VfV4+85g4QTisRMrXddpq/W4+30c2BOJu4zoS4w622mfljZEcxwNsFmwdzQ66O26aTayGJZGYo/bYgCUfiHN8ga+7eTBVLE1azHf120TBPwLZeVBVgAZ7OUuL4wKesx6viUzKhYrSpM2cG/jW0QdPBiQRQWw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux-foundation.org header.i=@linux-foundation.org header.b=KwuOKFgk; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id 29B87C433F1; Thu, 11 Apr 2024 00:10:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linux-foundation.org; s=korg; t=1712794241; bh=sGbIuL6+Qheb+6D9rz95VwEJtWh1KSDnG3kudzXCjRo=; h=From:To:Cc:Subject:Date:From; b=KwuOKFgkIgTQdLFDTnJa0Bsml2RpWX1CAq08rrv7v7YpXElHck7P/f/rb2hbZCLvt OytoFF+urComDnfd7euAaTLxr5w2vkal9O4El4k5wUKoOujuAWdrSjGv0QS8lOblzE IimnDjqbOJYcz60K99x1tjb7xnaiA6N/XzYv4KUo= From: Linus Torvalds To: Alexander Viro , Christian Brauner , Jan Kara Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Linus Torvalds , Andrew Lutomirski , Peter Anvin Subject: [PATCH] vfs: relax linkat() AT_EMPTY_PATH - aka flink() - requirements Date: Wed, 10 Apr 2024 17:10:12 -0700 Message-ID: <20240411001012.12513-1-torvalds@linux-foundation.org> X-Mailer: git-send-email 2.44.0.330.g4d18c88175 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "The definition of insanity is doing the same thing over and over again and expecting different results” We've tried to do this before, most recently with commit bb2314b47996 ("fs: Allow unprivileged linkat(..., AT_EMPTY_PATH) aka flink") about a decade ago. But the effort goes back even further than that, eg this thread back from 1998 that is so old that we don't even have it archived in lore: https://lkml.org/lkml/1998/3/10/108 which also points out some of the reasons why it's dangerous. Or, how about then in 2003: https://lkml.org/lkml/2003/4/6/112 where we went through some of the same arguments, just wirh different people involved. In particular, having access to a file descriptor does not necessarily mean that you have access to the path that was used for lookup, and there may be very good reasons why you absolutely must not have access to a path to said file. For example, if we were passed a file descriptor from the outside into some limited environment (think chroot, but also user namespaces etc) a 'flink()' system call could now make that file visible inside a context where it's not supposed to be visible. In the process the user may also be able to re-open it with permissions that the original file descriptor did not have (eg a read-only file descriptor may be associated with an underlying file that is writable). Another variation on this is if somebody else (typically root) opens a file in a directory that is not accessible to others, and passes the file descriptor on as a read-only file. Again, the access to the file descriptor does not imply that you should have access to a path to the file in the filesystem. So while we have tried this several times in the past, it never works. The last time we did this, that commit bb2314b47996 quickly got reverted again in commit f0cc6ffb8ce8 (Revert "fs: Allow unprivileged linkat(..., AT_EMPTY_PATH) aka flink"), with a note saying "We may re-do this once the whole discussion about the interface is done". Well, the discussion is long done, and didn't come to any resolution. There's no question that 'flink()' would be a useful operation, but it's a dangerous one. However, it does turn out that since 2008 (commit d76b0d9b2d87: "CRED: Use creds in file structs") we have had a fairly straightforward way to check whether the file descriptor was opened by the same credentials as the credentials of the flink(). That allows the most common patterns that people want to use, which tend to be to either open the source carefully (ie using the openat2() RESOLVE_xyz flags, and/or checking ownership with fstat() before linking), or to use O_TMPFILE and fill in the file contents before it's exposed to the world with linkat(). But it also means that if the file descriptor was opened by somebody else, or we've gone through a credentials change since, the operation no longer works (unless we have CAP_DAC_READ_SEARCH capabilities, as before). Note that the credential equality check is done by using pointer equality, which means that it's not enough that you have effectively the same user - they have to be literally identical, since our credentials are using copy-on-write semantics. So you can't change your credentials to something else and try to change it back to the same ones between the open() and the linkat(). This is not meant to be some kind of generic permission check, this is literally meant as a "the open and link calls are 'atomic' wrt user credentials" check. It also means that you can't just move things between namespaces, because the credentials aren't just a list of uid's and gid's: they includes the pointer to the user_ns that the capabilities are relative to. So let's try this one more time and see if maybe this approach ends up being workable after all. Cc: Andrew Lutomirski Cc: Al Viro Cc: Christian Brauner Cc: Peter Anvin Cc: Jan Kara Signed-off-by: Linus Torvalds --- fs/namei.c | 17 ++++++++++++----- include/linux/namei.h | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index c5b2a25be7d0..3c684014eb40 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2422,6 +2422,14 @@ static const char *path_init(struct nameidata *nd, unsigned flags) if (!f.file) return ERR_PTR(-EBADF); + if (flags & LOOKUP_DFD_MATCH_CREDS) { + if (f.file->f_cred != current_cred() && + !capable(CAP_DAC_READ_SEARCH)) { + fdput(f); + return ERR_PTR(-ENOENT); + } + } + dentry = f.file->f_path.dentry; if (*s && unlikely(!d_can_lookup(dentry))) { @@ -4641,14 +4649,13 @@ int do_linkat(int olddfd, struct filename *old, int newdfd, goto out_putnames; } /* - * To use null names we require CAP_DAC_READ_SEARCH + * To use null names we require CAP_DAC_READ_SEARCH or + * that the open-time creds of the dfd matches current. * This ensures that not everyone will be able to create * handlink using the passed filedescriptor. */ - if (flags & AT_EMPTY_PATH && !capable(CAP_DAC_READ_SEARCH)) { - error = -ENOENT; - goto out_putnames; - } + if (flags & AT_EMPTY_PATH) + how |= LOOKUP_DFD_MATCH_CREDS; if (flags & AT_SYMLINK_FOLLOW) how |= LOOKUP_FOLLOW; diff --git a/include/linux/namei.h b/include/linux/namei.h index 74e0cc14ebf8..678ffe4acf99 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -44,6 +44,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT}; #define LOOKUP_BENEATH 0x080000 /* No escaping from starting point. */ #define LOOKUP_IN_ROOT 0x100000 /* Treat dirfd as fs root. */ #define LOOKUP_CACHED 0x200000 /* Only do cached lookup */ +#define LOOKUP_DFD_MATCH_CREDS 0x400000 /* Require that dfd creds match current */ /* LOOKUP_* flags which do scope-related checks based on the dirfd. */ #define LOOKUP_IS_SCOPED (LOOKUP_BENEATH | LOOKUP_IN_ROOT) -- 2.44.0.330.g4d18c88175