Re: [PATCH v3 2/4] selftests/landlock: Test LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS

Mickaël Salaün <[email protected]>
Newsgroups gmane.linux.kernel.lsm,gmane.linux.kernel
Message-ID <[email protected]>
On Mon, Aug 03, 2026 at 06:31:06PM -0400, Justin Suess wrote:
> Check that a successful landlock_restrict_self(2) call with
> LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS sets no_new_privs without a prior
> prctl(2) call nor CAP_SYS_ADMIN, that a failed call from both an
> invalid ruleset and hitting the layer maximum leaves the attribute
> unchanged, and that LANDLOCK_RESTRICT_SELF_TSYNC extends it to sibling
> threads.  Also check that this flag requires a ruleset, and update the
> restrict_self_checks_ordering EPERM checks since this flag is now
> checked before the flags validity.
> 
> Finally, rename restrict_self_fd_logging_flags to
> restrict_self_fd_flags, and restrict_self_logging_flags to
> restrict_self_flags to indicate that non-logging flags are now tested.
> 
> Update the ABI version and last-flag checks accordingly.
> 
> Signed-off-by: Justin Suess <[email protected]>
> ---
> 
> Notes:
>     v2->v3:
>     - Run clang-format
>     - Add max-layers tests (base_test and tsync_test) checking E2BIG and
>       that a failed call leaves no_new_privs unchanged
>     - Mention the test renames in the commit message
>     - Match comment style of surrounding tests
> 
>  tools/testing/selftests/landlock/base_test.c  | 99 ++++++++++++++++++-
>  tools/testing/selftests/landlock/tsync_test.c | 72 ++++++++++++++
>  2 files changed, 166 insertions(+), 5 deletions(-)
> 
> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> index cbd3c1669951..c8ed165a32ed 100644
> --- a/tools/testing/selftests/landlock/base_test.c
> +++ b/tools/testing/selftests/landlock/base_test.c
> @@ -76,7 +76,7 @@ TEST(abi_version)
>  	const struct landlock_ruleset_attr ruleset_attr = {
>  		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
>  	};
> -	ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
> +	ASSERT_EQ(11, landlock_create_ruleset(NULL, 0,
>  					      LANDLOCK_CREATE_RULESET_VERSION));
>  
>  	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
> @@ -255,8 +255,15 @@ TEST(restrict_self_checks_ordering)
>  
>  	/* Checks unprivileged enforcement without no_new_privs. */
>  	drop_caps(_metadata);
> -	ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
> +	ASSERT_EQ(-1, landlock_restrict_self(
> +			      -1, ~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
>  	ASSERT_EQ(EPERM, errno);
> +	/*
> +	 * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS fulfills the no_new_privs /
> +	 * CAP_SYS_ADMIN requirement, so the invalid flags are checked first.
> +	 */
> +	ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
> +	ASSERT_EQ(EINVAL, errno);
>  	ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
>  	ASSERT_EQ(EPERM, errno);
>  	ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0));
> @@ -277,6 +284,41 @@ TEST(restrict_self_checks_ordering)
>  	ASSERT_EQ(0, close(ruleset_fd));
>  }
>  
> +TEST(restrict_self_max_layers)
> +{
> +	const struct landlock_ruleset_attr ruleset_attr = {
> +		.handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
> +	};
> +	struct landlock_path_beneath_attr path_beneath_attr = {
> +		.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE,
> +		.parent_fd = -1,
> +	};
> +	const int ruleset_fd =
> +		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> +	ASSERT_LE(0, ruleset_fd);
> +
> +	path_beneath_attr.parent_fd =
> +		open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);
> +	ASSERT_LE(0, path_beneath_attr.parent_fd);
> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
> +				       &path_beneath_attr, 0));
> +	ASSERT_EQ(0, close(path_beneath_attr.parent_fd));
> +
> +	/* Enforces the maximum number of allowed layers. */
> +	for (int i = 0; i < LANDLOCK_MAX_NUM_LAYERS; i++)
> +		ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
> +
> +	/* Enforces one too many rulesets. */
> +	drop_caps(_metadata);
> +	ASSERT_EQ(-1, landlock_restrict_self(
> +			      ruleset_fd, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
> +	ASSERT_EQ(E2BIG, errno);
> +
> +	/* Checks that the failed call did not set no_new_privs. */
> +	ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +	ASSERT_EQ(0, close(ruleset_fd));
> +}
> +
>  TEST(restrict_self_fd)
>  {
>  	int fd;
> @@ -288,7 +330,7 @@ TEST(restrict_self_fd)
>  	EXPECT_EQ(EBADFD, errno);
>  }
>  
> -TEST(restrict_self_fd_logging_flags)
> +TEST(restrict_self_fd_flags)
>  {
>  	int fd;
>  
> @@ -302,11 +344,16 @@ TEST(restrict_self_fd_logging_flags)
>  	EXPECT_EQ(-1, landlock_restrict_self(
>  			      fd, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
>  	EXPECT_EQ(EBADFD, errno);
> +
> +	/* LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS requires a ruleset FD. */
> +	EXPECT_EQ(-1, landlock_restrict_self(
> +			      fd, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
> +	EXPECT_EQ(EBADFD, errno);
>  }
>  
> -TEST(restrict_self_logging_flags)
> +TEST(restrict_self_flags)
>  {
> -	const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC;
> +	const __u32 last_flag = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
>  
>  	/* Tests invalid flag combinations. */
>  
> @@ -349,6 +396,17 @@ TEST(restrict_self_logging_flags)
>  				      LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON));
>  	EXPECT_EQ(EBADF, errno);
>  
> +	/* LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS requires a ruleset FD. */
> +
> +	EXPECT_EQ(-1, landlock_restrict_self(
> +			      -1, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
> +	EXPECT_EQ(EBADF, errno);
> +
> +	EXPECT_EQ(-1, landlock_restrict_self(
> +			      -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF |
> +					  LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
> +	EXPECT_EQ(EBADF, errno);
> +
>  	/* Tests with an invalid ruleset_fd. */
>  
>  	EXPECT_EQ(-1, landlock_restrict_self(
> @@ -359,6 +417,37 @@ TEST(restrict_self_logging_flags)
>  			     -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
>  }
>  
> +TEST(restrict_self_no_new_privs)
> +{
> +	const struct landlock_ruleset_attr ruleset_attr = {
> +		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
> +	};
> +	const int ruleset_fd =
> +		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> +
> +	ASSERT_LE(0, ruleset_fd);
> +
> +	/*
> +	 * The calling thread does not need CAP_SYS_ADMIN nor an explicit
> +	 * prctl(2) PR_SET_NO_NEW_PRIVS call.
> +	 */
> +	drop_caps(_metadata);
> +	ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +
> +	/* Checks that a failed call does not set no_new_privs. */
> +	EXPECT_EQ(-1, landlock_restrict_self(
> +			      -1, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
> +	EXPECT_EQ(EBADF, errno);
> +	EXPECT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +
> +	/* Checks that a successful call sets no_new_privs. */
> +	ASSERT_EQ(0, landlock_restrict_self(
> +			     ruleset_fd, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
> +	EXPECT_EQ(1, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +
> +	EXPECT_EQ(0, close(ruleset_fd));
> +}
> +
>  TEST(ruleset_fd_io)
>  {
>  	struct landlock_ruleset_attr ruleset_attr = {
> diff --git a/tools/testing/selftests/landlock/tsync_test.c b/tools/testing/selftests/landlock/tsync_test.c
> index 9cf1491bbaaf..afa4a8222248 100644
> --- a/tools/testing/selftests/landlock/tsync_test.c
> +++ b/tools/testing/selftests/landlock/tsync_test.c
> @@ -90,6 +90,78 @@ TEST(multi_threaded_success)
>  	EXPECT_EQ(0, close(ruleset_fd));
>  }
>  
> +TEST(multi_threaded_no_new_privs)
> +{
> +	pthread_t t1, t2;
> +	bool no_new_privs1, no_new_privs2;
> +	const int ruleset_fd = create_ruleset(_metadata);
> +
> +	disable_caps(_metadata);
> +
> +	ASSERT_EQ(0, pthread_create(&t1, NULL, idle, &no_new_privs1));
> +	ASSERT_EQ(0, pthread_create(&t2, NULL, idle, &no_new_privs2));
> +
> +	/* No prior prctl(2) PR_SET_NO_NEW_PRIVS call. */
> +	ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +
> +	EXPECT_EQ(0, landlock_restrict_self(
> +			     ruleset_fd,
> +			     LANDLOCK_RESTRICT_SELF_TSYNC |
> +				     LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
> +
> +	EXPECT_EQ(1, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +
> +	ASSERT_EQ(0, pthread_cancel(t1));
> +	ASSERT_EQ(0, pthread_cancel(t2));
> +	ASSERT_EQ(0, pthread_join(t1, NULL));
> +	ASSERT_EQ(0, pthread_join(t2, NULL));
> +
> +	/* The no_new_privs flag was enabled on all threads. */
> +	EXPECT_TRUE(no_new_privs1);
> +	EXPECT_TRUE(no_new_privs2);
> +
> +	EXPECT_EQ(0, close(ruleset_fd));
> +}
> +
> +TEST(multi_threaded_no_new_privs_max_layers)
> +{
> +	pthread_t t1, t2;
> +	bool no_new_privs1, no_new_privs2;
> +	const int ruleset_fd = create_ruleset(_metadata);
> +
> +	/* Enforces the maximum number of allowed layers. */
> +	for (int i = 0; i < LANDLOCK_MAX_NUM_LAYERS; i++)
> +		ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
> +
> +	ASSERT_EQ(0, pthread_create(&t1, NULL, idle, &no_new_privs1));
> +	ASSERT_EQ(0, pthread_create(&t2, NULL, idle, &no_new_privs2));
> +
> +	disable_caps(_metadata);
> +
> +	/* No prior prctl(2) PR_SET_NO_NEW_PRIVS call. */
> +	ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +
> +	ASSERT_EQ(-1,
> +		  landlock_restrict_self(ruleset_fd,
> +					 LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS |
> +						 LANDLOCK_RESTRICT_SELF_TSYNC));
> +	ASSERT_EQ(E2BIG, errno);
> +
> +	/* Checks that the failed call did not set no_new_privs. */
> +	ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +
> +	ASSERT_EQ(0, pthread_cancel(t1));
> +	ASSERT_EQ(0, pthread_cancel(t2));
> +	ASSERT_EQ(0, pthread_join(t1, NULL));
> +	ASSERT_EQ(0, pthread_join(t2, NULL));
> +
> +	/* The no_new_privs flag was not enabled on any thread. */
> +	EXPECT_FALSE(no_new_privs1);
> +	EXPECT_FALSE(no_new_privs2);
> +
> +	ASSERT_EQ(0, close(ruleset_fd));
> +}

multi_threaded_{success,no_new_privs{,_max_layers} should be test
variants to factor out the code.

> +
>  TEST(multi_threaded_success_despite_diverging_domains)
>  {
>  	pthread_t t1, t2;
> -- 
> 2.54.0
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.