Linux Security Modules development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Josef Bacik" <josef@toxicpanda.com>,
	"Serge Hallyn" <serge@hallyn.com>,
	"Paul Moore" <paul@paul-moore.com>,
	"Christian Brauner" <brauner@kernel.org>
Cc: "James Morris" <jmorris@namei.org>,
	"David Howells" <dhowells@redhat.com>,
	"Jarkko Sakkinen" <jarkko@kernel.org>,
	"Andrew G. Morgan" <morgan@kernel.org>,
	"Serge Hallyn" <sergeh@kernel.org>,
	linux-security-module@vger.kernel.org, keyrings@vger.kernel.org
Subject: Re: [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0
Date: Fri, 23 Oct 2026 17:03:57 +0000	[thread overview]
Message-ID: <sashiko-outbox-182977@kernel.org> (raw)
In-Reply-To: <20261023-b4-setfcap-userns-v5-3-0620223be951@toxicpanda.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] userns: missing CAP_SETFCAP check in verify_root_map() for tasks in map_ns
--

commit 715d4f4455aa94fb0eaba838f8aa378efb3afe54
Author: Josef Bacik <josef@toxicpanda.com>

userns: check the opener and the writer before mapping uid 0

This commit updates verify_root_map() to find the topmost ancestor whose root
user is included in a new uid_map and checks that both the file opener and the
writer hold CAP_SETFCAP reaching that ancestor's level.

> diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
> index 7bd3692c2f3eb..661d7e9b72d56 100644
> --- a/kernel/user_namespace.c
> +++ b/kernel/user_namespace.c

[ ... ]

> @@ -897,42 +937,40 @@ static bool verify_root_map(const struct file *file,
>  			    struct user_namespace *map_ns,
>  			    struct uid_gid_map *new_map)
>  {
> -	int idx;
>  	const struct user_namespace *file_ns = file->f_cred->user_ns;
> -	struct uid_gid_extent *extent0 = NULL;
> -
> -	for (idx = 0; idx < new_map->nr_extents; idx++) {
> -		if (new_map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
> -			extent0 = &new_map->extent[idx];
> -		else
> -			extent0 = &new_map->forward[idx];
> -		if (extent0->lower_first == 0)
> -			break;
> -
> -		extent0 = NULL;
> -	}
> +	int level;
>  
> -	if (!extent0)
> +	level = map_root_level(map_ns->parent, new_map);
> +	if (level == INT_MAX)
>  		return true;
>  
>  	if (map_ns == file_ns) {
>  		/* The process unshared its ns and is writing to its own
>  		 * /proc/self/uid_map.  User already has full capabilites in
>  		 * the new namespace.  Verify that the parent had CAP_SETFCAP
> -		 * when it unshared.
> +		 * when it unshared, as far up as those root users go.
>  		 * */
> -		if (file_ns->setfcap_level >= file_ns->level)
> +		if (file_ns->setfcap_level > level)
>  			return false;
>  	} else {
>  		/* Process p1 is writing to uid_map of p2, who is in a child
>  		 * user namespace to p1's.  Verify that the opener of the map
>  		 * file has CAP_SETFCAP against the parent of the new map
> -		 * namespace */
> -		if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP))
> +		 * namespace, and as far up as those root users go.
> +		 */
> +		if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP) ||
> +		    !cred_setfcap_reaches(file->f_cred, map_ns->parent, level))
>  			return false;
>  	}
>  
> -	return true;
> +	/* The file may have been handed to someone else since it was opened,
> +	 * so the same goes for the process that is doing the write.
> +	 */
> +	if (map_ns == current_user_ns())
> +		return map_ns->setfcap_level <= level;
> +
> +	return ns_capable(map_ns->parent, CAP_SETFCAP) &&
> +	       cred_setfcap_reaches(current_cred(), map_ns->parent, level);
>  }

[Severity: High]
When a task is in map_ns (so map_ns == file_ns for the opener or
map_ns == current_user_ns() for the writer), does checking only
map_ns->setfcap_level <= level allow a task that has dropped CAP_SETFCAP in
map_ns to still map the ancestor root user?

For example, if a process running as uid 0 with CAP_SETFCAP in init_user_ns
calls unshare(CLONE_NEWUSER), create_user_ns() sets map_ns->setfcap_level to 0.
Inside map_ns (before uid_map is written), two cases can occur:

1. Task A opens /proc/self/uid_map and passes the descriptor (by fork or
   SCM_RIGHTS) to Task B in map_ns, which has dropped CAP_SETFCAP and all
   other capabilities via capset().
2. A task in map_ns drops CAP_SETFCAP via capset() while keeping
   CAP_SYS_ADMIN, and then opens /proc/self/uid_map itself.

When the task without CAP_SETFCAP writes "0 0 1" through:

  proc_uid_map_write()
    -> map_write()
      -> new_idmap_permitted()
        -> verify_root_map()

map_write() only checks file_ns_capable(file, map_ns, CAP_SYS_ADMIN) on the
opener, and new_idmap_permitted() takes the single-extent unprivileged branch
which only checks uid_eq(ns->owner, cred->euid):

kernel/user_namespace.c:new_idmap_permitted() {
	if (cap_setid == CAP_SETUID && !verify_root_map(file, ns, new_map))
		return false;

	if ((new_map->nr_extents == 1) && (new_map->extent[0].count == 1) &&
	    uid_eq(ns->owner, cred->euid)) {
		u32 id = new_map->extent[0].lower_first;
		if (cap_setid == CAP_SETUID) {
			kuid_t uid = make_kuid(ns->parent, id);
			if (uid_eq(uid, cred->euid))
				return true;
		}
}

Because verify_root_map() only checks map_ns->setfcap_level <= level when
map_ns == file_ns and when map_ns == current_user_ns(), the write succeeds
even though the task dropped CAP_SETFCAP. Once "0 0 1" is installed in
map_ns->uid_map, make_kuid(map_ns, 0) becomes KUIDT_INIT(0) instead of
INVALID_UID, allowing the task in map_ns to call execve() and regain full
capabilities (including CAP_SETFCAP) via handle_privileged_root() in
security/commoncap.c.

Note that cap_setfcap_level() in security/commoncap.c already handles a cred
whose user_ns is the target namespace by checking whether CAP_SETFCAP is still
raised in cred->cap_effective before returning cred->user_ns->setfcap_level:

security/commoncap.c:cap_setfcap_level() {
	if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT) != 0)
		return ns->level + 1;

	if (cap_raised(cred->cap_effective, CAP_SETFCAP))
		return cred->user_ns->setfcap_level;

	/* All we have is that we own a child of our namespace. */
	return cred->user_ns->level + 1;
}

When map_ns == file_ns (or map_ns == current_user_ns()), should
verify_root_map() check file_ns_capable(file, map_ns, CAP_SETFCAP) and
cred_setfcap_reaches(file->f_cred, map_ns, level) for the opener, and
ns_capable(map_ns, CAP_SETFCAP) and
cred_setfcap_reaches(current_cred(), map_ns, level) for the writer?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com?part=3

  reply	other threads:[~2026-10-23 17:03 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23 16:30 [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-23 16:30 ` [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-23 16:44   ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-10-23 16:46   ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
2026-10-23 17:03   ` sashiko-bot [this message]
2026-10-23 18:42     ` Jarkko Sakkinen
2026-10-23 16:30 ` [PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
2026-10-23 16:56   ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
2026-10-23 16:47   ` sashiko-bot

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=sashiko-outbox-182977@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=brauner@kernel.org \
    --cc=dhowells@redhat.com \
    --cc=jarkko@kernel.org \
    --cc=jmorris@namei.org \
    --cc=josef@toxicpanda.com \
    --cc=keyrings@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=morgan@kernel.org \
    --cc=paul@paul-moore.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=serge@hallyn.com \
    --cc=sergeh@kernel.org \
    /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