From: sashiko-bot@kernel.org
Cc: linux-security-module@vger.kernel.org
Subject: Re: [PATCH v3 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches
Date: Wed, 21 Oct 2026 21:07:38 +0000 [thread overview]
Message-ID: <sashiko-outbox-180684@kernel.org> (raw)
In-Reply-To: <20261021-b4-setfcap-userns-v3-4-97c7ed4e43e1@toxicpanda.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] capabilities: v2 xattr bypasses setfcap_level check in cap_convert_nscap()
--
commit 06013f196f1052cfc90402268b0b1b3878c33f84
Author: Josef Bacik <josef@toxicpanda.com>
capabilities: limit fscaps to where CAP_SETFCAP reaches
This commit updates cap_convert_nscap() in security/commoncap.c to verify
that both the mount-mapped root uid (vfsrootid) and the filesystem root uid
(rootid) do not map to uid 0 in a user namespace ancestor above
task_ns->setfcap_level. This prevents a task that entered a user namespace
without CAP_SETFCAP from setting file capabilities for an ancestor's root
user when a privileged task has mapped that uid into the namespace.
> diff --git a/security/commoncap.c b/security/commoncap.c
> index b069142230395..1a419a4a872c8 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -703,10 +703,24 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry,
> if (!vfsuid_valid(vfsrootid))
> return -EINVAL;
>
> + /*
> + * The root user may be the root user of ancestors of our namespace as
> + * well. CAP_SETFCAP that we got for entering it doesn't cover those.
> + * On an idmapped mount the root user is vfsrootid as seen through
> + * this mount and rootid as seen through every other mount of the
> + * filesystem, so both have to stay within reach.
> + */
> + if (cap_root_level(vfsuid_into_kuid(vfsrootid), task_ns) <
> + task_ns->setfcap_level)
> + return -EPERM;
> +
> rootid = from_vfsuid(idmap, fs_ns, vfsrootid);
> if (!uid_valid(rootid))
> return -EINVAL;
>
> + if (cap_root_level(rootid, task_ns) < task_ns->setfcap_level)
> + return -EPERM;
[Severity: High]
Can a v2 xattr bypass these new setfcap_level checks in
security/commoncap.c:cap_convert_nscap()?
Earlier in cap_convert_nscap(), v2 capabilities take an early return before
reaching rootid_from_xattr() and the new cap_root_level() checks:
if (size == XATTR_CAPS_SZ_2 && (idmap == &nop_mnt_idmap))
if (ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP))
/* user is privileged, just write the v2 */
return size;
Because a task in task_ns has CAP_SETFCAP in task_ns,
ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP) succeeds whenever fs_ns
(inode->i_sb->s_user_ns) is task_ns or a descendant of task_ns, regardless
of task_ns->setfcap_level.
In the scenario from the commit message (task B has uid 0 and CAP_SYS_ADMIN
without CAP_SETFCAP in init_user_ns, unshares ns1 with ns1->setfcap_level = 1,
and task A writes "0 0 1" to ns1's uid_map), task B can mount a filesystem
in ns1 (s_user_ns == ns1) and write a v2 security.capability xattr
(size == XATTR_CAPS_SZ_2). While a v3 xattr with rootid 0 is rejected with
-EPERM, the v2 write returns early and stores a raw v2 xattr whose root user
is make_kuid(ns1, 0), which is kuid 0.
If task B in init_user_ns then mounts overlayfs in init_user_ns
(ovl_sb->s_user_ns == &init_user_ns) with that ns1 directory as lowerdir and
executes the file in init_user_ns, get_file_caps() checks
current_in_userns(file->f_path.mnt->mnt_sb->s_user_ns) against
ovl_sb->s_user_ns (&init_user_ns) and reads the capability through:
get_vfs_caps_from_disk()
__vfs_getxattr()
ovl_xattr_get() [fs/overlayfs/xattrs.c]
vfs_getxattr() [under with_ovl_creds(dentry->d_sb)]
cap_inode_getsecurity() [security/commoncap.c]
In fs/overlayfs/xattrs.c:ovl_xattr_get() (and similarly during copy-up in
fs/overlayfs/copy_up.c:ovl_copy_xattr()):
with_ovl_creds(dentry->d_sb)
return vfs_getxattr(mnt_idmap(realpath.mnt), realpath.dentry, name, value, size);
And in security/commoncap.c:cap_inode_getsecurity():
if (is_v2header(size, cap)) {
root = 0;
...
kroot = make_kuid(fs_ns, root);
cap_inode_getsecurity() resolves kroot to make_kuid(ns1, 0) == kuid 0 and
returns a valid v2 capability to get_vfs_caps_from_disk(), granting the file
capabilities in init_user_ns.
Additionally, if task A maps kuid 0 as a non-zero uid in ns1 (for example,
"0 1000 1" and "1000 0 1" with ns1->setfcap_level = 1), task B in ns1 can
unshare a child namespace ns2 and map "0 1000 1" in ns2 (permitted by
verify_root_map() because lower_first is 1000 != 0 in ns1). On a filesystem
mounted in ns2 (fs_ns == ns2), a caller in ns1 or ns2 writing a v2 xattr
satisfies ns_capable(ns2, CAP_SETFCAP) and writes an unconverted v2 xattr
whose root user is make_kuid(ns2, 0) == kuid 0 rather than make_kuid(ns1, 0).
Should the v2 fast path in cap_convert_nscap() be moved after the vfsrootid
and rootid checks (or verify that vfsrootid and rootid match and satisfy
cap_root_level() >= task_ns->setfcap_level before returning size)?
> +
> nsrootid = from_kuid(fs_ns, rootid);
> if (nsrootid == -1)
> return -EINVAL;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261021-b4-setfcap-userns-v3-0-97c7ed4e43e1@toxicpanda.com?part=4
next prev parent reply other threads:[~2026-10-21 21:07 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 20:37 [PATCH v3 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-21 20:37 ` [PATCH v3 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-21 20:44 ` sashiko-bot
2026-10-21 20:37 ` [PATCH v3 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-10-21 20:48 ` sashiko-bot
2026-10-21 20:37 ` [PATCH v3 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
2026-10-21 20:57 ` sashiko-bot
2026-10-21 20:37 ` [PATCH v3 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
2026-10-21 21:07 ` sashiko-bot [this message]
2026-10-21 20:37 ` [PATCH v3 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
2026-10-21 20:55 ` 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-180684@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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