From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-yx1-f41.google.com (mail-yx1-f41.google.com [74.125.224.41]) (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 3FABA28CF5D for ; Sun, 9 Aug 2026 15:45:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.41 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786290355; cv=none; b=iRsgUOJKmrBEQzVz6MUPHADCZS0c17I99NqrgncYHYgPLYcs8fKDPF+o3bnFYaLxyx6fGKdRX/qvXV8Ev8K2f5NdjgwGfPY86PygHWSSXg7ZwplbpldqBi+/wnYp9bxv7xcx1dqsvogN/K/35WQTsz5fXAepAPsUS7hnJ1ryPcs= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786290355; c=relaxed/simple; bh=PP74AEuJV6z4so5ONmbbCZB2EUZwnxJ0x3ltSyo07Uk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=fd9cHIWLFAmYvnudmFxftv8dqjj7Ne90IW3Mq1GePFHOauc0E9AtYnwepGXLnMy+wYHtzn3Irue4x1CKAatuN4wmoQX8/vFi19nfMxmC0Yea7oeozJVm5XxpcQLd8NG89Rr2TKD4Qg8vxX3dCRsQT8a1J8sd78AhK7b1J/C+f1k= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=plk4ljHI; arc=none smtp.client-ip=74.125.224.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="plk4ljHI" Received: by mail-yx1-f41.google.com with SMTP id 956f58d0204a3-664ce3000e6so696326d50.0 for ; Sun, 09 Aug 2026 08:45:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786290353; x=1786895153; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=028iBPYlb+T5xtatdfaP1GUdKAs7WH+fsWa8mh5VDGI=; b=plk4ljHILn3tkrHx9LNkkTR5pSxH5LNag/MPPMGgOq23F/LuKq1zaFQLrVxND8Dmgm oQ9ItZUPuqeP/Vdauh9Z1mzJ8ZIkUelRL033utCyA4V5YZM684OtVRDCvaZ/jfdEEXMK rDPqjnpH4pT4/fcHVVT4rwLd1NjO2kvHSye5UYUUlZr1DE5t2gWiAmps24IqKMhvDeKg W3xkANREbOrsWHIl0h9U3p4BrT2mBfOj2GgZNXSOL4S6wE/sw06qFzUBECSny16vigAv nfYtkijwflmytemb+qJKWf84FgtpJdaKtp3CiTK8Pk6y7+/oKiWYiZWgcc9A5cCQjdmt q1PA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786290353; x=1786895153; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=028iBPYlb+T5xtatdfaP1GUdKAs7WH+fsWa8mh5VDGI=; b=cm2BKXQ4UOt8EjKBZNMYMoxwmehTnRK/JWNMlKCp2T6PoRnfhJCVVuKU+9P014M/M6 DoygkkFcYUZ+VVhwjrAj/em5VTzrQ7UI+bSZ6M7b6DJhbQIN97z4t3RnVqyYjTuP5P2g JzhdXzlhPnNiVuvOiD6t4mRVuZrFwb6fz7tr9vbDJ3znbjIjXZsAC/IUlMXBy/462XXN Dj6f/2h2fPyVdTpO5olC+yxwWGPJ5U9pl1sQOLkPWhuEl2PpuWqETB8HTW7v95qsHe+e OVcQcDxC9dSf2W5hXGl90KkaoJ+iuMWUvQFnilLT9O7TB/SjAUq6J+qlRWl34E6EiNRQ xT9g== X-Gm-Message-State: AOJu0Yz9nER0fdZ1x23wFRtpQwzREdqX7QJ/axu0Fno1Sjw1VI7Rc1OG +CSw+qr9M5/MF56dW1Z1M9n54XpZTcojhhdsLy0QHRQ85ejrPXtCDA4z X-Gm-Gg: AR+sD11mzzLIKnHnp0svhSG9KS6VPz47bdVsOYDQrhf7upJHDVbZr/ORhL78S61lqOc 4bMxzv8asTXztLjmZo7OqMKE8OlrOlPGgGyMPA9EO2Lzpwh4EpKYsjrb5mr4+sY0jpYYu9cFh6Z WQaClLZ+hWeIgpPOVleo3BDcnsaNlW1Vbq3Plu0EZTPGLZXyli94vUK4MpeNUizoAF8Bl5aCrF7 raOivPkbfd3ewPp2chBIlyw2b1j+H9tmfFVl56sv8gHIqNVZ4Wm5J+cm1GAzs1MeP9/Mbl4kARi t6lm7HVZE5UUXDXDdw09u1xUkSASTws41c3Lg63vQMotIKbPbvWR9zmgDiy3/ZAr0CoaQxNLaYm oYkRwYKI89hhFdWKNaOgJW66M4nVPTOpz2I/bm0BkHPQY2RoahLGnHjuiC24nT9aiHWIRrlFXNp 58+N5WzmF1op6s5gUYDzQUKsSOV6NT4TXl+rK1gA6W2nLk2jVgF6r/jp/ERICSP2zIQJGDLV5IF rovbTE04pFrtJ9SlS1C3Kg= X-Received: by 2002:a05:690e:480d:b0:668:90cd:8aef with SMTP id 956f58d0204a3-66acd2f1bf7mr6955238d50.25.1786290352849; Sun, 09 Aug 2026 08:45:52 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:4665:53b0:3ac9:3545]) by smtp.gmail.com with ESMTPSA id 956f58d0204a3-66acacdbea9sm4838001d50.3.2026.08.09.08.45.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 09 Aug 2026 08:45:52 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v4 3/5] selftests/landlock: Test LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Date: Sun, 9 Aug 2026 11:45:21 -0400 Message-ID: <20260809154544.1253100-4-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260809154544.1253100-1-utilityemal77@gmail.com> References: <20260809154544.1253100-1-utilityemal77@gmail.com> 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: 8bit 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. Turn the multi_threaded_success test into a multi_threaded fixture with success, no_new_privs, and no_new_privs_max_layers variants to factor out the threading code. 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. Signed-off-by: Justin Suess --- Notes: v3->v4: - Turn multi_threaded_{success,no_new_privs,no_new_privs_max_layers} into variants of a multi_threaded fixture, per Mickaƫl's feedback. - Move the minimal ABI/flag checks into the previous patch. tools/testing/selftests/landlock/base_test.c | 86 ++++++++++++++++- tools/testing/selftests/landlock/tsync_test.c | 96 ++++++++++++++++--- 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 288d6bc19232..d20ab8f0862c 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -289,6 +289,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; @@ -300,7 +335,7 @@ TEST(restrict_self_fd) EXPECT_EQ(EBADFD, errno); } -TEST(restrict_self_fd_logging_flags) +TEST(restrict_self_fd_flags) { int fd; @@ -314,9 +349,14 @@ 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_NO_NEW_PRIVS; @@ -361,6 +401,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( @@ -371,6 +422,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..2b53596c986e 100644 --- a/tools/testing/selftests/landlock/tsync_test.c +++ b/tools/testing/selftests/landlock/tsync_test.c @@ -62,32 +62,104 @@ static void *idle(void *data) pthread_cleanup_pop(1); } -TEST(multi_threaded_success) +FIXTURE(multi_threaded) { - pthread_t t1, t2; - bool no_new_privs1, no_new_privs2; - const int ruleset_fd = create_ruleset(_metadata); + int ruleset_fd; +}; + +FIXTURE_VARIANT(multi_threaded) +{ + const __u32 restrict_flags; + /* Sets no_new_privs with prctl(2) before the enforcement. */ + const bool prior_no_new_privs; + /* Enforces the maximum number of allowed layers beforehand. */ + const bool max_layers; + const int expected_errno; + /* Expected no_new_privs state of all threads after the call. */ + const bool expected_no_new_privs; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(multi_threaded, success) { + /* clang-format on */ + .restrict_flags = LANDLOCK_RESTRICT_SELF_TSYNC, + .prior_no_new_privs = true, + .expected_no_new_privs = true, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(multi_threaded, no_new_privs) { + /* clang-format on */ + .restrict_flags = LANDLOCK_RESTRICT_SELF_TSYNC | + LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, + .expected_no_new_privs = true, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(multi_threaded, no_new_privs_max_layers) { + /* clang-format on */ + .restrict_flags = LANDLOCK_RESTRICT_SELF_TSYNC | + LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, + .max_layers = true, + .expected_errno = E2BIG, + .expected_no_new_privs = false, +}; + +FIXTURE_SETUP(multi_threaded) +{ + self->ruleset_fd = create_ruleset(_metadata); + + if (variant->max_layers) { + /* Enforces the maximum number of allowed layers. */ + for (int i = 0; i < LANDLOCK_MAX_NUM_LAYERS; i++) + ASSERT_EQ(0, + landlock_restrict_self(self->ruleset_fd, 0)); + } disable_caps(_metadata); +} + +FIXTURE_TEARDOWN(multi_threaded) +{ + EXPECT_EQ(0, close(self->ruleset_fd)); +} + +TEST_F(multi_threaded, restrict) +{ + pthread_t t1, t2; + bool no_new_privs1, no_new_privs2; ASSERT_EQ(0, pthread_create(&t1, NULL, idle, &no_new_privs1)); ASSERT_EQ(0, pthread_create(&t2, NULL, idle, &no_new_privs2)); - ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + if (variant->prior_no_new_privs) { + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + } else { + /* 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)); + if (variant->expected_errno) { + EXPECT_EQ(-1, landlock_restrict_self(self->ruleset_fd, + variant->restrict_flags)); + EXPECT_EQ(variant->expected_errno, errno); + } else { + EXPECT_EQ(0, landlock_restrict_self(self->ruleset_fd, + variant->restrict_flags)); + } + + /* Checks the no_new_privs state of the calling thread. */ + EXPECT_EQ(variant->expected_no_new_privs, + 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 implicitly enabled on all threads. */ - EXPECT_TRUE(no_new_privs1); - EXPECT_TRUE(no_new_privs2); - - EXPECT_EQ(0, close(ruleset_fd)); + /* Checks the no_new_privs state of the sibling threads. */ + EXPECT_EQ(variant->expected_no_new_privs, no_new_privs1); + EXPECT_EQ(variant->expected_no_new_privs, no_new_privs2); } TEST(multi_threaded_success_despite_diverging_domains) -- 2.55.0