linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Dirk Steinmetz <public@rsjtdrjgfuzkfg.com>
To: Serge Hallyn <serge.hallyn@ubuntu.com>,
	Seth Forshee <seth.forshee@canonical.com>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Linux FS Devel <linux-fsdevel@vger.kernel.org>,
	linux-kernel@vger.kernel.org,
	"Eric W . Biederman" <ebiederm@xmission.com>,
	Kees Cook <keescook@chromium.org>,
	Serge Hallyn <serge.hallyn@canonical.com>
Cc: Dirk Steinmetz <public@rsjtdrjgfuzkfg.com>
Subject: [RFC] namei: prevent sgid-hardlinks for unmapped gids
Date: Tue,  3 Nov 2015 01:39:47 +0100	[thread overview]
Message-ID: <1446511187-9131-1-git-send-email-public@rsjtdrjgfuzkfg.com> (raw)
In-Reply-To: <CALCETrUPyFMAk9uxJ8+hg0YoMN=mTM3q9jAT7zki1g9uuWtVkg@mail.gmail.com>

In order to hardlink to a sgid-executable, it is sufficient to be the
file's owner. When hardlinking within an unprivileged user namespace, the
users of that namespace could thus use hardlinks to pin setgid binaries
owned by themselves (or any mapped uid, with CAP_FOWNER) and a gid outside
of the namespace. This is a possible security risk.

This change prevents hardlinking of sgid-executables within user
namespaces, if the file is not owned by a mapped gid.

Signed-off-by: Dirk Steinmetz <public@rsjtdrjgfuzkfg.com>
---

MISSING: Documentation/sysctl/fs.txt not updated, as this patch is
intended for discussion.

If there are no further misunderstandings on my side, this patch is what
Serge and I agree on (modulo my not-that-much-linux-kernel-experience
codestyle, feel free to suggest improvements!).

The new condition for sgid-executables is equivalent to
> inode_owner_or_capable(inode) && kgid_has_mapping(ns, inode->i_gid)
which, as recommended by Serge, does not change the behaviour for the init
namespace. It fixes the problem of pinning parent namespace's gids.

However, I think the "same" security issue is also valid within any
namespace, for regular users pinning other gids within the same namespace.
I already presented an example for that in a previous mail:
- A file has the setgid and user/group executable bits set, and is owned
  by user:group.
- The user 'user' is not in the group 'group', and does not have any
  capabilities.
- The user 'user' hardlinks the file. The permission check will succeed,
  as the user is the owner of the file.
- The file is replaced with a newer version (for example fixing a security
  issue)
- Now user can still use the hardlink-pinned version to execute the file
  as 'user:group' (and for example exploit the security issue).

To prevent that, the condition would need to be changed to something like
inode_group_or_capable, resembling inode_owner_or_capable, but checking
that the caller is in the group the inode belongs to or has some
capability (for consistency with former behaviour, CAP_FOWNER? for
consistency with the documentation, CAP_FSETID?). However, this would
change userland behaviour outside of userns. Thus my main question:
Is the scenario above bad enough to change userland behaviour?

I'd apprechiate your comments.

- Dirk


Diffstat:
 fs/namei.c | 47 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 38 insertions(+), 9 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 29fc6a6..9c6c2e2 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -913,18 +913,19 @@ static inline int may_follow_link(struct nameidata *nd)
 }
 
 /**
- * safe_hardlink_source - Check for safe hardlink conditions
+ * safe_hardlink_source_uid - Check for safe hardlink conditions not dependent
+ * on the inode's group. These conditions may be overridden by inode ownership
+ * or CAP_FOWNER with respect to the inode's uid
  * @inode: the source inode to hardlink from
  *
  * Return false if at least one of the following conditions:
  *    - inode is not a regular file
  *    - inode is setuid
- *    - inode is setgid and group-exec
  *    - access failure for read and write
  *
  * Otherwise returns true.
  */
-static bool safe_hardlink_source(struct inode *inode)
+static bool safe_hardlink_source_uid(struct inode *inode)
 {
 	umode_t mode = inode->i_mode;
 
@@ -936,10 +937,6 @@ static bool safe_hardlink_source(struct inode *inode)
 	if (mode & S_ISUID)
 		return false;
 
-	/* Executable setgid files should not get pinned to the filesystem. */
-	if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
-		return false;
-
 	/* Hardlinking to unreadable or unwritable sources is dangerous. */
 	if (inode_permission(inode, MAY_READ | MAY_WRITE))
 		return false;
@@ -948,30 +945,62 @@ static bool safe_hardlink_source(struct inode *inode)
 }
 
 /**
+ * safe_hardlink_source_gid - Check for safe hardlink conditions dependent
+ * on the inode's group. These conditions may be overridden by inode ownership
+ * or CAP_FOWNER with respect to the inode's gid
+ * @inode: the source inode to hardlink from
+ *
+ * Return false if inode is setgid and group-exec
+ *
+ * Otherwise returns true.
+ */
+static bool safe_hardlink_source_gid(struct inode *inode)
+{
+	umode_t mode = inode->i_mode;
+
+	/* Executable setgid files should not get pinned to the filesystem. */
+	if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
+		return false;
+
+	return true;
+}
+
+/**
  * may_linkat - Check permissions for creating a hardlink
  * @link: the source to hardlink from
  *
  * Block hardlink when all of:
  *  - sysctl_protected_hardlinks enabled
  *  - fsuid does not match inode
- *  - hardlink source is unsafe (see safe_hardlink_source() above)
+ *  - hardlink source is unsafe (see safe_hardlink_source_*() above)
  *  - not CAP_FOWNER in a namespace with the inode owner uid mapped
+ *    (and inode gid mapped, if hardlink conditions depending on the inode's
+ *    group are not satisfied)
  *
  * Returns 0 if successful, -ve on error.
  */
 static int may_linkat(struct path *link)
 {
 	struct inode *inode;
+	struct user_namespace *ns;
+	bool owner;
+	bool safe_uid;
+	bool safe_gid;
 
 	if (!sysctl_protected_hardlinks)
 		return 0;
 
 	inode = link->dentry->d_inode;
+	ns = current_user_ns();
 
 	/* Source inode owner (or CAP_FOWNER) can hardlink all they like,
 	 * otherwise, it must be a safe source.
 	 */
-	if (inode_owner_or_capable(inode) || safe_hardlink_source(inode))
+	owner = inode_owner_or_capable(inode);
+	safe_uid = safe_hardlink_source_uid(inode) || owner;
+	safe_gid = safe_hardlink_source_gid(inode) ||
+			(owner && kgid_has_mapping(ns, inode->i_gid));
+	if (safe_uid && safe_gid)
 		return 0;
 
 	audit_log_link_denied("linkat", link);
-- 
2.1.4


  reply	other threads:[~2015-11-03  0:40 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-10-10 14:59 [PATCH] namei: permit linking with CAP_FOWNER in userns Dirk Steinmetz
2015-10-20 14:09 ` Dirk Steinmetz
2015-10-27 14:33   ` Seth Forshee
2015-10-27 18:08     ` Dirk Steinmetz
2015-10-27 20:28       ` Serge Hallyn
2015-10-28 15:07         ` Dirk Steinmetz
2015-10-28 17:33           ` Serge Hallyn
2015-11-02 15:10             ` Dirk Steinmetz
2015-11-02 18:02               ` Serge Hallyn
2015-11-02 19:57                 ` Andy Lutomirski
2015-11-03  0:39                   ` Dirk Steinmetz [this message]
2015-11-03 15:44                     ` [RFC] namei: prevent sgid-hardlinks for unmapped gids Serge Hallyn
2015-11-03 18:20                     ` Kees Cook
2015-11-03 23:21                       ` Dirk Steinmetz
2015-11-03 23:29                         ` Kees Cook
2015-11-04  6:58                           ` Willy Tarreau
2015-11-04 17:59                             ` Andy Lutomirski
2015-11-04 18:15                               ` Willy Tarreau
2015-11-04 18:17                                 ` Andy Lutomirski
2015-11-04 18:28                                   ` Willy Tarreau
2015-11-06 21:59                               ` Kees Cook
2015-11-06 22:30                                 ` Andy Lutomirski
2015-11-07  0:11                                   ` Kees Cook
2015-11-07  0:16                                     ` Kees Cook
2015-11-07  0:48                                       ` Andy Lutomirski
2015-11-07  5:05                                         ` Kees Cook
2015-11-08  2:02                                           ` Theodore Ts'o
2015-11-10 15:08                                             ` Jan Kara
2015-11-19 20:11                                               ` Kees Cook
2015-11-19 21:57                                                 ` Andy Lutomirski
2015-11-19 22:02                                                 ` Dave Chinner
2015-11-20  0:11                                                   ` Kees Cook
2015-11-04 14:46                       ` Serge E. Hallyn
2015-10-27 21:04     ` [PATCH] namei: permit linking with CAP_FOWNER in userns Eric W. Biederman
2015-11-03 17:51 ` Kees Cook

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1446511187-9131-1-git-send-email-public@rsjtdrjgfuzkfg.com \
    --to=public@rsjtdrjgfuzkfg.com \
    --cc=ebiederm@xmission.com \
    --cc=keescook@chromium.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=serge.hallyn@canonical.com \
    --cc=serge.hallyn@ubuntu.com \
    --cc=seth.forshee@canonical.com \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).