From nobody Thu Oct 15 00:04:27 2026 Received: from mail-qk1-f175.google.com (mail-qk1-f175.google.com [209.85.222.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E3FE3493D5B for ; Wed, 14 Oct 2026 20:48:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010892; cv=none; b=RqY3PkK+te2SWOL4ViuQurRoBgq/CaZRduP4y3ZkuZa32OPVIUPesL71wASZXwT8sMMWS6XyX1c2SjHyZ3GZpUafVYxKwrihT3RCPsQt+GmsPWolK86Sc+Dr1KruNAkOY46vmDmmKeqmbuXoudupyJkFT6KxqJ0kUyucHdzq1E0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010892; c=relaxed/simple; bh=jEUw1PTUbIUZFfIpYjNRGDosaCJoj/Pmupxim4Jwq8I=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pP69LVROcrdWbASIyCZnshdEF7yQf9VBXepbDsTgknYFbQZk+OZOg0poWFm2HhJyHL35BOYW9CJKiRpt5TV8Pn5q72LoQ7905g1/eQxKlyNHn5k6oFVXDjQCkeeMqS54L7UauCISL78gkh/oGkwaLPUiW/L2WhCj4BTNY6QTHb8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com; spf=pass smtp.mailfrom=toxicpanda.com; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b=HoxgpLHf; arc=none smtp.client-ip=209.85.222.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b="HoxgpLHf" Received: by mail-qk1-f175.google.com with SMTP id af79cd13be357-93e63ee7b4cso387873985a.2 for ; Wed, 14 Oct 2026 13:48:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=toxicpanda.com; s=google; t=1792010890; x=1792615690; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=HI+CsvyPZmneNEA8y7Xgdbvza6UQtYnY4BK0sCY6U6o=; b=HoxgpLHf00CImmsbXYSF52krlH9TVhbpPGiFIrp3xTdU5lqiSdqXv8/wDuE1HMq2n5 E1mI2fEJfE04p0p/OFgNmI+5QvZFWkGx8PKUhzePk5jXshOzforLwNBE3JfI/6OAbTfw N3SIKxXts5esh7hiDhXuwqfCXNzW7wtZwwrttmnRRtwALXUHKhgt5XtMmVsAhi5ukpzR 3WhlNuIgxHzHqjp8O908UhwtC3Fy8hYEeuMLdxIVQs3qxoGf+nDUi1VjdsgaFKCSkN/G lBQ4dxyXrNL/E7RRkqaAcJnDgSKhSKGJCh7GszlpQld7OFPx3YVaTCcDbp4F7/AwE+/i 529g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792010890; x=1792615690; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=HI+CsvyPZmneNEA8y7Xgdbvza6UQtYnY4BK0sCY6U6o=; b=bg41LFAwbqw382moRVvTdk0MAmJDpD5wEtB2dQqKeKowSZkVAaMo0cI1hfRERigAMA Ro4hgQ4/piStG8pi4ROR4cYHmBZr7i/1HcPWYcoeiu0zVRZ7KQhYxF/z8h04PaSC1AlN +vCGrfKioxXz4rsxvA8IAahkmE5QGeYRiShghoQ/C2sW7RQB1xdb9xQynH8xaXA49l8a bkBhEbcpSudDioSvNCtDA4q9jOgFLOixx1spqeK0SOgbyMiPhS5eRtX7rMHv6J8Zwh+O bL1pFte/CAjgBQgCWe/1sA6HNsrQNbu7d4HWH9CxMiN0ya2MZTARegu78TKxdou23ruL 7LIw== X-Forwarded-Encrypted: i=1; AKwUvByVTTwWm6k84Wzhv2Fhtag5ooYPRIT829p55nMKNZIaUWS/M4unnBR7akirGFxLBKPhIKdTWmGfwBzqpMk=@vger.kernel.org X-Gm-Message-State: AFq9FYJYbryTl4tlnCuASHUT6wjZQCKuRDiS64bU2uEJBLjUm5Wc8n+W Xa2s7WTp+750N5e61qufqcY8ur+u69Vm+VONdscwOkdW1ut0d4d1mjadozMO5zgUew4= X-Gm-Gg: AYBFou0lhhAgUjocUXnXEMmVV7/DSbYRBkemCzHRcleRxTnwxJb+bs9Sm6AVArj+tXC KGCsbuy6935NtyB/c+8eOmNMEEc4ggL0uB3YqRcxm+wsk450HIxm+l9nQ5zU0NTBldYCnTyP3/L d18uJ3+85iboSfWJXcYFxd5wXWLHp87vsDd2CIYTIXyCH2Ljttqq6nISJNCZDNEd/xN2Jll9mAI CowPxPuIohSPC/OVuJkQv8Kl/DceZegBNWyeU21T6oQju8PAhVvJAhP/x8yhAfPHZe8ASnS7dYD 8v/Rt973tM7EiJnoJhE6Kmyu1tHaZMMe9RoNnA0rUY6TOz8zW7Bhdxd3SP3BmzB8284ArswESGC FI6PuxzZ+nqKLsUIN+d905WNoTCwAB6NAWuDPMlHFjdxmebd3o0lhPaDc1Rsa4ZoyRSrNP41IU5 SL8Uzrbl+Ng/I12ISkpemt47mahCX7zcKaQWkCR/2ejFhrDcgXLlnZ/xYfDk02CiAPJ11TavxnF d0/z8zhryopZ145+WEj3xpjaA== X-Received: by 2002:a05:620a:450a:b0:939:ede4:a499 with SMTP id af79cd13be357-93ebd0c0349mr2772049485a.5.1792010889608; Wed, 14 Oct 2026 13:48:09 -0700 (PDT) Received: from toxicpanda.com ([153.61.196.250]) by smtp.gmail.com with ESMTPSA id af79cd13be357-93ef4f49861sm587646285a.4.2026.10.14.13.48.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 13:48:07 -0700 (PDT) From: Josef Bacik Date: Wed, 14 Oct 2026 20:47:46 +0000 Subject: [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches 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: quoted-printable Message-Id: <20261014-b4-setfcap-userns-v2-1-fac4d19281d7@toxicpanda.com> References: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> In-Reply-To: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> To: Serge Hallyn , Paul Moore , Christian Brauner Cc: James Morris , David Howells , Jarkko Sakkinen , "Andrew G. Morgan" , Serge Hallyn , linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-fsdevel@vger.kernel.org, Josef Bacik X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openssh-sha256; t=1792010880; l=5135; i=josef@toxicpanda.com; h=from:subject:message-id; bh=jEUw1PTUbIUZFfIpYjNRGDosaCJoj/Pmupxim4Jwq8I=; b=U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgUBr36M/n0nWN0DNbnxwzIiCZez6MG JiruuNaSCI/zXsAAAAGcGF0YXR0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAA QI65p6wQznOE9Cf+NXVo7SxIvmUwzj/AdxXnb54k7JXQ7jLO1ZcwvF8MKhg0bK+dC3mxYAgn8fi /0m3i57JAqww= X-Developer-Key: i=josef@toxicpanda.com; a=openssh; fpr=SHA256:C8kOX2QUJCMqnCX+KEeoqRAjLo9L+ELOSH2NSAJHqGA create_user_ns() records in ns->parent_could_setfcap whether the creator had CAP_SETFCAP in its effective set, and verify_root_map() trusts that when a task in the new namespace maps the parent's uid 0. A bool only describes the parent, though. File capabilities belong to the kuid of a root user, and a namespace that maps the parent's uid 0 shares that kuid with the parent and possibly with ancestors further up, so what matters is how far up the creator's CAP_SETFCAP counted. Replace the bool with ns->setfcap_level: CAP_SETFCAP held in the namespace counts there and in its ancestors up to the one at that ->level. cap_setfcap_level() computes it from the creator's cred. A creator that holds CAP_SETFCAP passes on the setfcap_level of its own namespace, one that doesn't gets the new namespace's own level, and the initial namespace has 0. verify_root_map() keeps checking the same condition: the bool was false exactly when the level is the namespace's own. No functional change. Assisted-by: LLM Signed-off-by: Josef Bacik Reviewed-by: Bradley Morgan --- include/linux/capability.h | 3 +++ include/linux/user_namespace.h | 7 ++++--- kernel/user_namespace.c | 4 ++-- security/commoncap.c | 26 ++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/include/linux/capability.h b/include/linux/capability.h index 622137f66f09..026974a5111b 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -34,6 +34,7 @@ struct cpu_vfs_cap_data { #define _USER_CAP_HEADER_SIZE (sizeof(struct __user_cap_header_struct)) #define _KERNEL_CAP_T_SIZE (sizeof(kernel_cap_t)) =20 +struct cred; struct file; struct inode; struct dentry; @@ -222,4 +223,6 @@ int get_vfs_caps_from_disk(const struct mnt_idmap *idma= p, int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry, const void **ivalue, size_t size); =20 +int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns); + #endif /* !_LINUX_CAPABILITY_H */ diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 91232053775d..f3753b6b6d29 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -86,9 +86,10 @@ struct user_namespace { kgid_t group; struct ns_common ns; unsigned long flags; - /* parent_could_setfcap: true if the creator if this ns had CAP_SETFCAP - * in its effective capability set at the child ns creation time. */ - bool parent_could_setfcap; + /* setfcap_level: CAP_SETFCAP held in this ns counts here and in the + * ancestors up to the one at this ->level, see cap_setfcap_level(). + */ + int setfcap_level; =20 #ifdef CONFIG_KEYS /* List of joinable keyrings in this namespace. Modification access of diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 50c374ed4687..a5a94367600d 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -125,7 +125,7 @@ int create_user_ns(struct cred *new) if (!ns) goto fail_dec; =20 - ns->parent_could_setfcap =3D cap_raised(new->cap_effective, CAP_SETFCAP); + ns->setfcap_level =3D cap_setfcap_level(new, parent_ns); =20 ret =3D ns_common_init(ns); if (ret) @@ -921,7 +921,7 @@ static bool verify_root_map(const struct file *file, * the new namespace. Verify that the parent had CAP_SETFCAP * when it unshared. * */ - if (!file_ns->parent_could_setfcap) + if (file_ns->setfcap_level >=3D file_ns->level) return false; } else { /* Process p1 is writing to uid_map of p2, who is in a child diff --git a/security/commoncap.c b/security/commoncap.c index d47ab3022343..a51b9d51d96d 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -131,6 +131,32 @@ int cap_capable(const struct cred *cred, struct user_n= amespace *target_ns, return ret; } =20 +/** + * cap_setfcap_level - Determine how far up CAP_SETFCAP of a cred reaches + * @cred: The credentials to use + * @ns: The user namespace of @cred or one of its descendants + * + * File capabilities belong to the kuid of a namespace's root user, and the + * same kuid can be the root user of ancestors of that namespace. Every t= ask + * gets CAP_SETFCAP when it enters a user namespace, so having it there sa= ys + * nothing about those ancestors. user_ns->setfcap_level does: it is taken + * from the creator when the namespace is created. + * + * Return: the ->level of the topmost namespace, from @ns upwards, for whi= ch + * CAP_SETFCAP of @cred counts; @ns->level + 1 if it doesn't even over @ns. + */ +int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns) +{ + if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT)) + 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; +} + /** * cap_settime - Determine whether the current process may set the system = clock * @ts: The time to set --=20 2.55.0 From nobody Thu Oct 15 00:04:27 2026 Received: from mail-qv1-f45.google.com (mail-qv1-f45.google.com [209.85.219.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 265774A5EC0 for ; Wed, 14 Oct 2026 20:48:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010895; cv=none; b=sThc1w7T3brrHVRN8IsF3VvrjwKynUSKMtwa0wb5CWBPsT8RjMXD+MAYVwdUGjKPDob5A0oKEQcC0QA4zYGbAxnlXuw235tiaWE1e7MDAH2rr8hbaqPHEJC0cBCAnpr/wh1CoNCdAe9CI6hgfiCebs/1jvl4jslpmMfxrEvGMP4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010895; c=relaxed/simple; bh=SURLLXVKbdJe4oh+rQN5mZZp8FsoWoJcWswVlOyrRPg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ORgXLtSDfrFVTE5m2QhqD+yyx46Rs5R9HQWH8VO/UC5kPjE4KwzInDto0iCsXBaHvP9BelWQUuglgeWLlVGo/ABPZOenDhDXRb+a6FcNk+K45eFN0a4Z1jKXqaAJJAq9FdiZU4dSoevuX7hNvi9p+MmhrGkMGDxqi2sVK83IMWE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com; spf=pass smtp.mailfrom=toxicpanda.com; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b=WdnCVCEP; arc=none smtp.client-ip=209.85.219.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b="WdnCVCEP" Received: by mail-qv1-f45.google.com with SMTP id 6a1803df08f44-91ba702cceaso9078796d6.0 for ; Wed, 14 Oct 2026 13:48:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=toxicpanda.com; s=google; t=1792010892; x=1792615692; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=N6ZEVCcL2437a6X5ybbau3kMmqg+eTdm9EgDz7SIcRM=; b=WdnCVCEPeqZW2Sa9urGXg2u5AgBM6snzqtBrgzpZfZO3axleDkqEUX/fmSYU+IqKBP gTM08/X6eu/wtbLDFL+3JEEMAPz4WHgOEh6kVy8H4GVV60sxP89A8+2JaIKZwBA22rUK fkhqNINojrdTNGQ99N2wSm9yxqdJ6o1NMYQNr8cY7NGet51Plenm9anzcBv0yDG/4lAQ 2juXqCtZInGZaMvrXHyvaqFfSSAhZWq5vR/N1iPTiBN+3D7Os184B5Fku7FYJkvlxZdC jEw/QgwRxEJ4M1BWqxW5kzdNcI3XhOobXmcQhgwQqCzplCLzEmA5QimUrF1dLridAzmE KktA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792010892; x=1792615692; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=N6ZEVCcL2437a6X5ybbau3kMmqg+eTdm9EgDz7SIcRM=; b=LTnMhc7wpAVWe/QBTi7JkWU+zTYDj32Q/Svb7qm6KAeXL+N9fHxFsHK7VcDw++sNAj nfw7OMBQKdpZ9CwdPsL8Ms/CwDlIukqmSa6v8ace1GMHHltyR6hKAsM8iSs16O5sklw3 fCj1/PSikj7BNK5mZmMkBme9seaeWkagvMDbvU6ve8WmUaNGeKd77FukQcStdZxa+5m6 ouEeOhWqSOhG/EnRCEUtD2BD5aaouw+ruuQ1p39wUXTCLbSsOe1Njw9rAxFSGOuUQwRT GgpsTiH/twK2h4XeDNxWLb8e8NYJszhnTVgVMNouoVCH2v5RKOTNYB8+hrfddRki6EAL VEQQ== X-Forwarded-Encrypted: i=1; AKwUvBzfnRklHySDuIN9MnBMB7mK7o2MbyEytsMhnNrBq1ZeGXuEzBhUoCp03QwQuRd3RfPC5ENsQEDhuQ+n3nA=@vger.kernel.org X-Gm-Message-State: AFq9FYI5jW8basUiip9HAZKcYyHGZ0buZnuYOfT7K2y6W4jNHAUZTQqJ xrbDgyKbB+Dyu4R93rHDWB+8JuRwOXzMmwjuHNJjb1CuWI6FK/RSSPlR4WBUjqUPAn0= X-Gm-Gg: AYBFou0cnmZbjnP48y+TGgv3o8eZHGW8Jj8xwMXGal8MhvfLoIihgcSrwyp/oi77sS5 5Wb+RugxQFsgqZp0lwIfILFHwvAlUobf10agZETKi6hD4pNsp7RNYRTdNh8E+cQ/MExXf8YEN1g gRHkCz22sXx9+eaGOJqRoD+Dj2wlvy5zCJLhRJnDVQBkvOM3O7eAYVK9inR3uC0iSYDZrE3Opjz IbUjjqez1ryIZHFDE348Uk291PmQgtJUZbRpvlzQ+wE5DkKB3uChFlofEtfCCCgfo4okpASZcTT elt6knm7xii5rVkxXvfv0t3pWeOtvKo9EGaCEmxc8Kd9m8x/xZl8boJf8JG6Ac6PSLAuISbf187 ps36WIuxlNTjR/U7MbgURnH2puMKJmVdXKD0j1MxR+aLKOlSQYHCT+OQcpKqyXutAjKek3NCgPa 3v6fzQVnJqEaJmLZmacNIrh+BkFaMoa5iiUa5a4buDNPpLT2/UpsTZB/v+RaX37uu47QpvlZXw4 z1RTyaSwlGsGvgVMw9Sl/nUQn0= X-Received: by 2002:a05:620a:171e:b0:93b:d7a3:45d0 with SMTP id af79cd13be357-93ebd293de6mr2563371285a.57.1792010892437; Wed, 14 Oct 2026 13:48:12 -0700 (PDT) Received: from toxicpanda.com ([153.61.196.243]) by smtp.gmail.com with ESMTPSA id af79cd13be357-93ef5028051sm582408385a.18.2026.10.14.13.48.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 13:48:11 -0700 (PDT) From: Josef Bacik Date: Wed, 14 Oct 2026 20:47:47 +0000 Subject: [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP 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: quoted-printable Message-Id: <20261014-b4-setfcap-userns-v2-2-fac4d19281d7@toxicpanda.com> References: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> In-Reply-To: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> To: Serge Hallyn , Paul Moore , Christian Brauner Cc: James Morris , David Howells , Jarkko Sakkinen , "Andrew G. Morgan" , Serge Hallyn , linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-fsdevel@vger.kernel.org, Josef Bacik X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openssh-sha256; t=1792010880; l=2699; i=josef@toxicpanda.com; h=from:subject:message-id; bh=SURLLXVKbdJe4oh+rQN5mZZp8FsoWoJcWswVlOyrRPg=; b=U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgUBr36M/n0nWN0DNbnxwzIiCZez6MG JiruuNaSCI/zXsAAAAGcGF0YXR0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAA QCAe4dSbXbzqRr9RUIjQ/O/Vty7mTOCY6bKiZblvqs4grh9LSM730NY/0mu92ENvCx96B/KrSLf 2BGxLu2NhaQg= X-Developer-Key: i=josef@toxicpanda.com; a=openssh; fpr=SHA256:C8kOX2QUJCMqnCX+KEeoqRAjLo9L+ELOSH2NSAJHqGA Mapping uid 0 of the parent into a user namespace requires CAP_SETFCAP, because a task in such a namespace can write file capabilities that are honoured in the parent. A task that is already in the namespace has a full capability set there and can't be asked any more, so verify_root_map() goes by what the creator had, now ns->setfcap_level. But setns() lets other tasks in, and they get the full set as well: task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP unshare(CLONE_NEWUSER) setns(A's user ns) allowed, euid =3D=3D ns->owner write "0 0 1" to /proc/self/uid_map ns->setfcap_level is A's -> allowed or, if A's namespace maps uid 0 already, B sets security.capability on a file. Either way B ends up with file capabilities that are honoured in the initial namespace, which it could not have written there, nor in a namespace it created itself. Refuse setns() into a user namespace unless the joiner's CAP_SETFCAP reaches at least as far up as the namespace's setfcap_level. Then every task in a namespace is entitled to what its setfcap_level says, and the checks on the way to file capabilities can go by the namespace. This has to apply to every user namespace, also to those that don't map the parent's uid 0: through an idmapped mount, a root user that only exists in the namespace can be stored as the parent's. The change in behaviour is that a task without CAP_SETFCAP gets -EPERM when it tries to setns() into a user namespace created by a task that had it, such as a root-created container. Tasks with CAP_SETFCAP, and unprivileged users joining their own namespaces, are not affected. Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0") Assisted-by: LLM Signed-off-by: Josef Bacik Reviewed-by: Bradley Morgan --- kernel/user_namespace.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index a5a94367600d..330085a7d7d4 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -1390,6 +1390,13 @@ static int userns_install(struct nsset *nsset, struc= t ns_common *ns) if (!ns_capable(user_ns, CAP_SYS_ADMIN)) return -EPERM; =20 + /* Everybody in the namespace gets CAP_SETFCAP that reaches as far up + * as its setfcap_level, so only let in tasks whose own reaches as far. + */ + if (cap_setfcap_level(current_cred(), user_ns->parent) > + user_ns->setfcap_level) + return -EPERM; + cred =3D nsset_cred(nsset); if (!cred) return -EINVAL; --=20 2.55.0 From nobody Thu Oct 15 00:04:27 2026 Received: from mail-qt1-f177.google.com (mail-qt1-f177.google.com [209.85.160.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 11D6D485924 for ; Wed, 14 Oct 2026 20:48:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010898; cv=none; b=ZgmvMa6rTKEj3+1G5821XCnMUuNoXX4W1h8mzCpfpfl3NZgMnrHGKWlPw6eZOnj26o6Vd5UqG59GeH2nC2vC+0rWwVwnoCVJHA4abO+QaD97G2KUvEJXLyATAKcIdsXmfnrvfHFmAtWQDK/AaUIz+nolOIrIlM4KxQTAZNDboTk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010898; c=relaxed/simple; bh=8sz1j/DD6WxQjBo90lfB0OuhFUgi5AfzmKju90FziQw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=MZbUj8Y6cDS59e5FnuoqrCD6CQsJCG/tVUPbk+J4azMFztyTQ1mO0rlwUSsVficVgMVq50ZEDx2GLJLbycOOVtq6onzmo0LLhJp8gM/7jcqctGG2FuH3wYiPq+wlL+9m94p2uvlhWljX+8W+m+qOH9MojB0w+OvHiCFqMVICvqk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com; spf=pass smtp.mailfrom=toxicpanda.com; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b=Wy5Sepmy; arc=none smtp.client-ip=209.85.160.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b="Wy5Sepmy" Received: by mail-qt1-f177.google.com with SMTP id d75a77b69052e-53397554beaso8619921cf.3 for ; Wed, 14 Oct 2026 13:48:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=toxicpanda.com; s=google; t=1792010896; x=1792615696; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=3lyDYY+tJ26xvGx3zliM7a3edHKALJomeRr5oBEGsPg=; b=Wy5SepmylvBqlCCTQsBILkj59B6/BNNkODnAargT9Y7GtnNObSbCUwYpH6UblmJSGn iqcvNf4YQ86pehWD5oAUXvwTRM4KWfrxGq2FYuvLmHwz3FoeegDdcPHYuqm+HB5le2RO h3F/plElaqOuF/spDNTHJTdtXHwYpNLSNCYsonJuwQ0j1yoA5Y+XFfHf9MgZfbXHVhbn xSv7jzv/E9Hk/w8chxHUgt7Sz0XUn3Rt97By70TpLtnwW8d5ovmvqg/BPqvPg0DFavIy cu+ParL87s1Bv9g+8rzOQ1bvIjkF/LlF+7xa8L7tCEZB6oCoHgwWHboFgdQ+D3tEQOVz 0UPg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792010896; x=1792615696; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=3lyDYY+tJ26xvGx3zliM7a3edHKALJomeRr5oBEGsPg=; b=eFiwImww+7KLuuxiWj7VremW+FHG8axFh/QSId0od5iYY160bNqn4BI1a5E1H95Fzs UAJYen8W0pmYHjtD7/WYSlMkQObvTfw9KjZ1Z4BnwU3nMmakuo2JoOr/Z/kVpmPFpj5i +1bUupFj3xcjh31rSxxREknvceJ7TfRhI4XYSVqIIScBgB+FIM/GhY19TqQXYYxuf8k9 6cPgy34t7ekJ9dWW9Qbr7tWCy7UerqIHcAPs6qnOxEUze8rBLB/3fYZ2vE4nU04qRmHq VgZTdPrsFzWcxj+1g0j74rYxkN8QfyH3Z1rx9DF/tW4BVBXb04dfEiuzKYW/DCZOXqV9 x+nQ== X-Forwarded-Encrypted: i=1; AKwUvBw0maygOxWFEIwm/X/V9BIM/1pV7S2/tblH9VUho+n74jFCkSTDyXHLNmgdVuomg2OvMbj3lBnO7Nv4M3I=@vger.kernel.org X-Gm-Message-State: AFq9FYLZTdwIGk9YTtxFB82YExUqtYuWJhPyexlcTIQqvLjc3of2ppbj l29y3+ZJLkTMUbvO3laWTeHywZ/i40r4FIYpwuZv2cLNd6rw3GfK5UOgzyTZStcubO8AJqJi8/t ETFIbmPo= X-Gm-Gg: AYBFou0r+ulSVrO5ehJHsmONZqNK5oejDQ7N6FrRRK6uq4sWI7tFwlNdCJWXqK7uv2H 57yYn5fQ6hE+odgO1xKC6XMbYmqwPKggKG4wH/YHsZkToY5VrnCtMAD+WNMCNMIgOg0QhTq5Flr IMMECvqBDbYu+ngXO8oeWWAmDVHZyX+R/DpqcdfyVRPenGnYTbCjP4oNQGwpGk+UVClX4SNZdSb qbM8bjpyzMeRdiv05Ow/j+bgR//Q3g7nlU2jxD8Giu21kmOalPnCaUOuOsOR3f4Gml3NSYlh4JW tRTny2QV4iRQvrIWdwQ6wBUhMTYWdIE6VXHNvoN/F5A5femquMJadAi3tABz790uA9jgAtJYM7i 7jh+vrnJhuW0Uc1fGOak0bzFgi8HBPA5lg1vW97HM+hWRihGgdBjM0F2UTH5izfG5m0A8rhnYS1 WB24CaDisQG4A1UZdLLz/FTTlpvUxpJz4jBJR6ocfxuA0OQCHyP0wddf4AFshPRcvLysTXNPovS QWHFAQg8XTsmeM= X-Received: by 2002:a05:622a:5a11:b0:535:85e1:ed5c with SMTP id d75a77b69052e-5359fae84e8mr286672971cf.25.1792010894889; Wed, 14 Oct 2026 13:48:14 -0700 (PDT) Received: from toxicpanda.com ([153.61.196.246]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-5361279a933sm6502721cf.6.2026.10.14.13.48.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 13:48:14 -0700 (PDT) From: Josef Bacik Date: Wed, 14 Oct 2026 20:47:48 +0000 Subject: [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0 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: quoted-printable Message-Id: <20261014-b4-setfcap-userns-v2-3-fac4d19281d7@toxicpanda.com> References: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> In-Reply-To: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> To: Serge Hallyn , Paul Moore , Christian Brauner Cc: James Morris , David Howells , Jarkko Sakkinen , "Andrew G. Morgan" , Serge Hallyn , linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-fsdevel@vger.kernel.org, Josef Bacik X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openssh-sha256; t=1792010880; l=6333; i=josef@toxicpanda.com; h=from:subject:message-id; bh=8sz1j/DD6WxQjBo90lfB0OuhFUgi5AfzmKju90FziQw=; b=U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgUBr36M/n0nWN0DNbnxwzIiCZez6MG JiruuNaSCI/zXsAAAAGcGF0YXR0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAA QFLUc10A0ngb/khkJw2po+5G9mbQvHdY67VFdyeSnSkSkE4bIFSM2BO2ucK/miJ8BQvXnfFFQww F1pF2LK8TygM= X-Developer-Key: i=josef@toxicpanda.com; a=openssh; fpr=SHA256:C8kOX2QUJCMqnCX+KEeoqRAjLo9L+ELOSH2NSAJHqGA verify_root_map() looks at the cred that opened the map file: it asks whether it has CAP_SETFCAP over the parent, or, for an opener inside the namespace, whether the creator had. Two things are missing. The writer isn't checked. For every other privileged mapping new_idmap_permitted() wants the capability from the opener and from the task that calls write(), but a map that consists of the single line "0 0 1" takes the unprivileged branch, where only the opener's euid counts. So an open uid_map file is a token for mapping uid 0: task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP fd =3D open("/proc//uid_map") B gets fd by inheritance or SCM_RIGHTS write(fd, "0 0 1") opener had CAP_SETFCAP -> allowed And CAP_SETFCAP over the parent isn't always enough. The parent may map its own parent's uid 0, so its root user is the root user of ancestors further up, and the capability has to count there too. A task without CAP_SETFCAP that unshared a namespace, which a privileged task then mapped "0 0 1" for, has CAP_SETFCAP in it. It could unshare again and pass on uid 0 of the initial namespace one level further down. Find the topmost namespace that shares the parent's root user with cap_root_level(), and require that the opener and the writer both reach that far: by ns->setfcap_level for a task in the namespace that is being mapped, by cap_setfcap_level() for one outside it. This only affects maps that contain uid 0 of the parent namespace. A write by a task without CAP_SETFCAP to a file opened by one with it, and a map by a task whose CAP_SETFCAP stops short of the root user it passes on, now fail with -EPERM. Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0") Assisted-by: LLM Signed-off-by: Josef Bacik Reviewed-by: Bradley Morgan --- include/linux/capability.h | 1 + kernel/user_namespace.c | 29 +++++++++++++++++++++-------- security/commoncap.c | 23 +++++++++++++++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/include/linux/capability.h b/include/linux/capability.h index 026974a5111b..90f9f976d216 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -224,5 +224,6 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, st= ruct dentry *dentry, const void **ivalue, size_t size); =20 int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns); +int cap_root_level(kuid_t kuid, struct user_namespace *ns); =20 #endif /* !_LINUX_CAPABILITY_H */ diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 330085a7d7d4..7dba6594516d 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -888,8 +888,9 @@ EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_sort); * @new_map: requested idmap * * If a process requests mapping parent uid 0 into the new ns, verify that= the - * process writing the map had the CAP_SETFCAP capability as the target pr= ocess - * will be able to write fscaps that are valid in ancestor user namespaces. + * process that opened the map file and the process writing the map had the + * CAP_SETFCAP capability as the target process will be able to write fsca= ps + * that are valid in ancestor user namespaces. * * Return: true if the mapping is allowed, false if not. */ @@ -897,7 +898,7 @@ static bool verify_root_map(const struct file *file, struct user_namespace *map_ns, struct uid_gid_map *new_map) { - int idx; + int idx, level; const struct user_namespace *file_ns =3D file->f_cred->user_ns; struct uid_gid_extent *extent0 =3D NULL; =20 @@ -915,24 +916,36 @@ static bool verify_root_map(const struct file *file, if (!extent0) return true; =20 + /* The parent may in turn share its root user with its ancestors. */ + level =3D cap_root_level(make_kuid(map_ns->parent, 0), map_ns->parent); + if (map_ns =3D=3D 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 that root user goes. * */ - if (file_ns->setfcap_level >=3D 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 that root user goes. + */ + if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP) || + cap_setfcap_level(file->f_cred, map_ns->parent) > level) return false; } =20 - 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 =3D=3D current_user_ns()) + return map_ns->setfcap_level <=3D level; + + return ns_capable(map_ns->parent, CAP_SETFCAP) && + cap_setfcap_level(current_cred(), map_ns->parent) <=3D level; } =20 /* diff --git a/security/commoncap.c b/security/commoncap.c index a51b9d51d96d..f52f1c7a87be 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -157,6 +157,29 @@ int cap_setfcap_level(const struct cred *cred, struct = user_namespace *ns) return cred->user_ns->level + 1; } =20 +/** + * cap_root_level - Find the topmost namespace in which a kuid is the root= user + * @kuid: The kuid to look for + * @ns: The user namespace to start from + * + * Return: the lowest ->level among @ns and its ancestors in which @kuid is + * uid 0, which is how far up file capabilities with that root user are + * honoured; INT_MAX if there is no such namespace. + */ +int cap_root_level(kuid_t kuid, struct user_namespace *ns) +{ + int level =3D INT_MAX; + + for (;; ns =3D ns->parent) { + if (from_kuid(ns, kuid) =3D=3D 0) + level =3D ns->level; + if (ns =3D=3D &init_user_ns) + break; + } + + return level; +} + /** * cap_settime - Determine whether the current process may set the system = clock * @ts: The time to set --=20 2.55.0 From nobody Thu Oct 15 00:04:27 2026 Received: from mail-qv1-f49.google.com (mail-qv1-f49.google.com [209.85.219.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F12554BE43A for ; Wed, 14 Oct 2026 20:48:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010899; cv=none; b=hfNwVH777xdPFoMNsrAtYORyRpTYgKWGSSozqS7lNsy6D2TDTl+CKFsVo7L6UTSV+T3hCrHis3/moHZJwq4kXCI0MmcB264dotd9TvwKXUauCnbeSGPLtQilbsxpRIi1OlsLfTLzJSPrKRWUX8heiaOAOupTs3s9PwswG+9NI4Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010899; c=relaxed/simple; bh=yz/ljsX0zsmZtZUSzyzVMVFxg8xWAZZITJvfwDjnZoA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=OrDb/dGSIUtfXqUnJR7nPUPcSCfwK7QGWz0eoXxGcQzB5t0ejHMFN1OriLJYU7EVUOJzgtYBOnrCMej8lThoGXZUOIbzFdUhAsjefKxxZK7sL8P8ARTFOoSvGE/fdohLZfOG+sNPVooRcFq5jSr6kw5HnFcZRBbdnB6mlGmrlY0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com; spf=pass smtp.mailfrom=toxicpanda.com; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b=I2cAzsDJ; arc=none smtp.client-ip=209.85.219.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b="I2cAzsDJ" Received: by mail-qv1-f49.google.com with SMTP id 6a1803df08f44-917a707f37eso58192576d6.0 for ; Wed, 14 Oct 2026 13:48:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=toxicpanda.com; s=google; t=1792010897; x=1792615697; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=O6PJ6Vsu/6xTVjkvaAXTBm2OKjtFf/wfZcImncZuPSs=; b=I2cAzsDJpEiJeka7tG7Ps8mxGceI9GPSrOxZSiqBQQhM2mq7Lzs1LH573HWDBOcD8G ZFd0WExaCcp3URILazR/uIl54BOJsBIyr0Z8lW1L6VTc5Lza0IOBFHC5lfHjPtkkDK33 B5COJYucPOHnsedIKado0RLkCp7BWq3EWFNpnXNykkmb0PR7SyMp7jxgKLky5k0efNxj FrPCVw+LnQpunHDu6XEqzy8cTWW2BDDxV8DSiLfC36/Fael2vOUkJR84VhGr/yWxSXDH jZi8+P3BiKdTztuuJLe0ORfBqfg0pVSrSKROSZnIgVBh+3nrDNg7NP6gLi59mFbkPKkt Godw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792010897; x=1792615697; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=O6PJ6Vsu/6xTVjkvaAXTBm2OKjtFf/wfZcImncZuPSs=; b=Ct6dUZwQ1nkPN79zJ0aG7k2LQT1GnU+vR5/JUTRAadD6wv0Vo2/Th8krExFdBwFQwH QS+YxmqIVwubAm6Aqx1jFzRs0S3v5L/hrPKNtU+8T6C2H2YxZFUmmnX7lLV0sgHVS6m/ uSyctIbXV3QnfQvViQqq7wDuqC/r15qUouxB2qSD1+dZSb3YPaOJ7cC79ZCNEFjsG7zD zJcVzCQeT1fNlnt8NeSyvknBQblD8zTRJxm/Qk8EfjWGCAKuE7T+SOHoyu4MzcrsW5UG rvRb5frd5+cExeTKzDTbeaif0bXnCJnoaeaPRmPK3XBT0xEBDeCNv0vbo0YNVTNopWag ssuA== X-Forwarded-Encrypted: i=1; AKwUvBxKf9JjrDsO58RNF65esBKpTXwU/IjFgjTtpXzR7kFPnxLZmW92oGOuFuEdDp3jWkwTkZm8CBOklOzkzJc=@vger.kernel.org X-Gm-Message-State: AFq9FYJKaLsVVqeIC+hI+qzIqs11uvB5yZdVXU1AaFkYIbVbXbukx5TL 73dX94ZT3A2TViyiXxXEZfCTG3EYJKSVuOh4Ag5CHvINTnzF8pWm5LntfNP/nIIEW9ThRPzninp rOw0uCSg= X-Gm-Gg: AYBFou3QYwG92GcM6Eg/MgosgBhVgCosfU0lLNmoCX558MxPTWi5UOMnFBqxVq3UWc4 97jSC/mNtK9Lu++t9zD8nrMb7Om/FeBHFUwu8qTwxWoFe8qYuG6Sj2k6d8Zt1kWqvEIlzKQVbau +lFnlz7g/0Q/AE7sQIy/EDOduc/rB6kdtMOUyuiuMuQnWKf2F74deYQ+JyvhVBsR1DK6jK9rG+f ti6pUP8r1gNlrxP/QCSqFePBVDHcDhAA6R/dhmPPrNGqZDzFy6sYAotHDv1dvrALrQgVszLGjqu /1BpOSUHrHd0FxOOVakrutu/056bKXG6s8j0HGE2Ir14X6orxgGrk9zINfh1H3DFR+bCmIh4AdV 5Vq7UG74AQuhJUST0RG67N1Yw2xe4XnbVfJs3SyerINd699p7VwE3VuiOP+2TZNDenDYZNpUVGv uvmsYTMmZhRmfm2Dleo7lpo1CN9gGY8mesG1WB2QD0UDI+n3t099dabMAlUugB1qjBf97lFfRYW psGZCHdCTuzTSM= X-Received: by 2002:a05:620a:a7dd:10b0:93e:f0b9:505a with SMTP id af79cd13be357-93ef0b95671mr867809385a.58.1792010896728; Wed, 14 Oct 2026 13:48:16 -0700 (PDT) Received: from toxicpanda.com ([153.61.196.246]) by smtp.gmail.com with ESMTPSA id af79cd13be357-93ef50080aasm588523985a.14.2026.10.14.13.48.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 13:48:16 -0700 (PDT) From: Josef Bacik Date: Wed, 14 Oct 2026 20:47:49 +0000 Subject: [PATCH v2 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches 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: quoted-printable Message-Id: <20261014-b4-setfcap-userns-v2-4-fac4d19281d7@toxicpanda.com> References: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> In-Reply-To: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> To: Serge Hallyn , Paul Moore , Christian Brauner Cc: James Morris , David Howells , Jarkko Sakkinen , "Andrew G. Morgan" , Serge Hallyn , linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-fsdevel@vger.kernel.org, Josef Bacik X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openssh-sha256; t=1792010880; l=3016; i=josef@toxicpanda.com; h=from:subject:message-id; bh=yz/ljsX0zsmZtZUSzyzVMVFxg8xWAZZITJvfwDjnZoA=; b=U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgUBr36M/n0nWN0DNbnxwzIiCZez6MG JiruuNaSCI/zXsAAAAGcGF0YXR0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAA QItvq5f13UpqrDRjEY0E2EsO4PlV1hqDHH2LfE8qTXjZ/Prj/Fa55wgA+40Z7Y6Ksz5LPam3uTB 1mNaPRQBY1A4= X-Developer-Key: i=josef@toxicpanda.com; a=openssh; fpr=SHA256:C8kOX2QUJCMqnCX+KEeoqRAjLo9L+ELOSH2NSAJHqGA Commit db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0") keeps a root process without CAP_SETFCAP from creating a user namespace that maps uid 0, in which it could attach capabilities to a file that are then honoured outside. But the map doesn't have to be written by that process. A privileged task can write it from the parent, and the file capabilities are not checked against the outside at all: task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP unshare(CLONE_NEWUSER) ns->setfcap_level =3D 1 write "0 0 1" to B's uid_map, gid_map A has CAP_SETFCAP -> allowed setxattr(file, "security.capability") rootid =3D kuid 0 uid 1000 in the initial namespace: execve(file) file capabilities apply The map doesn't even have to show uid 0 of the parent as uid 0, since a v3 xattr can name any mapped uid as the root user. Check at the place where it matters, in cap_convert_nscap(). On an idmapped mount the root user named in the xattr has two identities: the kuid seen through this mount, and the kuid that is stored and seen through every other mount of the filesystem. If either of them is the root user of an ancestor of the caller's namespace too, CAP_SETFCAP has to reach up to there, which ns->setfcap_level tells. Tasks in the initial namespace, tasks in namespaces created by a task with CAP_SETFCAP, and file capabilities for a root user that only exists in namespaces within reach are not affected. That includes everything an unprivileged user does in its own namespaces. The rest gets -EPERM. Fixes: 8db6c34f1dbc ("Introduce v3 namespaced file capabilities") Assisted-by: LLM Signed-off-by: Josef Bacik --- security/commoncap.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/security/commoncap.c b/security/commoncap.c index f52f1c7a87be..2cae4354aeae 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -644,10 +644,24 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, = struct dentry *dentry, if (!vfsuid_valid(vfsrootid)) return -EINVAL; =20 + /* + * 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 =3D from_vfsuid(idmap, fs_ns, vfsrootid); if (!uid_valid(rootid)) return -EINVAL; =20 + if (cap_root_level(rootid, task_ns) < task_ns->setfcap_level) + return -EPERM; + nsrootid =3D from_kuid(fs_ns, rootid); if (nsrootid =3D=3D -1) return -EINVAL; --=20 2.55.0 From nobody Thu Oct 15 00:04:27 2026 Received: from mail-qv1-f50.google.com (mail-qv1-f50.google.com [209.85.219.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 753554C14E8 for ; Wed, 14 Oct 2026 20:48:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010902; cv=none; b=nKnWy3fe3GqcbE//ZKEnWgc8rDQkvUnIFEr1bMIEJzTuqvwtRNbc6BWYMBkRDWnRHs/zZKBBnYGH+s88kcKnyq5QymGq0/HTpQsnE2NTWa3TR7ApU40L3pTAYBtHkdWhWowmKd8iGCjP4RFP7VnCfpGADauO9wv5+rrKZujrRuw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792010902; c=relaxed/simple; bh=JxNh2ZjX4icAlvNneZTo89JakH8InNfiSrOt6oIkwl4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PfKMGzR9WWdHV6Q2RyobPn6Z3uIwTK9g3q899JMCWIzN5F1HJd6/CChfEPpS64tzNwlrxyU1Un4l1y15oQDye8xUXDCc8aMHAiMLQSRZBlxdfpx0LEwkoSSXKcTONoY2k43oEFvIZLzuu+Gdypo2tacVA0CRaOOoBpNJZaZghCk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com; spf=pass smtp.mailfrom=toxicpanda.com; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b=Ki9EmMGg; arc=none smtp.client-ip=209.85.219.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=toxicpanda.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=toxicpanda.com header.i=@toxicpanda.com header.b="Ki9EmMGg" Received: by mail-qv1-f50.google.com with SMTP id 6a1803df08f44-917a64f28d0so85928456d6.0 for ; Wed, 14 Oct 2026 13:48:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=toxicpanda.com; s=google; t=1792010899; x=1792615699; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=fVOOTjqsEAexejDaS34RRaZCsLjQoE9QZWSztQhKcEY=; b=Ki9EmMGgOHeJzFoCZtrU+J97n0ldQcNVhFDKHuc5dBfjFylCtqDZJOYC/WUq/9Qn6k a2TlnpFN88u+ddwVTyHwQx8/G9/a23Z0Kz+fQomrrA3voaC6RVg/TzhUjQ88Gxz3TI5q eXy/c2/EqbJbeipowayhAcVQIlnxFG/FsHoA4kS3lKP87v47jVqXilx4JGt6G81vcXxb 3Bw24PE8ZW3faEq9fu7jkGtoK32M6FMsTRfuEBndq/oIXaMAHxgfnyS1ClkSdJKik9lB w/BbYBMdCXUrbeG9p2DPpGWFHYpQDd5TOH7zjT3ZT93qPWswlhNE4MYcbBLREj/gf3GD lzAw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792010899; x=1792615699; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=fVOOTjqsEAexejDaS34RRaZCsLjQoE9QZWSztQhKcEY=; b=v0c8Pdp2Ca/an2fdRKX48T5yHII1+DkBZ0Ns2zfyxDbeC0RHAQ9C91zdHJoXXhHmbe RJbKFhLyk/MDcAEqhQBtJeY1quTHnqNCKmJwS0uO+LxP9r8/GE+Fq65WXfnUo5oopDHJ 43yNc3zlXX5SPg/P+xbLlCvIS4Imu3yLsOTVr07FqibGAPQ6GkXpAgQXDcjIxjLK4i9g epgA2pp1/m5ZyR3+4CHf27LkBiOZqxaa7K0qLcnxWrmNlABup5kKLSeceCZm4rH8Lp+q cc5ek551hMNVjHm1r3vyPwZ5f1PUvrHHrOOedX5xiT9Kg8SxNYhnR3QK02r5rDOjLEic rcaw== X-Forwarded-Encrypted: i=1; AKwUvBzzgjSMdDw8EFRkNcr1IJhb+j7e01c2dntRoNoiPl+CqNex4S+D3ZMHOnyjnjASwWWeKlXh7FH/EkAs6vg=@vger.kernel.org X-Gm-Message-State: AFq9FYLtPALyEE3Y0946NS0pN+TR9gKvkF58w6pL/fkWZmrP6siMPP3j HfWrlXYGU0YOdZnb1zUpIf5J5CRylDiS2gyhqlasstIhThuVHn0L6KOpy2SEg9+epr0= X-Gm-Gg: AYBFou3Cgu78ZRWb3XJKjiMZ+5HNXWKrfZpkSLCEonuOOl9bDgbbfeaAvOeAUgA+gkM PEsSNfl61Pu2mDoyA2B5wLrucMzvzNbl0dL56kzI3mWT/jDqJ5F+0fUTIRqG2kcxduaNuTIz9d+ 061icSuoOz76OphHgR+EiDUqG6tY365WPdv3RWstm33VmirBUior43Tb9hXY8m1/h+6KAq8XClW 6HgO8eTiB+VnwA0rD07GHiMvv7iUCmuhnuM6K1CrL87RKgrg7/5xeTJEG8iZ+zUf/LjggjM+5ve sImi6oe4MQn5jTzhm1p8pjKeolqU+eLRdBseZfWeHPub4u/dRDXPaIa3Anrm+Gz9wAIeBZ9o4c4 FaLxguk50hwV5wuEow59FLdKpUl/XJtL5170z/eMlTdaJ8g+fksQgUun6Hffoz2whQjMteu7zeU nr8rBnqxi+12AuuKl2sgjHq51M9QEPjyiQkYhlXKDoGn0buD0YIzbBWMbvgyGQawVoSzR8VaNUA Y/C8ldKuOb/gQUa X-Received: by 2002:a05:6214:808b:b0:91b:80ca:76dc with SMTP id 6a1803df08f44-91bb46d8e7emr26763046d6.6.1792010898952; Wed, 14 Oct 2026 13:48:18 -0700 (PDT) Received: from toxicpanda.com ([153.61.196.246]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-91ba3d63c70sm42906276d6.43.2026.10.14.13.48.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 13:48:18 -0700 (PDT) From: Josef Bacik Date: Wed, 14 Oct 2026 20:47:50 +0000 Subject: [PATCH v2 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP 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: quoted-printable Message-Id: <20261014-b4-setfcap-userns-v2-5-fac4d19281d7@toxicpanda.com> References: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> In-Reply-To: <20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com> To: Serge Hallyn , Paul Moore , Christian Brauner Cc: James Morris , David Howells , Jarkko Sakkinen , "Andrew G. Morgan" , Serge Hallyn , linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, keyrings@vger.kernel.org, linux-fsdevel@vger.kernel.org, Josef Bacik X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openssh-sha256; t=1792010880; l=6264; i=josef@toxicpanda.com; h=from:subject:message-id; bh=JxNh2ZjX4icAlvNneZTo89JakH8InNfiSrOt6oIkwl4=; b=U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgUBr36M/n0nWN0DNbnxwzIiCZez6MG JiruuNaSCI/zXsAAAAGcGF0YXR0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAA QKA9QkVT7IXAtcSIHZtMS/kvt0PL3EQLWnajEYduVEA3ULMH7lNRl8Ad5cqAc4AMwRJiZpuBOH+ lsomgepoHJgA= X-Developer-Key: i=josef@toxicpanda.com; a=openssh; fpr=SHA256:C8kOX2QUJCMqnCX+KEeoqRAjLo9L+ELOSH2NSAJHqGA Tasks in a user namespace created by a task with CAP_SETFCAP may map uid 0 of the parent and write file capabilities that are honoured there. That is the one privilege they keep outside of their namespace. setns() no longer lets anybody in who doesn't have it, but the ptrace checks don't know about it: for a target in another user namespace cap_ptrace_access_check() is satisfied with CAP_SYS_PTRACE over the target's namespace, which the owner rule hands to any task with the right euid. task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP unshare(CLONE_NEWUSER) ptrace(PTRACE_ATTACH, A) uids match, euid =3D=3D ns->owner make A write "0 0 1" to its uid_map, or set file capabilities A is entitled -> allowed The same goes for /proc//mem, process_vm_writev() and pidfd_getfd(). Require for PTRACE_MODE_ATTACH and PTRACE_TRACEME what setns() requires to get into the target's namespace: CAP_SETFCAP of the tracer has to reach as far up as the namespace's setfcap_level, or the tracer has CAP_SYS_PTRACE over the ancestor at that level, which gives control of tasks there that have CAP_SETFCAP anyway. Whether the namespace maps the root user of that ancestor doesn't matter, for the same reason as in setns(): through an idmapped mount its tasks can store file capabilities for it all the same. A tracer in the target's namespace got in with as much and always passes. Targets in the initial namespace and in namespaces created without CAP_SETFCAP (what unprivileged users create) are not affected. What remains is a tracer that gave up CAP_SETFCAP and CAP_SYS_PTRACE and a target in a namespace created by a task that didn't, such as a root-created container, which the tracer can't setns() into either. PTRACE_MODE_READ is unchanged. Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0") Assisted-by: LLM Signed-off-by: Josef Bacik --- security/commoncap.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++= ---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/security/commoncap.c b/security/commoncap.c index 2cae4354aeae..83cb5c9c3a25 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -195,14 +195,48 @@ int cap_settime(const struct timespec64 *ts, const st= ruct timezone *tz) return 0; } =20 +/* + * Tasks in a user namespace have CAP_SETFCAP that can count in ancestors = of + * the namespace, see cap_setfcap_level(). That is a privilege outside of= the + * namespace, so capabilities over the namespace are not enough to take + * control of such a task. + */ +static bool cap_covers_setfcap(const struct cred *cred, + const struct cred *child_cred) +{ + struct user_namespace *ns =3D child_cred->user_ns, *top; + + if (ns->setfcap_level >=3D ns->level) + return true; + + /* setns() only lets in tasks whose CAP_SETFCAP reaches as far. */ + if (cred->user_ns =3D=3D ns) + return true; + + /* + * Whether the namespace maps the root user of an ancestor doesn't + * matter: through an idmapped mount its tasks can store file + * capabilities for it anyway. So ask for as much as setns() does. + */ + for (top =3D ns->parent; top->level > ns->setfcap_level; top =3D top->par= ent) + ; + + /* CAP_SYS_PTRACE up there gives control of tasks with CAP_SETFCAP. */ + return cap_setfcap_level(cred, ns->parent) <=3D ns->setfcap_level || + !cap_capable(cred, top, CAP_SYS_PTRACE, CAP_OPT_NOAUDIT); +} + /** * cap_ptrace_access_check - Determine whether the current process may acc= ess * another * @child: The process to be accessed * @mode: The mode of attachment. * - * If we are in the same or an ancestor user_ns and have all the target - * task's capabilities, then ptrace access is allowed. + * For PTRACE_MODE_ATTACH, if the target task may make use of CAP_SETFCAP = in + * an ancestor of its user_ns that our CAP_SETFCAP or CAP_SYS_PTRACE doesn= 't + * reach, then ptrace access is denied. + * Otherwise, if we are in the same or an ancestor user_ns and have all the + * target task's capabilities, then ptrace access is allowed. * If we have the ptrace capability to the target user_ns, then ptrace * access is allowed. * Else denied. @@ -223,11 +257,15 @@ int cap_ptrace_access_check(struct task_struct *child= , unsigned int mode) caller_caps =3D &cred->cap_effective; else caller_caps =3D &cred->cap_permitted; + if ((mode & PTRACE_MODE_ATTACH) && + !cap_covers_setfcap(cred, child_cred)) + goto deny; if (cred->user_ns =3D=3D child_cred->user_ns && cap_issubset(child_cred->cap_permitted, *caller_caps)) goto out; if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE)) goto out; +deny: ret =3D -EPERM; out: rcu_read_unlock(); @@ -238,8 +276,11 @@ int cap_ptrace_access_check(struct task_struct *child,= unsigned int mode) * cap_ptrace_traceme - Determine whether another process may trace the cu= rrent * @parent: The task proposed to be the tracer * - * If parent is in the same or an ancestor user_ns and has all current's - * capabilities, then ptrace access is allowed. + * If current may make use of CAP_SETFCAP in an ancestor of its user_ns th= at + * parent's CAP_SETFCAP or CAP_SYS_PTRACE doesn't reach, then ptrace access + * is denied. + * Otherwise, if parent is in the same or an ancestor user_ns and has all + * current's capabilities, then ptrace access is allowed. * If parent has the ptrace capability to current's user_ns, then ptrace * access is allowed. * Else denied. @@ -255,11 +296,14 @@ int cap_ptrace_traceme(struct task_struct *parent) rcu_read_lock(); cred =3D __task_cred(parent); child_cred =3D current_cred(); + if (!cap_covers_setfcap(cred, child_cred)) + goto deny; if (cred->user_ns =3D=3D child_cred->user_ns && cap_issubset(child_cred->cap_permitted, cred->cap_permitted)) goto out; if (has_ns_capability(parent, child_cred->user_ns, CAP_SYS_PTRACE)) goto out; +deny: ret =3D -EPERM; out: rcu_read_unlock(); --=20 2.55.0