lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <aSXICxT6u0Rx1FhW@mail.hallyn.com>
Date: Tue, 25 Nov 2025 09:15:23 -0600
From: "Serge E. Hallyn" <serge@...lyn.com>
To: Ryan Foster <foster.ryan.r@...il.com>
Cc: serge@...lyn.com, paul@...l-moore.com,
	linux-security-module@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH v2] security: Rename functions and add namespace mapping
 tests

On Fri, Nov 21, 2025 at 09:48:26AM -0800, Ryan Foster wrote:
> Rename rootid_owns_currentns() to uid_owns_currentns() and
> rootid_owns_userns() to uid_owns_ns() for clarity, as the function checks
> any UID, not just root. Update all call sites accordingly.
> 
> Add tests that create actual user namespaces with different UID mappings
> to verify namespace traversal logic. The tests create namespaces where
> uid 0 maps to different kuids (e.g., kuid 1000, 2000) and verify that
> uid_owns_ns() correctly identifies ownership based on the namespace
> hierarchy traversal.
> 
> This addresses feedback to use clearer function naming and test actual
> namespace functionality with real user namespace creation and mappings,
> rather than just basic input validation.

Hi Ryan,

did you see https://lore.kernel.org/all/aR0JrOvDxDKZPELd@mail.hallyn.com ?

That is now in linux-next, and should be merged into 6.19 when that window
opens.  So please base your patch on that (so you can drop your uid_owns_ns()
renames).

I haven't looked closely at the tests, but at a cursory glance this is what
I had in mind, thanks!  I'll look more closely when you send next version.

> ---
>  security/commoncap.c      |  26 ++--
>  security/commoncap_test.c | 286 ++++++++++++++++++++++++++++++++------
>  2 files changed, 254 insertions(+), 58 deletions(-)
> 
> diff --git a/security/commoncap.c b/security/commoncap.c
> index 15d8147a34c4..cca291df9551 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -359,16 +359,16 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
>  }
>  
>  #ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST
> -bool rootid_owns_userns(struct user_namespace *ns, kuid_t kroot);
> -bool rootid_owns_userns(struct user_namespace *ns, kuid_t kroot)
> +bool uid_owns_ns(struct user_namespace *ns, kuid_t kuid);
> +bool uid_owns_ns(struct user_namespace *ns, kuid_t kuid)
>  #else
> -static bool rootid_owns_userns(struct user_namespace *ns, kuid_t kroot)
> +static bool uid_owns_ns(struct user_namespace *ns, kuid_t kuid)
>  #endif
>  {
>  	struct user_namespace *iter;
>  
>  	for (iter = ns;; iter = iter->parent) {
> -		if (from_kuid(iter, kroot) == 0)
> +		if (from_kuid(iter, kuid) == 0)
>  			return true;
>  		if (iter == &init_user_ns)
>  			break;
> @@ -378,19 +378,19 @@ static bool rootid_owns_userns(struct user_namespace *ns, kuid_t kroot)
>  }
>  
>  #ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST
> -bool rootid_owns_currentns(vfsuid_t rootvfsuid);
> -bool rootid_owns_currentns(vfsuid_t rootvfsuid)
> +bool uid_owns_currentns(vfsuid_t vfsuid);
> +bool uid_owns_currentns(vfsuid_t vfsuid)
>  #else
> -static bool rootid_owns_currentns(vfsuid_t rootvfsuid)
> +static bool uid_owns_currentns(vfsuid_t vfsuid)
>  #endif
>  {
> -	kuid_t kroot;
> +	kuid_t kuid;
>  
> -	if (!vfsuid_valid(rootvfsuid))
> +	if (!vfsuid_valid(vfsuid))
>  		return false;
>  
> -	kroot = vfsuid_into_kuid(rootvfsuid);
> -	return rootid_owns_userns(current_user_ns(), kroot);
> +	kuid = vfsuid_into_kuid(vfsuid);
> +	return uid_owns_ns(current_user_ns(), kuid);
>  }
>  
>  static __u32 sansflags(__u32 m)
> @@ -497,7 +497,7 @@ int cap_inode_getsecurity(struct mnt_idmap *idmap,
>  		goto out_free;
>  	}
>  
> -	if (!rootid_owns_currentns(vfsroot)) {
> +	if (!uid_owns_currentns(vfsroot)) {
>  		size = -EOVERFLOW;
>  		goto out_free;
>  	}
> @@ -738,7 +738,7 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
>  	/* Limit the caps to the mounter of the filesystem
>  	 * or the more limited uid specified in the xattr.
>  	 */
> -	if (!rootid_owns_currentns(rootvfsuid))
> +	if (!uid_owns_currentns(rootvfsuid))
>  		return -ENODATA;
>  
>  	cpu_caps->permitted.val = le32_to_cpu(caps->data[0].permitted);
> diff --git a/security/commoncap_test.c b/security/commoncap_test.c
> index 962aa899455d..7f066dc0df5d 100644
> --- a/security/commoncap_test.c
> +++ b/security/commoncap_test.c
> @@ -10,6 +10,8 @@
>  #include <linux/user_namespace.h>
>  #include <linux/uidgid.h>
>  #include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/refcount.h>
>  
>  /* Forward declare types and functions we need from mnt_idmapping.h
>   * We avoid including the full header because it contains inline functions
> @@ -50,38 +52,38 @@ static inline kuid_t vfsuid_into_kuid(vfsuid_t vfsuid)
>  #ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST
>  
>  /* Forward declarations - functions are exported when KUNIT_TEST is enabled */
> -extern bool rootid_owns_userns(struct user_namespace *ns, kuid_t kroot);
> -extern bool rootid_owns_currentns(vfsuid_t rootvfsuid);
> +extern bool uid_owns_ns(struct user_namespace *ns, kuid_t kuid);
> +extern bool uid_owns_currentns(vfsuid_t vfsuid);
>  
>  /**
> - * test_rootid_owns_currentns_init_ns - Test rootid_owns_currentns with init ns
> + * test_uid_owns_currentns_init_ns - Test uid_owns_currentns with init ns
>   *
> - * Verifies that a root ID in the init namespace correctly owns the current
> + * Verifies that UID 0 in the init namespace correctly owns the current
>   * namespace when running in init_user_ns.
>   *
>   * @test: KUnit test context
>   */
> -static void test_rootid_owns_currentns_init_ns(struct kunit *test)
> +static void test_uid_owns_currentns_init_ns(struct kunit *test)
>  {
> -	vfsuid_t root_vfsuid;
> -	kuid_t root_kuid;
> +	vfsuid_t vfsuid;
> +	kuid_t kuid;
>  
> -	/* Create a root UID in init namespace */
> -	root_kuid = KUIDT_INIT(0);
> -	root_vfsuid = VFSUIDT_INIT(root_kuid);
> +	/* Create UID 0 in init namespace */
> +	kuid = KUIDT_INIT(0);
> +	vfsuid = VFSUIDT_INIT(kuid);
>  
> -	/* In init namespace, root should own current namespace */
> -	KUNIT_EXPECT_TRUE(test, rootid_owns_currentns(root_vfsuid));
> +	/* In init namespace, UID 0 should own current namespace */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_currentns(vfsuid));
>  }
>  
>  /**
> - * test_rootid_owns_currentns_invalid - Test rootid_owns_currentns with invalid vfsuid
> + * test_uid_owns_currentns_invalid - Test uid_owns_currentns with invalid vfsuid
>   *
>   * Verifies that an invalid vfsuid correctly returns false.
>   *
>   * @test: KUnit test context
>   */
> -static void test_rootid_owns_currentns_invalid(struct kunit *test)
> +static void test_uid_owns_currentns_invalid(struct kunit *test)
>  {
>  	vfsuid_t invalid_vfsuid;
>  
> @@ -89,74 +91,268 @@ static void test_rootid_owns_currentns_invalid(struct kunit *test)
>  	invalid_vfsuid = INVALID_VFSUID;
>  
>  	/* Invalid vfsuid should return false */
> -	KUNIT_EXPECT_FALSE(test, rootid_owns_currentns(invalid_vfsuid));
> +	KUNIT_EXPECT_FALSE(test, uid_owns_currentns(invalid_vfsuid));
>  }
>  
>  /**
> - * test_rootid_owns_currentns_nonroot - Test rootid_owns_currentns with non-root UID
> + * test_uid_owns_currentns_nonzero - Test uid_owns_currentns with non-zero UID
>   *
> - * Verifies that a non-root UID correctly returns false.
> + * Verifies that a non-zero UID correctly returns false.
>   *
>   * @test: KUnit test context
>   */
> -static void test_rootid_owns_currentns_nonroot(struct kunit *test)
> +static void test_uid_owns_currentns_nonzero(struct kunit *test)
>  {
> -	vfsuid_t nonroot_vfsuid;
> -	kuid_t nonroot_kuid;
> +	vfsuid_t vfsuid;
> +	kuid_t kuid;
>  
> -	/* Create a non-root UID */
> -	nonroot_kuid = KUIDT_INIT(1000);
> -	nonroot_vfsuid = VFSUIDT_INIT(nonroot_kuid);
> +	/* Create a non-zero UID */
> +	kuid = KUIDT_INIT(1000);
> +	vfsuid = VFSUIDT_INIT(kuid);
>  
> -	/* Non-root UID should return false */
> -	KUNIT_EXPECT_FALSE(test, rootid_owns_currentns(nonroot_vfsuid));
> +	/* Non-zero UID should return false */
> +	KUNIT_EXPECT_FALSE(test, uid_owns_currentns(vfsuid));
>  }
>  
>  /**
> - * test_rootid_owns_userns_init_ns - Test rootid_owns_userns with init namespace
> + * test_uid_owns_ns_init_ns_uid0 - Test uid_owns_ns with init namespace and UID 0
>   *
> - * Verifies that rootid_owns_userns correctly identifies root UID in init namespace.
> - * This tests the core namespace traversal logic.
> + * Verifies that uid_owns_ns correctly identifies UID 0 in init namespace.
> + * This tests the core namespace traversal logic. In init namespace, UID 0
> + * maps to itself, so it should own the namespace.
>   *
>   * @test: KUnit test context
>   */
> -static void test_rootid_owns_userns_init_ns(struct kunit *test)
> +static void test_uid_owns_ns_init_ns_uid0(struct kunit *test)
>  {
> -	kuid_t root_kuid;
> +	kuid_t kuid;
>  	struct user_namespace *init_ns;
>  
> -	root_kuid = KUIDT_INIT(0);
> +	kuid = KUIDT_INIT(0);
>  	init_ns = &init_user_ns;
>  
> -	/* Root UID should own init namespace */
> -	KUNIT_EXPECT_TRUE(test, rootid_owns_userns(init_ns, root_kuid));
> +	/* UID 0 should own init namespace */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(init_ns, kuid));
>  }
>  
>  /**
> - * test_rootid_owns_userns_nonroot - Test rootid_owns_userns with non-root UID
> + * test_uid_owns_ns_init_ns_nonzero - Test uid_owns_ns with init namespace and non-zero UID
>   *
> - * Verifies that rootid_owns_userns correctly rejects non-root UIDs.
> + * Verifies that uid_owns_ns correctly rejects non-zero UIDs in init namespace.
> + * Only UID 0 should own a namespace.
>   *
>   * @test: KUnit test context
>   */
> -static void test_rootid_owns_userns_nonroot(struct kunit *test)
> +static void test_uid_owns_ns_init_ns_nonzero(struct kunit *test)
>  {
> -	kuid_t nonroot_kuid;
> +	kuid_t kuid;
>  	struct user_namespace *init_ns;
>  
> -	nonroot_kuid = KUIDT_INIT(1000);
> +	kuid = KUIDT_INIT(1000);
>  	init_ns = &init_user_ns;
>  
> -	/* Non-root UID should not own namespace */
> -	KUNIT_EXPECT_FALSE(test, rootid_owns_userns(init_ns, nonroot_kuid));
> +	/* Non-zero UID should not own namespace */
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(init_ns, kuid));
> +}
> +
> +/**
> + * test_uid_owns_ns_init_ns_various_uids - Test uid_owns_ns with various UIDs
> + *
> + * Verifies that uid_owns_ns correctly identifies only UID 0 as owning
> + * the namespace, regardless of the UID value tested.
> + *
> + * @test: KUnit test context
> + */
> +static void test_uid_owns_ns_init_ns_various_uids(struct kunit *test)
> +{
> +	struct user_namespace *init_ns;
> +	kuid_t kuid;
> +
> +	init_ns = &init_user_ns;
> +
> +	/* UID 0 should own the namespace */
> +	kuid = KUIDT_INIT(0);
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(init_ns, kuid));
> +
> +	/* Other UIDs should not own the namespace */
> +	kuid = KUIDT_INIT(1);
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(init_ns, kuid));
> +
> +	kuid = KUIDT_INIT(1000);
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(init_ns, kuid));
> +
> +	kuid = KUIDT_INIT(65534);
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(init_ns, kuid));
> +}
> +
> +/**
> + * create_test_user_ns_with_mapping - Create a test user namespace with uid mapping
> + *
> + * Creates a minimal user namespace for testing where uid 0 in the namespace
> + * maps to the specified kuid in the parent namespace.
> + *
> + * The mapping semantics:
> + * - first: uid in this namespace (0)
> + * - lower_first: kuid in parent namespace (mapped_kuid)
> + * - count: range size (1)
> + *
> + * This means: from_kuid(ns, mapped_kuid) will return 0
> + * because map_id_up looks for kuid in [lower_first, lower_first+count)
> + * and returns first + (kuid - lower_first) = 0 + (mapped_kuid - mapped_kuid) = 0
> + *
> + * @test: KUnit test context
> + * @parent_ns: Parent user namespace
> + * @mapped_kuid: The kuid that uid 0 in the new namespace maps to
> + *
> + * Returns: The new user namespace, or NULL on failure
> + */
> +static struct user_namespace *create_test_user_ns_with_mapping(struct kunit *test,
> +								struct user_namespace *parent_ns,
> +								kuid_t mapped_kuid)
> +{
> +	struct user_namespace *ns;
> +	struct uid_gid_extent extent;
> +
> +	/* Allocate a test namespace - use kzalloc to zero all fields */
> +	ns = kunit_kzalloc(test, sizeof(*ns), GFP_KERNEL);
> +	if (!ns)
> +		return NULL;
> +
> +	/* Initialize basic namespace structure fields */
> +	ns->parent = parent_ns;
> +	ns->level = parent_ns ? parent_ns->level + 1 : 0;
> +	ns->owner = mapped_kuid;
> +	ns->group = KGIDT_INIT(0);
> +
> +	/* Initialize ns_common structure */
> +	refcount_set(&ns->ns.__ns_ref, 1);
> +
> +	/* Set up uid mapping: uid 0 in this namespace maps to mapped_kuid in parent
> +	 * Format: first (uid in ns) : lower_first (kuid in parent) : count
> +	 * So: uid 0 in ns -> kuid mapped_kuid in parent
> +	 * This means from_kuid(ns, mapped_kuid) returns 0
> +	 */
> +	extent.first = 0;                              /* uid 0 in this namespace */
> +	extent.lower_first = __kuid_val(mapped_kuid);  /* maps to this kuid in parent */
> +	extent.count = 1;
> +
> +	ns->uid_map.extent[0] = extent;
> +	ns->uid_map.nr_extents = 1;
> +
> +	/* Set up gid mapping: gid 0 maps to gid 0 in parent (simplified) */
> +	extent.first = 0;
> +	extent.lower_first = 0;
> +	extent.count = 1;
> +
> +	ns->gid_map.extent[0] = extent;
> +	ns->gid_map.nr_extents = 1;
> +
> +	return ns;
> +}
> +
> +/**
> + * test_uid_owns_ns_with_mapping - Test uid_owns_ns with namespace where uid 0
> + *				   maps to different kuid
> + *
> + * Creates a user namespace where uid 0 maps to kuid 1000 in the parent namespace.
> + * Verifies that uid_owns_ns correctly identifies kuid 1000 as owning the namespace.
> + *
> + * Note: uid_owns_ns walks up the namespace hierarchy, so it checks the current
> + * namespace first, then parent, then parent's parent, etc. So:
> + * - kuid 1000 owns test_ns because from_kuid(test_ns, 1000) == 0
> + * - kuid 0 also owns test_ns because from_kuid(init_user_ns, 0) == 0
> + *   (checked in parent)
> + *
> + * This tests the actual functionality as requested: creating namespaces with
> + * different values for the namespace's uid 0.
> + *
> + * @test: KUnit test context
> + */
> +static void test_uid_owns_ns_with_mapping(struct kunit *test)
> +{
> +	struct user_namespace *test_ns;
> +	struct user_namespace *parent_ns;
> +	kuid_t mapped_kuid, other_kuid;
> +
> +	parent_ns = &init_user_ns;
> +	mapped_kuid = KUIDT_INIT(1000);  /* uid 0 in test_ns maps to kuid 1000 */
> +	other_kuid = KUIDT_INIT(2000);   /* This should not own the namespace */
> +
> +	/* Create test namespace where uid 0 maps to kuid 1000 */
> +	test_ns = create_test_user_ns_with_mapping(test, parent_ns, mapped_kuid);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_ns);
> +
> +	/* kuid 1000 should own the namespace (because uid 0 in test_ns maps to it) */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(test_ns, mapped_kuid));
> +
> +	/* kuid 0 also owns the namespace because it maps to 0 in init_user_ns (parent) */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(test_ns, KUIDT_INIT(0)));
> +
> +	/* Other kuids that don't map to 0 in test_ns or any parent should not own */
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(test_ns, other_kuid));
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(test_ns, KUIDT_INIT(500)));
> +}
> +
> +/**
> + * test_uid_owns_ns_with_different_mappings - Test with multiple namespaces
> + *					      having different mappings
> + *
> + * Creates multiple test namespaces with different uid 0 mappings to verify
> + * the function correctly identifies ownership based on the mapping.
> + *
> + * Since uid_owns_ns walks up the hierarchy, kuids that map to 0 in init_user_ns
> + * (like kuid 0) will own all namespaces. But we can still verify that the
> + * specific mapped kuids own their respective namespaces.
> + *
> + * @test: KUnit test context
> + */
> +static void test_uid_owns_ns_with_different_mappings(struct kunit *test)
> +{
> +	struct user_namespace *ns1, *ns2, *ns3;
> +	struct user_namespace *parent_ns;
> +
> +	parent_ns = &init_user_ns;
> +
> +	/* Namespace 1: uid 0 maps to kuid 1000 */
> +	ns1 = create_test_user_ns_with_mapping(test, parent_ns, KUIDT_INIT(1000));
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns1);
> +	/* kuid 1000 owns ns1 because it maps to uid 0 in ns1 */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(ns1, KUIDT_INIT(1000)));
> +	/* kuid 0 also owns ns1 because it maps to 0 in init_user_ns (parent) */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(ns1, KUIDT_INIT(0)));
> +	/* kuid 2000 doesn't map to 0 in ns1 or any parent */
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(ns1, KUIDT_INIT(2000)));
> +
> +	/* Namespace 2: uid 0 maps to kuid 2000 */
> +	ns2 = create_test_user_ns_with_mapping(test, parent_ns, KUIDT_INIT(2000));
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns2);
> +	/* kuid 2000 owns ns2 because it maps to uid 0 in ns2 */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(ns2, KUIDT_INIT(2000)));
> +	/* kuid 0 also owns ns2 because it maps to 0 in init_user_ns (parent) */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(ns2, KUIDT_INIT(0)));
> +	/* kuid 1000 doesn't map to 0 in ns2 or any parent */
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(ns2, KUIDT_INIT(1000)));
> +
> +	/* Namespace 3: uid 0 maps to kuid 0 (identity mapping) */
> +	ns3 = create_test_user_ns_with_mapping(test, parent_ns, KUIDT_INIT(0));
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns3);
> +	/* kuid 0 owns ns3 because it maps to uid 0 in ns3 */
> +	KUNIT_EXPECT_TRUE(test, uid_owns_ns(ns3, KUIDT_INIT(0)));
> +	/* kuid 1000 doesn't map to 0 in ns3 or any parent */
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(ns3, KUIDT_INIT(1000)));
> +	/* kuid 2000 doesn't map to 0 in ns3 or any parent */
> +	KUNIT_EXPECT_FALSE(test, uid_owns_ns(ns3, KUIDT_INIT(2000)));
>  }
>  
>  static struct kunit_case commoncap_test_cases[] = {
> -	KUNIT_CASE(test_rootid_owns_currentns_init_ns),
> -	KUNIT_CASE(test_rootid_owns_currentns_invalid),
> -	KUNIT_CASE(test_rootid_owns_currentns_nonroot),
> -	KUNIT_CASE(test_rootid_owns_userns_init_ns),
> -	KUNIT_CASE(test_rootid_owns_userns_nonroot),
> +	KUNIT_CASE(test_uid_owns_currentns_init_ns),
> +	KUNIT_CASE(test_uid_owns_currentns_invalid),
> +	KUNIT_CASE(test_uid_owns_currentns_nonzero),
> +	KUNIT_CASE(test_uid_owns_ns_init_ns_uid0),
> +	KUNIT_CASE(test_uid_owns_ns_init_ns_nonzero),
> +	KUNIT_CASE(test_uid_owns_ns_init_ns_various_uids),
> +	KUNIT_CASE(test_uid_owns_ns_with_mapping),
> +	KUNIT_CASE(test_uid_owns_ns_with_different_mappings),
>  	{}
>  };
>  
> -- 
> 2.43.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ