* [PATCH 1/2] landlock: Fix landlock_restrict_self() failure on long executable paths
2026-11-11 4:54 [PATCH 0/2] landlock: Fix landlock_restrict_self() on long executable paths Daejun Park via B4 Relay
@ 2026-11-11 4:54 ` Daejun Park via B4 Relay
2026-11-11 4:59 ` sashiko-bot
2026-11-11 4:54 ` [PATCH 2/2] selftests/landlock: Test a long executable path Daejun Park via B4 Relay
1 sibling, 1 reply; 5+ messages in thread
From: Daejun Park via B4 Relay @ 2026-11-11 4:54 UTC (permalink / raw)
To: Mickaël Salaün, Günther Noack
Cc: Paul Moore, James Morris, Serge E. Hallyn, Shuah Khan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Dwi Siswanto,
Günther Noack, linux-security-module, linux-kselftest,
linux-doc, linux-kernel, stable, Daejun Park
From: Daejun Park <daejun7.park@samsung.com>
get_current_exe() treats a d_path() error on the current executable as
a bug. But an absolute path is not bounded by PATH_MAX, and d_path()
returns -ENAMETOOLONG when the path does not fit in the
LANDLOCK_PATH_MAX_SIZE buffer.
An unprivileged task with such an executable triggers the
WARN_ON_ONCE(), and its landlock_restrict_self() call fails with
-ENAMETOOLONG.
The path is not used for enforcement. Record "<too_long>" instead, as
audit_log_d_path() does, and create the domain.
Reported-by: Dwi Siswanto <me@dw1.io>
Closes: https://lore.kernel.org/r/20261102022018.73143-1-me@dw1.io
Suggested-by: Dwi Siswanto <me@dw1.io>
Fixes: 1d636984e088 ("landlock: Add AUDIT_LANDLOCK_DOMAIN and log domain status")
Cc: stable@vger.kernel.org
Signed-off-by: Daejun Park <daejun7.park@samsung.com>
---
Documentation/admin-guide/LSM/landlock.rst | 3 ++-
security/landlock/domain.c | 15 +++++++++++----
security/landlock/domain.h | 3 ++-
3 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/Documentation/admin-guide/LSM/landlock.rst b/Documentation/admin-guide/LSM/landlock.rst
index 718e0d23ffb5..983223ce6818 100644
--- a/Documentation/admin-guide/LSM/landlock.rst
+++ b/Documentation/admin-guide/LSM/landlock.rst
@@ -104,7 +104,8 @@ AUDIT_LANDLOCK_DOMAIN
- the enforcement ``mode``
- the domain creator's ``pid``
- the domain creator's ``uid``
- - the domain creator's executable path (``exe``)
+ - the domain creator's executable path (``exe``), or ``"<too_long>"`` if
+ this path does not fit in the kernel buffer
- the domain creator's command line (``comm``)
Example::
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 4c0b1f0e1db5..4c9858c11b3d 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -492,7 +492,8 @@ landlock_merge_ruleset(struct landlock_domain *const parent,
* get_current_exe - Get the current's executable path, if any
*
* @exe_str: Returned pointer to a path string with a lifetime tied to the
- * returned buffer, if any.
+ * returned buffer, if any. It is "<too_long>" if the path does
+ * not fit in this buffer.
* @exe_size: Returned size of @exe_str (including the trailing null
* character), if any.
*
@@ -502,6 +503,8 @@ landlock_merge_ruleset(struct landlock_domain *const parent,
static const void *get_current_exe(const char **const exe_str,
size_t *const exe_size)
{
+ /* Cf. audit_log_d_path() */
+ static const char too_long_path[] = "<too_long>";
const size_t buffer_size = LANDLOCK_PATH_MAX_SIZE;
struct mm_struct *mm = current->mm;
struct file *file __free(fput) = NULL;
@@ -521,9 +524,13 @@ static const void *get_current_exe(const char **const exe_str,
return ERR_PTR(-ENOMEM);
exe = d_path(&file->f_path, buffer, buffer_size);
- if (WARN_ON_ONCE(IS_ERR(exe)))
- /* Should never happen according to LANDLOCK_PATH_MAX_SIZE. */
- return ERR_CAST(exe);
+ if (IS_ERR(exe)) {
+ /* The path does not fit: it is not bounded by PATH_MAX. */
+ memcpy(buffer, too_long_path, sizeof(too_long_path));
+ *exe_size = sizeof(too_long_path);
+ *exe_str = buffer;
+ return no_free_ptr(buffer);
+ }
size = buffer + buffer_size - exe;
if (WARN_ON_ONCE(size <= 0))
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 935e18b12b3c..d087882fd428 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -72,7 +72,8 @@ struct landlock_details {
/**
* @exe_path: Executable path of the task that initially restricted
* itself, at creation time. Always NULL terminated, and never greater
- * than LANDLOCK_PATH_MAX_SIZE.
+ * than LANDLOCK_PATH_MAX_SIZE. It is "<too_long>" if the path does not
+ * fit.
*/
char exe_path[];
};
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/2] selftests/landlock: Test a long executable path
2026-11-11 4:54 [PATCH 0/2] landlock: Fix landlock_restrict_self() on long executable paths Daejun Park via B4 Relay
2026-11-11 4:54 ` [PATCH 1/2] landlock: Fix landlock_restrict_self() failure " Daejun Park via B4 Relay
@ 2026-11-11 4:54 ` Daejun Park via B4 Relay
2026-11-11 5:00 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Daejun Park via B4 Relay @ 2026-11-11 4:54 UTC (permalink / raw)
To: Mickaël Salaün, Günther Noack
Cc: Paul Moore, James Morris, Serge E. Hallyn, Shuah Khan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Dwi Siswanto,
Günther Noack, linux-security-module, linux-kselftest,
linux-doc, linux-kernel, Daejun Park
From: Daejun Park <daejun7.park@samsung.com>
Add two tests that execute a copy of wait-pipe-sandbox from a path
longer than LANDLOCK_PATH_MAX_SIZE. layout0.long_exe_path checks,
without audit, that the copy can sandbox itself.
audit_layout0.long_exe_path also checks that the copy's domain records
show exe="<too_long>".
Without the get_current_exe() fix, landlock_restrict_self() fails there
with -ENAMETOOLONG.
Also name the right test in the header of wait-pipe-sandbox.c.
Signed-off-by: Daejun Park <daejun7.park@samsung.com>
---
tools/testing/selftests/landlock/fs_test.c | 175 +++++++++++++++++++++
.../testing/selftests/landlock/wait-pipe-sandbox.c | 3 +-
2 files changed, 177 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index f725882fa4a7..738a7175d94c 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -8013,6 +8013,181 @@ TEST_F(audit_layout1, mount)
EXPECT_EQ(1, records.domain);
}
+/*
+ * Executes a copy of bin_wait_pipe_sandbox from a path which does not fit in
+ * the kernel buffer for the executable path, and checks that this copy can
+ * sandbox itself. Returns the PID of this copy, once it has exited.
+ */
+static pid_t test_long_exe_path(struct __test_metadata *const _metadata)
+{
+ int pipe_child[2], pipe_parent[2];
+ char name[NAME_MAX + 1];
+ int dir_fd, status, i;
+ char buf_parent;
+ pid_t child;
+
+ /*
+ * Creates nested directories whose path (17 * 256 bytes) is longer
+ * than the kernel buffer for the executable path (PATH_MAX + 11
+ * bytes), whatever the current directory is.
+ */
+ memset(name, 'd', sizeof(name) - 1);
+ name[sizeof(name) - 1] = '\0';
+ dir_fd = open(TMP_DIR, O_PATH | O_DIRECTORY | O_CLOEXEC);
+ ASSERT_LE(0, dir_fd);
+ for (i = 0; i <= PATH_MAX / sizeof(name); i++) {
+ int fd;
+
+ ASSERT_EQ(0, mkdirat(dir_fd, name, 0700));
+ fd = openat(dir_fd, name, O_PATH | O_DIRECTORY | O_CLOEXEC);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, close(dir_fd));
+ dir_fd = fd;
+ }
+
+ /* Copies bin_wait_pipe_sandbox to the last directory. */
+ create_file(_metadata, TMP_DIR "/f");
+ copy_file(_metadata, bin_wait_pipe_sandbox, TMP_DIR "/f");
+ ASSERT_EQ(0, renameat(AT_FDCWD, TMP_DIR "/f", dir_fd, "f"));
+
+ ASSERT_EQ(0, pipe2(pipe_child, 0));
+ ASSERT_EQ(0, pipe2(pipe_parent, 0));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ const struct landlock_ruleset_attr layer1 = {
+ .scoped = LANDLOCK_SCOPE_SIGNAL,
+ };
+ char pipe_child_str[12], pipe_parent_str[12];
+ char *const argv[] = { "f", pipe_child_str, pipe_parent_str,
+ NULL };
+
+ /* Passes the pipe FDs to the executed binary. */
+ EXPECT_EQ(0, close(pipe_child[0]));
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ snprintf(pipe_child_str, sizeof(pipe_child_str), "%d",
+ pipe_child[1]);
+ snprintf(pipe_parent_str, sizeof(pipe_parent_str), "%d",
+ pipe_parent[0]);
+
+ /* The executed binary expects that it cannot send signals. */
+ drop_access_rights(_metadata, &layer1);
+
+ ASSERT_EQ(0, sys_execveat(dir_fd, argv[0], argv, NULL, 0))
+ {
+ TH_LOG("Failed to execute \"%s\": %s", argv[0],
+ strerror(errno));
+ };
+ _exit(1);
+ }
+
+ EXPECT_EQ(0, close(pipe_child[1]));
+ EXPECT_EQ(0, close(pipe_parent[0]));
+ EXPECT_EQ(0, close(dir_fd));
+
+ /*
+ * Goes through the three steps of the executed binary: it tries to
+ * send a signal, sandboxes itself and tries to open a directory, then
+ * sandboxes itself again and exits.
+ */
+ for (i = 0; i < 3; i++) {
+ ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1))
+ {
+ /*
+ * The child exits when a step fails. Reaps it before
+ * the teardown, which may fail to unmount TMP_DIR
+ * while the child still uses it.
+ */
+ close(pipe_parent[1]);
+ waitpid(child, &status, 0);
+ }
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ }
+
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ EXPECT_EQ(0, close(pipe_child[0]));
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ return child;
+}
+
+/*
+ * Checks that a task can sandbox itself when the path of its executable does
+ * not fit in the kernel buffer.
+ */
+TEST_F(layout0, long_exe_path)
+{
+ test_long_exe_path(_metadata);
+}
+
+static int matches_log_domain_too_long(int audit_fd, pid_t pid)
+{
+ static const char log_template[] = REGEX_LANDLOCK_PREFIX
+ " status=allocated mode=enforcing pid=%d uid=[0-9]\\+"
+ " exe=\"<too_long>\" comm=\"f\"$";
+ char log_match[sizeof(log_template) + 10];
+ int log_match_len;
+
+ log_match_len =
+ snprintf(log_match, sizeof(log_match), log_template, pid);
+ if (log_match_len >= sizeof(log_match))
+ return -E2BIG;
+
+ return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match,
+ NULL);
+}
+
+FIXTURE(audit_layout0)
+{
+ int audit_fd;
+};
+
+FIXTURE_SETUP(audit_layout0)
+{
+ prepare_layout(_metadata);
+
+ /*
+ * No executable filter: audit_init_with_exe_filter() would only keep
+ * the records of this test binary, and the path of the executed copy
+ * is too long for a rule. The test matches its records by PID.
+ */
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ self->audit_fd = audit_init();
+ clear_cap(_metadata, CAP_AUDIT_CONTROL);
+ if (self->audit_fd < 0) {
+ /*
+ * FIXTURE_TEARDOWN_PARENT() is not called when FIXTURE_SETUP()
+ * failed, so we need to remove TMP_DIR to avoid cascading
+ * errors with the next tests.
+ */
+ cleanup_layout(_metadata);
+ }
+ ASSERT_LE(0, self->audit_fd);
+}
+
+FIXTURE_TEARDOWN_PARENT(audit_layout0)
+{
+ cleanup_layout(_metadata);
+
+ EXPECT_EQ(0, audit_cleanup(-1, NULL));
+}
+
+/*
+ * Checks that the records of the domains created by such a task get a
+ * placeholder instead of the path of its executable.
+ */
+TEST_F(audit_layout0, long_exe_path)
+{
+ const pid_t child = test_long_exe_path(_metadata);
+
+ /* The executed binary created two domains. */
+ EXPECT_EQ(0, matches_log_domain_too_long(self->audit_fd, child));
+ EXPECT_EQ(0, matches_log_domain_too_long(self->audit_fd, child));
+}
+
static bool debug_quiet_tests;
FIXTURE(audit_quiet_layout1)
diff --git a/tools/testing/selftests/landlock/wait-pipe-sandbox.c b/tools/testing/selftests/landlock/wait-pipe-sandbox.c
index 87dbc9164430..0dfa8e75fc13 100644
--- a/tools/testing/selftests/landlock/wait-pipe-sandbox.c
+++ b/tools/testing/selftests/landlock/wait-pipe-sandbox.c
@@ -2,7 +2,8 @@
/*
* Write in a pipe, wait, sandbox itself, test sandboxing, and wait again.
*
- * Used by audit_exec.flags from audit_test.c
+ * Used by audit_exec.signal_and_open from audit_test.c, and by
+ * layout0.long_exe_path and audit_layout0.long_exe_path from fs_test.c
*
* Copyright © 2024-2025 Microsoft Corporation
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread