* [PATCH v2 0/4] Landlock: Restrict POSIX message queue creation and access
@ 2026-11-02 14:45 Oxana Kharitonova
2026-11-02 14:45 ` [PATCH v2 1/4] ipc: Move MQUEUE_MAGIC to a shared header Oxana Kharitonova
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Oxana Kharitonova @ 2026-11-02 14:45 UTC (permalink / raw)
To: mic, brauner, viro, gnoack, utilityemal77
Cc: linux-security-module, linux-fsdevel, webprosto, landlock
Hi,
Christian and Al, could you please review the mqueue-specific changes,
particularly the shared MQUEUE_MAGIC definition in patch 1?
Mickaël, Günther, and Justin, thank you for your feedback. I have revised
the changes and split the series into two parts: patches 1 and 2 enforce
the existing MAKE_REG permission, while patches 3 and 4 introduce and
expose the new scope. The first two patches can be applied without the
latter two.
The new scope adds control based on the queue creator's Landlock domain,
allowing sandboxes with shared filesystem access to isolate their queues.
However, I am unsure how useful this additional isolation would be in
practice.
The scope cannot replace the existing filesystem permission checks
without changing behavior, because applications may already rely on
them to restrict queue access. Both mechanisms therefore remain in
place. When READ_FILE is handled and queue scoping is enabled, the
checks combine as follows for opening an existing queue read-only,
assuming other access controls permit the operation:
Filesystem read access | Queue scope | Expected result
-----------------------+-------------+----------------
Denied | Allowed | EACCES
Denied | Denied | EPERM
Allowed | Allowed | Success
Allowed | Denied | EPERM
Selftests cover all four combinations.
I would welcome feedback on whether this domain-based isolation provides
enough practical value to justify the additional logic needed to track
creator domains and manage their lifetimes.
Queue creation
==============
POSIX message queues are represented by files in the mqueue filesystem.
Landlock's existing READ_FILE and WRITE_FILE restrictions can deny
mq_open(), but these checks occur after queue creation. Consequently,
mq_open() with O_CREAT may create a queue before access is denied,
leaving the queue behind despite the failed call.
Patch 2 adds an inode_create hook to enforce MAKE_REG before queue
creation. When this right is handled, creation requires an applicable
rule allowing MAKE_REG on the queue's parent directory. Opening an
existing queue does not require MAKE_REG.
Queue scoping
=============
Patch 3 adds LANDLOCK_SCOPE_POSIX_MSG_QUEUE through Landlock ABI 13.
It restricts opening queues to those created within the domain enabling
the scope or its descendants.
The restriction applies to mq_open() and opens through a mounted mqueue
filesystem. Existing, inherited, and passed descriptors remain usable.
Filesystem read/write restrictions continue to apply alongside the
scope.
The implementation includes audit logging, audit quieting, and a
dedicated denial trace event. Patch 4 exposes the scope and quieting
options through the sandboxer sample.
Unlink and queue lifetime
========================
A queue's lifetime is independent of its creator. While its IPC
namespace exists, the queue remains until its name is unlinked and all
open descriptors are closed.
The scope leaves mq_unlink() unrestricted, subject to other access
controls, so that an application can clean up stale queues from previous
runs.
A process permitted to unlink and recreate a queue can replace its name,
including from another Landlock domain. Existing descriptors continue
to refer to the original queue, while subsequent opens refer to the
replacement and undergo its scope check. The scope therefore does not
guarantee the identity or availability of queues by name. These
limitations are documented.
Changes since v1
================
- Separate MAKE_REG enforcement from the proposed queue scope.
- Store the creator-domain reference in landlock_object rather than
directly in landlock_inode_security.
- Add an inode_create hook to enforce MAKE_REG before queue creation.
- Expand documentation of filesystem permissions, scoping, descriptor
reuse, and unlink/name-replacement limitations.
- Extend selftest coverage, including audit logging and quieting,
path-based opens, inherited descriptors, and descriptor passing.
- Enable CONFIG_POSIX_MQUEUE=y in the Landlock selftest configuration.
Oxana Kharitonova (4):
ipc: Move MQUEUE_MAGIC to a shared header
Landlock: Enforce MAKE_REG for POSIX message queue creation
Landlock: Add POSIX message queue scoping
samples/landlock: Add POSIX message queue scoping to sandboxer
Documentation/trace/events-landlock.rst | 1 +
Documentation/userspace-api/landlock.rst | 80 +-
include/linux/ipc_namespace.h | 2 +
include/linux/landlock.h | 3 +-
include/trace/events/landlock.h | 46 +
include/uapi/linux/landlock.h | 5 +
ipc/mqueue.c | 1 -
samples/landlock/sandboxer.c | 26 +-
security/landlock/audit.c | 5 +
security/landlock/fs.c | 133 ++-
security/landlock/fs.h | 12 +
security/landlock/limits.h | 2 +-
security/landlock/log.c | 4 +
security/landlock/log.h | 1 +
security/landlock/object.c | 2 +
security/landlock/object.h | 7 +
security/landlock/syscalls.c | 2 +-
security/landlock/task.c | 66 ++
security/landlock/task.h | 4 +
security/landlock/trace.c | 7 +
tools/testing/selftests/landlock/Makefile | 4 +-
tools/testing/selftests/landlock/audit_test.c | 114 +++
tools/testing/selftests/landlock/base_test.c | 40 +-
tools/testing/selftests/landlock/config | 1 +
.../selftests/landlock/posix_mqueue_test.c | 233 +++++
.../landlock/scoped_posix_msg_queue_test.c | 846 ++++++++++++++++++
.../testing/selftests/landlock/scoped_test.c | 2 +-
27 files changed, 1634 insertions(+), 15 deletions(-)
create mode 100644 tools/testing/selftests/landlock/posix_mqueue_test.c
create mode 100644 tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/4] ipc: Move MQUEUE_MAGIC to a shared header
2026-11-02 14:45 [PATCH v2 0/4] Landlock: Restrict POSIX message queue creation and access Oxana Kharitonova
@ 2026-11-02 14:45 ` Oxana Kharitonova
2026-11-02 14:51 ` sashiko-bot
2026-11-02 14:45 ` [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation Oxana Kharitonova
` (2 subsequent siblings)
3 siblings, 1 reply; 11+ messages in thread
From: Oxana Kharitonova @ 2026-11-02 14:45 UTC (permalink / raw)
To: mic, brauner, viro, gnoack, utilityemal77
Cc: linux-security-module, linux-fsdevel, webprosto, landlock
Move MQUEUE_MAGIC from ipc/mqueue.c to include/linux/ipc_namespace.h so
it can be shared by code outside of ipc/mqueue.c without duplicating
the constant.
Signed-off-by: Oxana Kharitonova <webprosto@gmail.com>
---
include/linux/ipc_namespace.h | 2 ++
ipc/mqueue.c | 1 -
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/linux/ipc_namespace.h b/include/linux/ipc_namespace.h
index 12faca29bbb9..8a15ffe38e9a 100644
--- a/include/linux/ipc_namespace.h
+++ b/include/linux/ipc_namespace.h
@@ -15,6 +15,8 @@
struct user_namespace;
+#define MQUEUE_MAGIC 0x19800202
+
struct ipc_ids {
int in_use;
unsigned short seq;
diff --git a/ipc/mqueue.c b/ipc/mqueue.c
index d1dd36a651b0..0f00e4efcb60 100644
--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
@@ -47,7 +47,6 @@ struct mqueue_fs_context {
bool newns; /* Set if newly created ipc namespace */
};
-#define MQUEUE_MAGIC 0x19800202
#define DIRENT_SIZE 20
#define FILENT_SIZE 80
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation
2026-11-02 14:45 [PATCH v2 0/4] Landlock: Restrict POSIX message queue creation and access Oxana Kharitonova
2026-11-02 14:45 ` [PATCH v2 1/4] ipc: Move MQUEUE_MAGIC to a shared header Oxana Kharitonova
@ 2026-11-02 14:45 ` Oxana Kharitonova
2026-11-02 15:06 ` sashiko-bot
2026-11-03 2:42 ` kernel test robot
2026-11-02 14:45 ` [PATCH v2 3/4] Landlock: Add POSIX message queue scoping Oxana Kharitonova
2026-11-02 14:46 ` [PATCH v2 4/4] samples/landlock: Add POSIX message queue scoping to sandboxer Oxana Kharitonova
3 siblings, 2 replies; 11+ messages in thread
From: Oxana Kharitonova @ 2026-11-02 14:45 UTC (permalink / raw)
To: mic, brauner, viro, gnoack, utilityemal77
Cc: linux-security-module, linux-fsdevel, webprosto, landlock
POSIX message queues are represented by files in the mqueue filesystem.
Landlock's existing READ_FILE and WRITE_FILE restrictions can deny
mq_open() through hook_file_open(). However, these checks occur after
queue creation, so mq_open() with O_CREAT may create a queue before
access is denied, leaving the queue behind despite the failed call.
Add an inode_create hook to enforce LANDLOCK_ACCESS_FS_MAKE_REG before
queue creation. Use the mqueue mount associated with the current IPC
namespace to construct the parent path. Opening an existing queue does
not require MAKE_REG.
Add selftests covering denied creation without leaving a queue behind,
explicitly allowed creation, opening existing queues when MAKE_REG is
denied, and existing read/write restrictions. Enable POSIX message queues
in the selftest configuration and link the tests with librt.
Document the creation and opening checks, including that a queue may
still remain if creation is allowed but the subsequent open is denied.
Signed-off-by: Oxana Kharitonova <webprosto@gmail.com>
---
Documentation/userspace-api/landlock.rst | 24 ++
include/uapi/linux/landlock.h | 1 +
security/landlock/fs.c | 31 +++
security/landlock/fs.h | 6 +
tools/testing/selftests/landlock/Makefile | 4 +-
tools/testing/selftests/landlock/config | 1 +
.../selftests/landlock/posix_mqueue_test.c | 233 ++++++++++++++++++
7 files changed, 298 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/landlock/posix_mqueue_test.c
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 3d45b956b640..1fb91884cb8a 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -458,6 +458,30 @@ OverlayFS layer will not restrict the resulted merged hierarchy, and vice versa.
Landlock users should then only think about file hierarchies they want to allow
access to, regardless of the underlying filesystem.
+POSIX message queues
+--------------------
+
+POSIX message queues are represented by regular files in the mqueue
+filesystem. Opening a queue with :manpage:`mq_open(3)` is subject to
+``LANDLOCK_ACCESS_FS_READ_FILE`` and ``LANDLOCK_ACCESS_FS_WRITE_FILE``,
+according to the requested access mode, when these rights are handled by an
+enforced ruleset. These checks also apply to opening queue files through a
+mounted mqueue filesystem with :manpage:`open(2)`.
+
+Creating a new queue with :manpage:`mq_open(3)` and ``O_CREAT`` is checked
+separately. When an enforced ruleset handles
+``LANDLOCK_ACCESS_FS_MAKE_REG``, creation is denied unless an applicable
+filesystem rule allows this right on the queue's parent directory. Such a
+rule can refer to the root of the mqueue filesystem for the calling process's
+IPC namespace, commonly mounted at ``/dev/mqueue``. Opening an existing queue,
+including with ``O_CREAT``, does not require this right.
+
+The creation check occurs before the file-open check. If creation is allowed
+but the subsequent read or write access check denies the open, the newly
+created queue remains even though :manpage:`mq_open(3)` fails. In particular,
+handling only ``LANDLOCK_ACCESS_FS_READ_FILE`` or
+``LANDLOCK_ACCESS_FS_WRITE_FILE`` does not prevent queue creation.
+
Inheritance
-----------
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 24510e5d8479..04a18824c700 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -450,6 +450,7 @@ struct landlock_capability_attr {
* device.
* - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create (or rename) a directory.
* - %LANDLOCK_ACCESS_FS_MAKE_REG: Create (or rename or link) a regular file.
+ * This also controls POSIX message queue creation with mq_open(3).
* This also guards the creation of whiteout objects as used in OverlayFS.
* - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create (or rename or link) a UNIX domain
* socket.
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 2ab67bcad198..d5dcb19d5eb1 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -29,6 +29,7 @@
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/net.h>
+#include <linux/nsproxy.h>
#include <linux/path.h>
#include <linux/pid.h>
#include <linux/rcupdate.h>
@@ -1676,6 +1677,35 @@ static int hook_path_truncate(const struct path *const path)
return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
}
+/*
+ * Unlike regular file creation, mq_open(2) calls vfs_mkobj() directly and
+ * therefore does not trigger the path_mknod hook. Use the mqueuefs mount
+ * associated with the current IPC namespace to recover the parent path.
+ */
+static int hook_inode_create(struct inode *const dir,
+ struct dentry *const dentry, const umode_t)
+{
+ struct ipc_namespace *ipc_ns;
+ struct vfsmount *mnt;
+ struct path parent;
+
+ if (!landlock_is_posix_mqueue_fs(dir))
+ return 0;
+
+ if (!current->nsproxy)
+ return 0;
+ ipc_ns = current->nsproxy->ipc_ns;
+ if (!ipc_ns)
+ return 0;
+ mnt = ipc_ns->mq_mnt;
+ if (WARN_ON_ONCE(!mnt || mnt->mnt_sb != dir->i_sb))
+ return 0;
+
+ parent.mnt = mnt;
+ parent.dentry = dentry->d_parent;
+ return current_check_access_path(&parent, LANDLOCK_ACCESS_FS_MAKE_REG);
+}
+
/**
* unmask_scoped_access - Remove access right bits in @masks in all layers
* where @client and @server have the same domain
@@ -2084,6 +2114,7 @@ static void hook_file_free_security(struct file *file)
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
+ LSM_HOOK_INIT(inode_create, hook_inode_create),
LSM_HOOK_INIT(sb_delete, hook_sb_delete),
LSM_HOOK_INIT(sb_mount, hook_sb_mount),
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index 50b3ddb8ac1c..05e36ef1acf7 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -14,6 +14,7 @@
#include <linux/cleanup.h>
#include <linux/fs.h>
#include <linux/init.h>
+#include <linux/ipc_namespace.h>
#include <linux/rcupdate.h>
#include "access.h"
@@ -144,6 +145,11 @@ landlock_inode(const struct inode *const inode)
return inode->i_security + landlock_blob_sizes.lbs_inode;
}
+static inline bool landlock_is_posix_mqueue_fs(const struct inode *const inode)
+{
+ return inode->i_sb->s_magic == MQUEUE_MAGIC;
+}
+
static inline struct landlock_superblock_security *
landlock_superblock(const struct super_block *const superblock)
{
diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
index fc43225d319a..80da63f4251d 100644
--- a/tools/testing/selftests/landlock/Makefile
+++ b/tools/testing/selftests/landlock/Makefile
@@ -18,11 +18,11 @@ TEST_GEN_PROGS_EXTENDED := \
wait-pipe-sandbox
# Short targets:
-$(TEST_GEN_PROGS): LDLIBS += -lcap -lpthread
+$(TEST_GEN_PROGS): LDLIBS += -lcap -lpthread -lrt
$(TEST_GEN_PROGS_EXTENDED): LDFLAGS += -static
include ../lib.mk
# Targets with $(OUTPUT)/ prefix:
-$(TEST_GEN_PROGS): LDLIBS += -lcap -lpthread
+$(TEST_GEN_PROGS): LDLIBS += -lcap -lpthread -lrt
$(TEST_GEN_PROGS_EXTENDED): LDFLAGS += -static
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 5568d7a4b2d7..7761916db48b 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -14,6 +14,7 @@ CONFIG_NET=y
CONFIG_NET_NS=y
CONFIG_OVERLAY_FS=y
CONFIG_PID_NS=y
+CONFIG_POSIX_MQUEUE=y
CONFIG_PROC_FS=y
CONFIG_SECURITY=y
CONFIG_SECURITY_LANDLOCK=y
diff --git a/tools/testing/selftests/landlock/posix_mqueue_test.c b/tools/testing/selftests/landlock/posix_mqueue_test.c
new file mode 100644
index 000000000000..9a8b56e2dbda
--- /dev/null
+++ b/tools/testing/selftests/landlock/posix_mqueue_test.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Landlock tests - POSIX message queue filesystem access */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/landlock.h>
+#include <mqueue.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "common.h"
+
+static void set_mq_name(char *const name, const size_t size)
+{
+ snprintf(name, size, "/selftests-landlock-mq-tid%d", sys_gettid());
+}
+
+/* Denied creation must not leave a queue behind. */
+TEST(posix_mqueue_make_reg)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG,
+ };
+ char name[64];
+ int fd;
+
+ drop_caps(_metadata);
+ ASSERT_LT(0, snprintf(name, sizeof(name), "landlock-%d", getpid()));
+
+ /* Check that POSIX message queues are supported by the running kernel. */
+ fd = syscall(__NR_mq_open, name, O_CREAT | O_EXCL | O_RDONLY, 0600,
+ NULL);
+ if (fd < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, fd);
+ ASSERT_EQ(0, close(fd));
+ ASSERT_EQ(0, syscall(__NR_mq_unlink, name));
+
+ drop_access_rights(_metadata, &ruleset_attr);
+
+ EXPECT_EQ(-1, syscall(__NR_mq_open, name, O_CREAT | O_EXCL | O_RDONLY,
+ 0600, NULL));
+ EXPECT_EQ(EACCES, errno);
+
+ /* A denied mq_open() must not leave the message queue behind. */
+ EXPECT_EQ(-1, syscall(__NR_mq_unlink, name));
+ EXPECT_EQ(ENOENT, errno);
+}
+
+/*
+ * A process restricted by LANDLOCK_ACCESS_FS_MAKE_REG must still be able to
+ * create a POSIX message queue when this access right is explicitly allowed
+ * on the mqueue filesystem mounted at /dev/mqueue.
+ */
+TEST(posix_mqueue_make_reg_allowed)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG,
+ };
+ struct landlock_path_beneath_attr path_beneath = {
+ .allowed_access = LANDLOCK_ACCESS_FS_MAKE_REG,
+ };
+ char mq_name[NAME_MAX], mq_path[PATH_MAX];
+ int ruleset_fd;
+ int check_fd;
+ mqd_t mq;
+
+ drop_caps(_metadata);
+ set_mq_name(mq_name, sizeof(mq_name));
+ ASSERT_LT(0,
+ snprintf(mq_path, sizeof(mq_path), "/dev/mqueue%s", mq_name));
+ mq_unlink(mq_name);
+
+ /* Makes sure the current IPC namespace is visible through this path. */
+ mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDONLY, 0600, NULL);
+ if (mq < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, mq);
+ check_fd = open(mq_path, O_PATH | O_CLOEXEC);
+ if (check_fd < 0) {
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+ SKIP(return,
+ "/dev/mqueue is not mounted for this IPC namespace");
+ }
+ EXPECT_EQ(0, close(check_fd));
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ path_beneath.parent_fd = open("/dev/mqueue", O_PATH | O_CLOEXEC);
+ ASSERT_LE(0, path_beneath.parent_fd);
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+ &path_beneath, 0));
+ EXPECT_EQ(0, close(path_beneath.parent_fd));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDONLY, 0600, NULL);
+ ASSERT_LE(0, mq);
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+}
+
+/*
+ * LANDLOCK_ACCESS_FS_MAKE_REG controls the creation of POSIX message queues,
+ * but must not prevent opening a queue that already exists.
+ */
+TEST(posix_mqueue_make_reg_existing)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG,
+ };
+ char existing_name[NAME_MAX], new_name[NAME_MAX];
+ mqd_t existing_mq, reopened_mq;
+
+ drop_caps(_metadata);
+ ASSERT_LT(0, snprintf(existing_name, sizeof(existing_name),
+ "/selftests-landlock-mq-existing-tid%d",
+ sys_gettid()));
+ ASSERT_LT(0,
+ snprintf(new_name, sizeof(new_name),
+ "/selftests-landlock-mq-new-tid%d", sys_gettid()));
+ mq_unlink(existing_name);
+ mq_unlink(new_name);
+
+ existing_mq =
+ mq_open(existing_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ if (existing_mq < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, existing_mq);
+
+ drop_access_rights(_metadata, &ruleset_attr);
+
+ reopened_mq = mq_open(existing_name, O_RDWR);
+ ASSERT_LE(0, reopened_mq);
+ EXPECT_EQ(0, mq_close(reopened_mq));
+
+ EXPECT_EQ(-1, mq_open(new_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL));
+ EXPECT_EQ(EACCES, errno);
+
+ EXPECT_EQ(0, mq_close(existing_mq));
+ EXPECT_EQ(0, mq_unlink(existing_name));
+ EXPECT_EQ(-1, mq_unlink(new_name));
+ EXPECT_EQ(ENOENT, errno);
+}
+
+/* Filesystem rules must continue to restrict queue opens. */
+TEST(posix_mqueue_path_access)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_WRITE_FILE,
+ };
+ struct landlock_path_beneath_attr path_beneath = {
+ .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
+ };
+ const int modes[] = { O_RDONLY, O_WRONLY, O_RDWR };
+ char name[NAME_MAX], path[PATH_MAX];
+ struct stat queue_stat, path_stat;
+ int ruleset_fd, fd;
+ mqd_t mq;
+
+ drop_caps(_metadata);
+ set_mq_name(name, sizeof(name));
+ ASSERT_LT(0, snprintf(path, sizeof(path), "/dev/mqueue%s", name));
+ mq_unlink(name);
+ mq = mq_open(name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ if (mq < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, mq);
+ ASSERT_EQ(0, fstat(mq, &queue_stat));
+ if (stat(path, &path_stat) || queue_stat.st_dev != path_stat.st_dev ||
+ queue_stat.st_ino != path_stat.st_ino) {
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(name));
+ SKIP(return, "/dev/mqueue does not expose this IPC namespace");
+ }
+
+ /* Establish that ordinary permissions allow every tested open mode. */
+ for (size_t i = 0; i < ARRAY_SIZE(modes); i++) {
+ fd = open(path, modes[i] | O_CLOEXEC);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, close(fd));
+ fd = mq_open(name, modes[i]);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, mq_close(fd));
+ }
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ path_beneath.parent_fd = open("/dev/mqueue", O_PATH | O_CLOEXEC);
+ ASSERT_LE(0, path_beneath.parent_fd);
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+ &path_beneath, 0));
+ EXPECT_EQ(0, close(path_beneath.parent_fd));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ /* Allow reads through /dev/mqueue, but deny writes through both APIs. */
+ for (size_t i = 0; i < ARRAY_SIZE(modes); i++) {
+ fd = open(path, modes[i] | O_CLOEXEC);
+ if (modes[i] == O_RDONLY) {
+ EXPECT_LE(0, fd);
+ } else {
+ EXPECT_EQ(-1, fd);
+ EXPECT_EQ(EACCES, errno);
+ }
+ if (fd >= 0)
+ EXPECT_EQ(0, close(fd));
+
+ fd = mq_open(name, modes[i]);
+ if (modes[i] == O_RDONLY) {
+ EXPECT_LE(0, fd);
+ } else {
+ EXPECT_EQ(-1, fd);
+ EXPECT_EQ(EACCES, errno);
+ }
+ if (fd >= 0)
+ EXPECT_EQ(0, mq_close(fd));
+ }
+
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(name));
+}
+
+TEST_HARNESS_MAIN
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 3/4] Landlock: Add POSIX message queue scoping
2026-11-02 14:45 [PATCH v2 0/4] Landlock: Restrict POSIX message queue creation and access Oxana Kharitonova
2026-11-02 14:45 ` [PATCH v2 1/4] ipc: Move MQUEUE_MAGIC to a shared header Oxana Kharitonova
2026-11-02 14:45 ` [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation Oxana Kharitonova
@ 2026-11-02 14:45 ` Oxana Kharitonova
2026-11-02 15:09 ` sashiko-bot
2026-11-03 4:55 ` kernel test robot
2026-11-02 14:46 ` [PATCH v2 4/4] samples/landlock: Add POSIX message queue scoping to sandboxer Oxana Kharitonova
3 siblings, 2 replies; 11+ messages in thread
From: Oxana Kharitonova @ 2026-11-02 14:45 UTC (permalink / raw)
To: mic, brauner, viro, gnoack, utilityemal77
Cc: linux-security-module, linux-fsdevel, webprosto, landlock
Filesystem access rights control POSIX message queue creation and
opening, but do not distinguish queues by their creator's Landlock
domain. Sandboxes sharing access to the mqueue filesystem may therefore
open each other's queues when other access controls permit it.
Add LANDLOCK_SCOPE_POSIX_MSG_QUEUE to restrict opening queues to those
created within the domain enabling the scope or its descendants.
Associate queues with their creator's domain and check this boundary
before filesystem access checks when opening a queue.
Apply the scope to mq_open() and opens through a mounted mqueue
filesystem. Leave existing, inherited, and passed descriptors usable.
Do not restrict mq_unlink(), allowing applications to clean up stale
queues, subject to other access controls. Document the resulting
limitations around unlinking and queue name reuse.
Expose the scope through ABI 13, with audit logging, audit quieting,
and a dedicated denial trace event.
Add selftests covering domain relationships, queue lifetime, filesystem
permission interactions, path-based opens, descriptor inheritance and
passing, UAPI validation, and audit logging and quieting.
Signed-off-by: Oxana Kharitonova <webprosto@gmail.com>
---
Documentation/trace/events-landlock.rst | 1 +
Documentation/userspace-api/landlock.rst | 56 +-
include/linux/landlock.h | 3 +-
include/trace/events/landlock.h | 46 +
include/uapi/linux/landlock.h | 4 +
security/landlock/audit.c | 5 +
security/landlock/fs.c | 102 ++-
security/landlock/fs.h | 6 +
security/landlock/limits.h | 2 +-
security/landlock/log.c | 4 +
security/landlock/log.h | 1 +
security/landlock/object.c | 2 +
security/landlock/object.h | 7 +
security/landlock/syscalls.c | 2 +-
security/landlock/task.c | 66 ++
security/landlock/task.h | 4 +
security/landlock/trace.c | 7 +
tools/testing/selftests/landlock/audit_test.c | 114 +++
tools/testing/selftests/landlock/base_test.c | 40 +-
.../landlock/scoped_posix_msg_queue_test.c | 846 ++++++++++++++++++
.../testing/selftests/landlock/scoped_test.c | 2 +-
21 files changed, 1312 insertions(+), 8 deletions(-)
create mode 100644 tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c
diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst
index 9ccf2769a2d5..1f55c67650ed 100644
--- a/Documentation/trace/events-landlock.rst
+++ b/Documentation/trace/events-landlock.rst
@@ -46,6 +46,7 @@ Landlock trace events are organized in four categories:
- ``landlock_deny_scope_signal``: signal delivery denied
- ``landlock_deny_scope_abstract_unix_socket``: abstract unix socket
access denied
+- ``landlock_deny_scope_posix_msg_queue``: POSIX message queue open denied
- ``landlock_deny_permission_namespace``: namespace use denied
- ``landlock_deny_permission_capability``: capability use denied
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 1fb91884cb8a..fa65fa4aad02 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -95,7 +95,8 @@ to be explicit about the denied-by-default access rights.
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
.scoped =
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
- LANDLOCK_SCOPE_SIGNAL,
+ LANDLOCK_SCOPE_SIGNAL |
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
.handled_permissions =
LANDLOCK_PERMISSION_NAMESPACE_USE |
LANDLOCK_PERMISSION_CAPABILITY_USE,
@@ -159,6 +160,10 @@ version, and only use the available subset of access rights:
ruleset_attr.handled_permissions &=
~(LANDLOCK_PERMISSION_NAMESPACE_USE |
LANDLOCK_PERMISSION_CAPABILITY_USE);
+ __attribute__((fallthrough));
+ case 12:
+ /* Removes LANDLOCK_SCOPE_POSIX_MSG_QUEUE for ABI < 13 */
+ ruleset_attr.scoped &= ~LANDLOCK_SCOPE_POSIX_MSG_QUEUE;
}
This enables the creation of an inclusive ruleset that will contain our rules.
@@ -482,6 +487,13 @@ created queue remains even though :manpage:`mq_open(3)` fails. In particular,
handling only ``LANDLOCK_ACCESS_FS_READ_FILE`` or
``LANDLOCK_ACCESS_FS_WRITE_FILE`` does not prevent queue creation.
+Independently of these filesystem access rights,
+``LANDLOCK_SCOPE_POSIX_MSG_QUEUE`` can restrict opening queues according to
+their creator's Landlock domain, as described in `IPC scoping`_. When both
+restrictions apply, opening a queue must satisfy both. A queue-scope denial
+returns ``EPERM`` and is checked before Landlock's read or write access
+checks, which return ``EACCES`` on denial.
+
Inheritance
-----------
@@ -535,6 +547,40 @@ The operations which can be scoped are:
A :manpage:`sendto(2)` on a socket which was previously connected will not
be restricted. This works for both datagram and stream sockets.
+``LANDLOCK_SCOPE_POSIX_MSG_QUEUE``
+ Since ABI 13, this limits opening POSIX message queues to those created
+ in the domain that enables this scope or one of its descendants. Queues
+ created outside this boundary, including before entering the domain,
+ cannot be opened. Descendants inherit this boundary; enabling the scope
+ again in a descendant establishes a narrower boundary.
+
+ The scope applies to both :manpage:`mq_open(3)` and :manpage:`open(2)`
+ through a mounted mqueue filesystem, including opening an existing queue
+ with ``O_CREAT``.
+
+ Newly created queues are associated with the creator's current Landlock
+ domain. The scope does not grant permission to create or open a queue:
+ the filesystem restrictions described in `POSIX message queues`_ and
+ other access controls still apply.
+
+ The scope does not restrict use of descriptors opened before entering
+ the domain, inherited from another process, or received through
+ descriptor passing.
+
+ A queue's lifetime is independent of its creator. While its IPC
+ namespace exists, the queue remains until its name is unlinked and all
+ open descriptors are closed. This scope does not restrict
+ :manpage:`mq_unlink(3)`, so a process permitted by other access controls
+ may unlink a queue outside its scope. Leaving unlink unrestricted
+ allows an application to clean up stale queues from earlier runs.
+
+ Queue names are unique within an IPC namespace. After unlinking, the
+ same name can be reused for a new queue, including by a process in
+ another Landlock domain. Existing descriptors still refer to the old
+ queue, while subsequent opens refer to the replacement and are subject
+ to its creator-domain scope check. This scope therefore does not
+ guarantee the identity or availability of a queue identified by name.
+
Scoping does not support exceptions via :manpage:`landlock_add_rule(2)`. If an
operation is scoped within a domain, no rules can be added to allow access to
resources or processes outside of the scope.
@@ -1075,6 +1121,14 @@ fd-reference acquisition (:manpage:`open_tree(2)`, :manpage:`fsmount(2)`) with
the new ``LANDLOCK_PERMISSION_NAMESPACE_USE`` permission flag and
``LANDLOCK_RULE_NAMESPACE`` rule type.
+POSIX message queue scoping (ABI < 13)
+-------------------------------------
+
+Starting with ABI version 13, ``LANDLOCK_SCOPE_POSIX_MSG_QUEUE`` can restrict
+opening POSIX message queues according to the creator's Landlock domain.
+This scope is independent of the filesystem access rights used to control
+queue creation and opening.
+
.. _kernel_support:
Kernel support
diff --git a/include/linux/landlock.h b/include/linux/landlock.h
index 0ee4880988ba..17501fd05b7a 100644
--- a/include/linux/landlock.h
+++ b/include/linux/landlock.h
@@ -53,7 +53,8 @@
#define _LANDLOCK_SCOPE_NAMES \
_LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, \
"abstract_unix_socket"), \
- _LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_SIGNAL, "signal")
+ _LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_SIGNAL, "signal"), \
+ _LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_POSIX_MSG_QUEUE, "posix_msg_queue")
#define _LANDLOCK_PERMISSION_NAMESPACE_NAME "namespace"
#define _LANDLOCK_PERMISSION_CAPABILITY_NAME "capability"
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index 4f19d1c04d74..1d02dea3fff1 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -1253,6 +1253,52 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
__get_dynamic_array_len(sun_path) - 1))
);
+/**
+ * landlock_deny_scope_posix_msg_queue - POSIX message queue access denied
+ * by LANDLOCK_SCOPE_POSIX_MSG_QUEUE
+ *
+ * @hierarchy: Denying domain's hierarchy node (never NULL).
+ * @same_exec: Whether the current task entered the denying domain itself.
+ * @logged: The domain's audit-logging decision for this denial.
+ * @creator_domain_id: Queue creator's domain ID, or 0 if unsandboxed.
+ * @file: Queue file being opened (never NULL).
+ *
+ * Emitted when a Landlock domain denies opening a scoped-out queue.
+ * The device and inode identify the queue within its IPC namespace.
+ */
+TRACE_EVENT(landlock_deny_scope_posix_msg_queue,
+
+ TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
+ bool logged, u64 creator_domain_id, const struct file *file),
+
+ TP_ARGS(hierarchy, same_exec, logged, creator_domain_id, file),
+
+ TP_STRUCT__entry(
+ __field( __u64, domain_id )
+ __field( bool, same_exec )
+ __field( bool, logged )
+ __field( __u64, creator_domain_id)
+ __field( dev_t, dev )
+ __field( ino_t, ino )
+ ),
+
+ TP_fast_assign(
+ const struct inode *inode = file_inode(file);
+
+ __entry->domain_id = hierarchy->id;
+ __entry->same_exec = same_exec;
+ __entry->logged = logged;
+ __entry->creator_domain_id = creator_domain_id;
+ __entry->dev = inode->i_sb->s_dev;
+ __entry->ino = inode->i_ino;
+ ),
+
+ TP_printk("domain=%llx same_exec=%d logged=%d creator_domain=%llx dev=%u:%u ino=%lu",
+ __entry->domain_id, __entry->same_exec, __entry->logged,
+ __entry->creator_domain_id,
+ MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino)
+);
+
#undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES
#undef _LANDLOCK_NAME_ENTRY
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 04a18824c700..a011667dcfd1 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -588,10 +588,14 @@ struct landlock_capability_attr {
* related Landlock domain (e.g., a parent domain or a non-sandboxed process).
* - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal
* to another process outside the domain.
+ * - %LANDLOCK_SCOPE_POSIX_MSG_QUEUE: Restrict a sandboxed process from opening
+ * a POSIX message queue outside the domain. Support added in Landlock ABI
+ * version 13.
*/
/* clang-format off */
#define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0)
#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1)
+#define LANDLOCK_SCOPE_POSIX_MSG_QUEUE (1ULL << 2)
/* clang-format on*/
/**
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index be49ef47b4ea..df9104b2939d 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -80,6 +80,10 @@ get_blocker(const enum landlock_request_type type,
WARN_ON_ONCE(access_bit != -1);
return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)];
+ case LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE:
+ WARN_ON_ONCE(access_bit != -1);
+ return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_POSIX_MSG_QUEUE)];
+
case LANDLOCK_REQUEST_NAMESPACE:
case LANDLOCK_REQUEST_CAPABILITY:
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(permission_strings)))
@@ -113,6 +117,7 @@ blocker_prefix(const enum landlock_request_type type)
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ case LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE:
return "scope.";
case LANDLOCK_REQUEST_NAMESPACE:
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index d5dcb19d5eb1..e9c52332dfcd 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -53,6 +53,7 @@
#include "object.h"
#include "ruleset.h"
#include "setup.h"
+#include "task.h"
#include <trace/events/landlock.h>
@@ -1374,6 +1375,80 @@ static void hook_inode_free_security_rcu(void *inode_security)
WARN_ON_ONCE(inode_sec->object);
}
+/*
+ * Tag a newly created POSIX message queue with its creator's domain.
+ *
+ * This is the earliest reachable point with both the creator's context and a
+ * fully initialized inode.
+ */
+static void hook_d_instantiate(struct dentry *const dentry,
+ struct inode *const inode)
+{
+ struct landlock_domain *dom;
+ struct landlock_object *object;
+
+ if (!landlock_is_posix_mqueue_inode(inode))
+ return;
+
+ dom = landlock_get_current_domain();
+ if (!dom)
+ return;
+
+ object = get_inode_object(inode);
+ if (IS_ERR(object))
+ return;
+
+ spin_lock(&object->lock);
+ if (WARN_ON_ONCE(object->domain)) {
+ spin_unlock(&object->lock);
+ landlock_put_object(object);
+ return;
+ }
+
+ landlock_get_domain(dom);
+ object->domain = dom;
+ spin_unlock(&object->lock);
+}
+
+/*
+ * Drop the reference retained by hook_d_instantiate() for a POSIX message
+ * queue once its sole name is unlinked.
+ */
+static int hook_inode_unlink(struct inode *const dir,
+ struct dentry *const dentry)
+{
+ const struct inode *const inode = d_backing_inode(dentry);
+ struct landlock_domain *domain;
+ struct landlock_object *object;
+
+ if (!landlock_is_posix_mqueue_inode(inode))
+ return 0;
+
+ rcu_read_lock();
+ object = rcu_dereference(landlock_inode(inode)->object);
+ /* The temporary reference protects against concurrent object release. */
+ if (!object || !refcount_inc_not_zero(&object->usage)) {
+ rcu_read_unlock();
+ return 0;
+ }
+ rcu_read_unlock();
+
+ spin_lock(&object->lock);
+ domain = object->domain;
+ object->domain = NULL;
+ spin_unlock(&object->lock);
+
+ /* Drop the temporary reference acquired by refcount_inc_not_zero(). */
+ landlock_put_object(object);
+ if (domain) {
+ landlock_put_domain_deferred(domain);
+ /* Drops the reference retained by hook_d_instantiate(). */
+ landlock_put_object(object);
+ }
+
+ return 0;
+}
+
/* Super-block hooks */
/*
@@ -1390,6 +1465,7 @@ static void hook_sb_delete(struct super_block *const sb)
spin_lock(&sb->s_inode_list_lock);
list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+ struct landlock_domain *domain = NULL;
struct landlock_object *object;
/* Only handles referenced inodes. */
@@ -1432,6 +1508,13 @@ static void hook_sb_delete(struct super_block *const sb)
*/
spin_lock(&object->lock);
if (object->underobj == inode) {
+ /*
+ * Claim the domain and retained object references. Unlink
+ * uses the same NULL transition, so only one teardown path
+ * can release them.
+ */
+ domain = object->domain;
+ object->domain = NULL;
object->underobj = NULL;
spin_unlock(&object->lock);
rcu_read_unlock();
@@ -1457,7 +1540,7 @@ static void hook_sb_delete(struct super_block *const sb)
rcu_read_unlock();
}
- if (prev_inode) {
+ if (prev_inode || domain) {
/*
* At this point, we still own the __iget() reference
* that we just set in this loop walk. Therefore we
@@ -1469,7 +1552,12 @@ static void hook_sb_delete(struct super_block *const sb)
* We can now actually put the inode reference from the
* previous loop walk, which is not needed anymore.
*/
- iput(prev_inode);
+ if (prev_inode)
+ iput(prev_inode);
+ if (domain) {
+ landlock_put_domain_deferred(domain);
+ landlock_put_object(object);
+ }
cond_resched();
spin_lock(&sb->s_inode_list_lock);
}
@@ -1891,6 +1979,14 @@ static int hook_file_open(struct file *const file)
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(file->f_cred, any_fs, NULL);
struct landlock_request request = {};
+ int err;
+
+ /*
+ * Check queue creator-domain scoping before filesystem access rights.
+ */
+ err = landlock_check_posix_mqueue_open(file);
+ if (err)
+ return err;
if (!subject)
return 0;
@@ -2115,6 +2211,8 @@ static void hook_file_free_security(struct file *file)
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
LSM_HOOK_INIT(inode_create, hook_inode_create),
+ LSM_HOOK_INIT(d_instantiate, hook_d_instantiate),
+ LSM_HOOK_INIT(inode_unlink, hook_inode_unlink),
LSM_HOOK_INIT(sb_delete, hook_sb_delete),
LSM_HOOK_INIT(sb_mount, hook_sb_mount),
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index 05e36ef1acf7..85025030659c 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -150,6 +150,12 @@ static inline bool landlock_is_posix_mqueue_fs(const struct inode *const inode)
return inode->i_sb->s_magic == MQUEUE_MAGIC;
}
+static inline bool
+landlock_is_posix_mqueue_inode(const struct inode *const inode)
+{
+ return S_ISREG(inode->i_mode) && landlock_is_posix_mqueue_fs(inode);
+}
+
static inline struct landlock_superblock_security *
landlock_superblock(const struct super_block *const superblock)
{
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index ddd300e10689..469ddcb4a65f 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -29,7 +29,7 @@
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
-#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL
+#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_POSIX_MSG_QUEUE
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
diff --git a/security/landlock/log.c b/security/landlock/log.c
index 43efbb95725c..d2e0b5de3ce3 100644
--- a/security/landlock/log.c
+++ b/security/landlock/log.c
@@ -406,6 +406,7 @@ static bool is_valid_request(const struct landlock_request *const request)
case LANDLOCK_REQUEST_NET_ACCESS:
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ case LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE:
if (WARN_ON_ONCE(request->permission))
return false;
break;
@@ -485,6 +486,9 @@ is_denial_quieted(const struct landlock_request *const request,
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
return !!(youngest_denied->quiet_access.scope &
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ case LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE:
+ return !!(youngest_denied->quiet_access.scope &
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
case LANDLOCK_REQUEST_NAMESPACE:
return !!(youngest_denied->quiet_permission.ns_types &
landlock_ns_type_to_bit(request->audit.u.ns.ns_type));
diff --git a/security/landlock/log.h b/security/landlock/log.h
index 596feeeb7aa6..8eec738d3ca4 100644
--- a/security/landlock/log.h
+++ b/security/landlock/log.h
@@ -25,6 +25,7 @@ enum landlock_request_type {
LANDLOCK_REQUEST_NET_ACCESS,
LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
LANDLOCK_REQUEST_SCOPE_SIGNAL,
+ LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE,
LANDLOCK_REQUEST_NAMESPACE,
LANDLOCK_REQUEST_CAPABILITY,
};
diff --git a/security/landlock/object.c b/security/landlock/object.c
index 0d6e159ef8b5..bea45c855a91 100644
--- a/security/landlock/object.c
+++ b/security/landlock/object.c
@@ -15,6 +15,7 @@
#include <linux/slab.h>
#include <linux/spinlock.h>
+#include "domain.h"
#include "object.h"
struct landlock_object *
@@ -62,6 +63,7 @@ void landlock_put_object(struct landlock_object *const object)
* @object->underobj to @object (if it still exists).
*/
object->underops->release(object);
+ landlock_put_domain_deferred(object->domain);
kfree_rcu(object, rcu_free);
}
}
diff --git a/security/landlock/object.h b/security/landlock/object.h
index 5f28c35e8aa8..3518ee36ab71 100644
--- a/security/landlock/object.h
+++ b/security/landlock/object.h
@@ -14,6 +14,7 @@
#include <linux/spinlock.h>
struct landlock_object;
+struct landlock_domain;
/**
* struct landlock_object_underops - Operations on an underlying object
@@ -74,6 +75,12 @@ struct landlock_object {
*/
const struct landlock_object_underops *underops;
};
+ /**
+ * @domain: Domain associated with a POSIX message queue object. While
+ * non-NULL, the object owns both a reference to this domain and the usage
+ * reference retained when the queue was tagged. Protected by @lock.
+ */
+ struct landlock_domain *domain;
};
struct landlock_object *
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index fb5af318b69b..11d3a2abcec1 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -191,7 +191,7 @@ static const struct file_operations ruleset_fops = {
* If the change involves a fix that requires userspace awareness, also update
* the errata documentation in Documentation/userspace-api/landlock.rst .
*/
-const int landlock_abi_version = 12;
+const int landlock_abi_version = 13;
/**
* sys_landlock_create_ruleset - Create a new ruleset
diff --git a/security/landlock/task.c b/security/landlock/task.c
index d9eae86fc552..0daa7edff2d7 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -522,6 +522,72 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
return -EPERM;
}
+static const struct access_masks posix_mqueue_scope = {
+ .scope = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+};
+
+/**
+ * landlock_check_posix_mqueue_open - Deny opening a POSIX message queue
+ * created by a task from a different (non-ancestor) domain
+ *
+ * @file: The mqueuefs file being opened.
+ *
+ * Return: -EPERM if the open must be denied, 0 otherwise.
+ */
+int landlock_check_posix_mqueue_open(struct file *const file)
+{
+ const struct inode *const inode = file_inode(file);
+ const struct landlock_cred_security *subject;
+ struct landlock_object *object;
+ struct landlock_domain *creator_domain = NULL;
+ bool is_scoped;
+ size_t handle_layer;
+
+ if (!landlock_is_posix_mqueue_inode(inode))
+ return 0;
+
+ subject = landlock_get_applicable_subject(
+ file->f_cred, posix_mqueue_scope, &handle_layer);
+ if (!subject)
+ return 0;
+
+ rcu_read_lock();
+ object = rcu_dereference(landlock_inode(inode)->object);
+ /* The temporary reference protects against concurrent object release. */
+ if (object && !refcount_inc_not_zero(&object->usage))
+ object = NULL;
+ rcu_read_unlock();
+
+ if (object) {
+ spin_lock(&object->lock);
+ creator_domain = object->domain;
+ landlock_get_domain(creator_domain);
+ spin_unlock(&object->lock);
+ }
+
+ /* Untagged queues were created outside any Landlock domain. */
+ is_scoped = domain_is_scoped(subject->domain, creator_domain,
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ if (!is_scoped) {
+ landlock_put_domain_deferred(creator_domain);
+ landlock_put_object(object);
+ return 0;
+ }
+
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE,
+ .other_domain_id = creator_domain ? creator_domain->hierarchy->id : 0,
+ .audit = {
+ .type = LSM_AUDIT_DATA_FILE,
+ .u.file = file,
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+ landlock_put_domain_deferred(creator_domain);
+ landlock_put_object(object);
+ return -EPERM;
+}
+
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
diff --git a/security/landlock/task.h b/security/landlock/task.h
index 7c00360219a2..0b031dc17bbf 100644
--- a/security/landlock/task.h
+++ b/security/landlock/task.h
@@ -9,6 +9,10 @@
#ifndef _SECURITY_LANDLOCK_TASK_H
#define _SECURITY_LANDLOCK_TASK_H
+#include <linux/fs.h>
+
__init void landlock_add_task_hooks(void);
+int landlock_check_posix_mqueue_open(struct file *const file);
+
#endif /* _SECURITY_LANDLOCK_TASK_H */
diff --git a/security/landlock/trace.c b/security/landlock/trace.c
index 995c6b948e16..1d2bb0c3175b 100644
--- a/security/landlock/trace.c
+++ b/security/landlock/trace.c
@@ -246,6 +246,13 @@ void landlock_trace_denial(
request->other_domain_id,
request->audit.u.net->sk);
break;
+ case LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE:
+ if (trace_landlock_deny_scope_posix_msg_queue_enabled())
+ trace_landlock_deny_scope_posix_msg_queue(
+ youngest_denied, same_exec, logged,
+ request->other_domain_id,
+ request->audit.u.file);
+ break;
default:
WARN_ONCE(1, "Unhandled Landlock request type %d",
request->type);
diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index 4bea8c880a4d..a15c1e5d277f 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -10,6 +10,7 @@
#include <fcntl.h>
#include <limits.h>
#include <linux/landlock.h>
+#include <mqueue.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/mount.h>
@@ -997,4 +998,117 @@ TEST_F(audit_exec, signal_and_open)
EXPECT_EQ(0, records.access);
}
+FIXTURE(posix_mqueue_audit)
+{
+ struct audit_filter audit_filter;
+ int audit_fd;
+};
+
+FIXTURE_VARIANT(posix_mqueue_audit)
+{
+ const __u64 quiet_scoped;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(posix_mqueue_audit, logged) {
+ /* clang-format on */
+ .quiet_scoped = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(posix_mqueue_audit, quiet) {
+ /* clang-format on */
+ .quiet_scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+};
+
+FIXTURE_SETUP(posix_mqueue_audit)
+{
+ disable_caps(_metadata);
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
+ EXPECT_LE(0, self->audit_fd);
+ drop_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN_PARENT(posix_mqueue_audit)
+{
+ EXPECT_EQ(0, audit_cleanup(-1, NULL));
+}
+
+/* Checks audit logging and quieting of a denied POSIX message queue open. */
+TEST_F(posix_mqueue_audit, open)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+ .quiet_scoped = variant->quiet_scoped,
+ };
+ char mq_name[NAME_MAX], log_match[PATH_MAX];
+ int pipe_parent[2], pipe_child[2], ruleset_fd, status;
+ struct audit_records records;
+ char buf;
+ pid_t child;
+ mqd_t mq;
+
+ ASSERT_LT(0,
+ snprintf(mq_name, sizeof(mq_name),
+ "/selftests-landlock-audit-mq-tid%d", sys_gettid()));
+ mq_unlink(mq_name);
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ EXPECT_EQ(0, close(pipe_child[0]));
+
+ mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq);
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(pipe_parent[0]));
+ EXPECT_EQ(0, close(pipe_child[1]));
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
+
+ EXPECT_EQ(-1, mq_open(mq_name, O_RDWR));
+ EXPECT_EQ(EPERM, errno);
+
+ if (variant->quiet_scoped) {
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
+ } else {
+ ASSERT_LT(
+ 0,
+ snprintf(
+ log_match, sizeof(log_match),
+ REGEX_LANDLOCK_PREFIX
+ " blockers=scope\\.posix_msg_queue path=\\\"%s\\\" dev=\\\"[^\\\"]\\+\\\" ino=[0-9]\\+$",
+ mq_name));
+ EXPECT_EQ(0, audit_match_record(self->audit_fd,
+ AUDIT_LANDLOCK_ACCESS,
+ log_match, NULL));
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ }
+
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 89c0d96607de..1f361c6fe3ee 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(12, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(13, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
@@ -755,4 +755,42 @@ TEST(invalid_quiet_bits_2)
ASSERT_EQ(EINVAL, errno);
}
+/* Checks the UAPI validation of LANDLOCK_SCOPE_POSIX_MSG_QUEUE. */
+TEST(posix_mqueue_scope_uapi)
+{
+ const struct landlock_ruleset_attr scoped = {
+ .scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+ };
+ const struct landlock_ruleset_attr quiet = {
+ .scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+ .quiet_scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+ };
+ const struct landlock_ruleset_attr quiet_without_scope = {
+ .scoped = LANDLOCK_SCOPE_SIGNAL,
+ .quiet_scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+ };
+ const struct landlock_ruleset_attr unknown_scope = {
+ .scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE << 1,
+ };
+ int ruleset_fd;
+
+ drop_caps(_metadata);
+
+ ruleset_fd = landlock_create_ruleset(&scoped, sizeof(scoped), 0);
+ ASSERT_LE(0, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ ruleset_fd = landlock_create_ruleset(&quiet, sizeof(quiet), 0);
+ ASSERT_LE(0, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ EXPECT_EQ(-1, landlock_create_ruleset(&quiet_without_scope,
+ sizeof(quiet_without_scope), 0));
+ EXPECT_EQ(EINVAL, errno);
+
+ EXPECT_EQ(-1, landlock_create_ruleset(&unknown_scope,
+ sizeof(unknown_scope), 0));
+ EXPECT_EQ(EINVAL, errno);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c b/tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c
new file mode 100644
index 000000000000..4475746081bc
--- /dev/null
+++ b/tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c
@@ -0,0 +1,846 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - POSIX message queue scoping
+ *
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/landlock.h>
+#include <mqueue.h>
+#include <sched.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "scoped_common.h"
+
+static void set_mq_name(char *const name, const size_t size)
+{
+ snprintf(name, size, "/selftests-landlock-mq-tid%d", sys_gettid());
+}
+
+FIXTURE(scoped_domains)
+{
+ char mq_name[NAME_MAX];
+};
+
+#include "scoped_base_variants.h"
+
+FIXTURE_SETUP(scoped_domains)
+{
+ drop_caps(_metadata);
+
+ set_mq_name(self->mq_name, sizeof(self->mq_name));
+ /* Removes a possibly stale queue from a previous run. */
+ mq_unlink(self->mq_name);
+}
+
+FIXTURE_TEARDOWN(scoped_domains)
+{
+}
+
+/*
+ * The parent creates the message queue, then the child tries
+ * to open it, with scoped domain(s) or no domain at all.
+ */
+TEST_F(scoped_domains, open_parent_queue)
+{
+ pid_t child;
+ int status;
+ int pipe_parent[2], pipe_child[2];
+ char buf;
+ mqd_t mq;
+
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+
+ if (variant->domain_both)
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ mqd_t mq_child;
+
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ EXPECT_EQ(0, close(pipe_child[0]));
+
+ if (variant->domain_child)
+ create_scoped_domain(_metadata,
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ /* Waits for the parent to create the queue. */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+
+ mq_child = mq_open(self->mq_name, O_RDWR);
+ if (!variant->domain_child) {
+ EXPECT_LE(0, mq_child);
+ if (mq_child >= 0)
+ EXPECT_EQ(0, mq_close(mq_child));
+ } else {
+ EXPECT_EQ(-1, mq_child);
+ EXPECT_EQ(EPERM, errno);
+ }
+
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(pipe_parent[0]));
+ EXPECT_EQ(0, close(pipe_child[1]));
+
+ if (variant->domain_parent)
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ mq = mq_open(self->mq_name, O_CREAT | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq);
+
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(self->mq_name));
+
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+}
+
+/*
+ * The child creates the message queue, then the parent tries
+ * to open it, with scoped domain(s) or no domain at all.
+ */
+TEST_F(scoped_domains, open_child_queue)
+{
+ pid_t child;
+ bool can_open_child_queue;
+ int status;
+ int pipe_parent[2], pipe_child[2];
+ char buf;
+ mqd_t mq;
+
+ can_open_child_queue = !variant->domain_parent;
+
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+
+ if (variant->domain_both)
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ mqd_t mq_child;
+
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ EXPECT_EQ(0, close(pipe_child[0]));
+
+ if (variant->domain_child)
+ create_scoped_domain(_metadata,
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ /* Waits for the parent to be in a domain, if any. */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+
+ mq_child = mq_open(self->mq_name, O_CREAT | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq_child);
+
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+
+ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+ EXPECT_EQ(0, mq_close(mq_child));
+ EXPECT_EQ(0, mq_unlink(self->mq_name));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(pipe_parent[0]));
+ EXPECT_EQ(0, close(pipe_child[1]));
+
+ if (variant->domain_parent)
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ /* Signals that the parent is in a domain, if any. */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+ /* Waits for the child to create the queue. */
+ ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
+
+ mq = mq_open(self->mq_name, O_RDWR);
+ if (can_open_child_queue) {
+ EXPECT_LE(0, mq);
+ if (mq >= 0)
+ EXPECT_EQ(0, mq_close(mq));
+ } else {
+ EXPECT_EQ(-1, mq);
+ EXPECT_EQ(EPERM, errno);
+ }
+
+ /* Signals to the child that the open attempt is done. */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+}
+
+/*
+ * O_CREAT must not bypass message queue scoping when the named queue already
+ * exists. In this case mq_open() opens the existing object instead of
+ * creating a new one.
+ */
+TEST(open_existing_queue_with_create)
+{
+ char mq_name[NAME_MAX];
+ int pipe_parent[2], status;
+ pid_t child;
+ mqd_t mq;
+
+ drop_caps(_metadata);
+ set_mq_name(mq_name, sizeof(mq_name));
+ mq_unlink(mq_name);
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ char buf;
+
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+
+ mq = mq_open(mq_name, O_CREAT | O_RDWR, 0600, NULL);
+ EXPECT_EQ(-1, mq);
+ EXPECT_EQ(EPERM, errno);
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(pipe_parent[0]));
+
+ mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq);
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ EXPECT_EQ(0, close(pipe_parent[1]));
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+}
+
+/*
+ * Unlinking a queue while it is still open must detach its creator domain.
+ * Reusing the same name for a queue created in a sibling domain must associate
+ * the new queue with the new creator without affecting the unlinked object.
+ */
+TEST(unlink_and_recreate_in_sibling_domain)
+{
+ char mq_name[NAME_MAX], buf;
+ int pipe_parent[2], pipe_child[2], status;
+ struct mq_attr attr;
+ pid_t child;
+ mqd_t old_mq, new_mq;
+
+ drop_caps(_metadata);
+ set_mq_name(mq_name, sizeof(mq_name));
+ mq_unlink(mq_name);
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+
+ /* Both processes inherit this common parent domain. */
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ EXPECT_EQ(0, close(pipe_child[0]));
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ /* Waits until the old queue has been unlinked. */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+ new_mq =
+ mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, new_mq);
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+
+ /* Keeps the new queue alive until the parent checks its scope. */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+ EXPECT_EQ(0, mq_close(new_mq));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(pipe_parent[0]));
+ EXPECT_EQ(0, close(pipe_child[1]));
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ old_mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, old_mq);
+ ASSERT_EQ(0, mq_unlink(mq_name));
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
+
+ new_mq = mq_open(mq_name, O_RDWR);
+ EXPECT_EQ(-1, new_mq);
+ EXPECT_EQ(EPERM, errno);
+ /* The descriptor for the unlinked queue must remain usable. */
+ EXPECT_EQ(0, mq_getattr(old_mq, &attr));
+
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(0, mq_close(old_mq));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+}
+
+/*
+ * A queue must retain its creator domain after the creator exits. A process
+ * in the parent domain and an unsandboxed process may open the stale queue,
+ * whereas a process in a new sibling domain must not.
+ */
+TEST(open_queue_after_creator_exits)
+{
+ char mq_name[NAME_MAX], buf;
+ int pipe_unscoped[2], status;
+ pid_t creator, sibling, unscoped;
+ mqd_t mq;
+
+ drop_caps(_metadata);
+ set_mq_name(mq_name, sizeof(mq_name));
+ mq_unlink(mq_name);
+ ASSERT_EQ(0, pipe2(pipe_unscoped, O_CLOEXEC));
+
+ /* Forks before entering a domain to retain an unsandboxed process. */
+ unscoped = fork();
+ ASSERT_LE(0, unscoped);
+ if (unscoped == 0) {
+ EXPECT_EQ(0, close(pipe_unscoped[1]));
+ ASSERT_EQ(1, read(pipe_unscoped[0], &buf, 1));
+ mq = mq_open(mq_name, O_RDWR);
+ EXPECT_LE(0, mq);
+ if (mq >= 0)
+ EXPECT_EQ(0, mq_close(mq));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(pipe_unscoped[0]));
+
+ /* This domain is the parent of the creator and sibling domains. */
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ creator = fork();
+ ASSERT_LE(0, creator);
+ if (creator == 0) {
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq);
+ EXPECT_EQ(0, mq_close(mq));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ ASSERT_EQ(creator, waitpid(creator, &status, 0));
+ ASSERT_EQ(1, WIFEXITED(status));
+ ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ /* The parent domain may access a queue created in its subdomain. */
+ mq = mq_open(mq_name, O_RDWR);
+ ASSERT_LE(0, mq);
+ EXPECT_EQ(0, mq_close(mq));
+
+ sibling = fork();
+ ASSERT_LE(0, sibling);
+ if (sibling == 0) {
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ mq = mq_open(mq_name, O_RDWR);
+ EXPECT_EQ(-1, mq);
+ EXPECT_EQ(EPERM, errno);
+ _exit(_metadata->exit_code);
+ return;
+ }
+ ASSERT_EQ(sibling, waitpid(sibling, &status, 0));
+ ASSERT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ /* A process without a Landlock domain is not restricted by the scope. */
+ ASSERT_EQ(1, write(pipe_unscoped[1], ".", 1));
+ EXPECT_EQ(0, close(pipe_unscoped[1]));
+ ASSERT_EQ(unscoped, waitpid(unscoped, &status, 0));
+ ASSERT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+}
+
+/*
+ * A child domain must not open a queue created by its parent domain, including
+ * after the process that created the queue has exited.
+ */
+TEST(open_queue_from_child_after_creator_exits)
+{
+ char mq_name[NAME_MAX], buf, result;
+ int pipe_ready[2], pipe_access[2], pipe_result[2], status;
+ pid_t creator;
+
+ drop_caps(_metadata);
+ set_mq_name(mq_name, sizeof(mq_name));
+ mq_unlink(mq_name);
+ ASSERT_EQ(0, pipe2(pipe_ready, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_access, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_result, O_CLOEXEC));
+
+ creator = fork();
+ ASSERT_LE(0, creator);
+ if (creator == 0) {
+ pid_t manager;
+ mqd_t mq;
+
+ EXPECT_EQ(0, close(pipe_ready[1]));
+ EXPECT_EQ(0, close(pipe_access[1]));
+ EXPECT_EQ(0, close(pipe_result[0]));
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq);
+ EXPECT_EQ(0, mq_close(mq));
+
+ manager = fork();
+ ASSERT_LE(0, manager);
+ if (manager == 0) {
+ int child_status;
+ pid_t child;
+
+ /* Retains the creator domain until a child can be created. */
+ ASSERT_EQ(1, write(pipe_result[1], ".", 1));
+ ASSERT_EQ(1, read(pipe_access[0], &buf, 1));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ create_scoped_domain(
+ _metadata,
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ mq = mq_open(mq_name, O_RDWR);
+ if (mq == -1 && errno == EPERM)
+ result = KSFT_PASS;
+ else
+ result = KSFT_FAIL;
+ if (mq >= 0)
+ EXPECT_EQ(0, mq_close(mq));
+ _exit(result);
+ return;
+ }
+ ASSERT_EQ(child, waitpid(child, &child_status, 0));
+ if (WIFEXITED(child_status))
+ result = WEXITSTATUS(child_status);
+ else
+ result = KSFT_FAIL;
+ ASSERT_EQ(1, write(pipe_result[1], &result, 1));
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ /* Exits once the same-domain manager is ready. */
+ ASSERT_EQ(1, read(pipe_ready[0], &buf, 1));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(pipe_ready[0]));
+ EXPECT_EQ(0, close(pipe_access[0]));
+ EXPECT_EQ(0, close(pipe_result[1]));
+
+ /* Relays the manager's ready notification to the creator. */
+ ASSERT_EQ(1, read(pipe_result[0], &buf, 1));
+ ASSERT_EQ(1, write(pipe_ready[1], ".", 1));
+ ASSERT_EQ(creator, waitpid(creator, &status, 0));
+ ASSERT_EQ(1, WIFEXITED(status));
+ ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ ASSERT_EQ(1, write(pipe_access[1], ".", 1));
+ ASSERT_EQ(1, read(pipe_result[0], &result, 1));
+ EXPECT_EQ(KSFT_PASS, result);
+ EXPECT_EQ(0, mq_unlink(mq_name));
+}
+
+/*
+ * Destroying an IPC namespace containing a queue tagged with a Landlock domain
+ * must release both the queue object and its retained domain reference.
+ */
+TEST(posix_mqueue_ipc_namespace_lifetime)
+{
+ int status;
+ pid_t child;
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ char mq_name[NAME_MAX];
+ mqd_t mq;
+
+ if (unshare(CLONE_NEWIPC)) {
+ if (errno == EPERM || errno == EINVAL)
+ _exit(KSFT_SKIP);
+ TH_LOG("Failed to create an IPC namespace: %s",
+ strerror(errno));
+ _exit(KSFT_FAIL);
+ }
+ drop_caps(_metadata);
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ set_mq_name(mq_name, sizeof(mq_name));
+ mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq);
+ EXPECT_EQ(0, mq_close(mq));
+
+ /* Exiting destroys the IPC namespace with the queue still linked. */
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_EQ(1, WIFEXITED(status));
+ if (WEXITSTATUS(status) == KSFT_SKIP)
+ SKIP(return, "IPC namespaces are unavailable or not permitted");
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+/*
+ * A process must always be able to open a queue it created within its own
+ * domain, whatever the enforced scope is.
+ */
+TEST(create_and_open_same_domain)
+{
+ char mq_name[NAME_MAX];
+ mqd_t mq_create, mq_open_again;
+
+ drop_caps(_metadata);
+ set_mq_name(mq_name, sizeof(mq_name));
+ mq_unlink(mq_name);
+
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+
+ mq_create = mq_open(mq_name, O_CREAT | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, mq_create);
+
+ mq_open_again = mq_open(mq_name, O_RDWR);
+ EXPECT_LE(0, mq_open_again);
+ if (mq_open_again >= 0)
+ EXPECT_EQ(0, mq_close(mq_open_again));
+
+ EXPECT_EQ(0, mq_close(mq_create));
+ EXPECT_EQ(0, mq_unlink(mq_name));
+}
+
+/*
+ * Queue scoping and filesystem access must both allow an open. A same-domain
+ * queue is denied by READ_FILE, and an outside queue is denied by scoping.
+ */
+TEST(posix_mqueue_scope_with_read_file_denied)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
+ .scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+ };
+ char outside_name[NAME_MAX], inside_name[NAME_MAX];
+ char file_path[] = "/tmp/landlock-mq-read-XXXXXX";
+ mqd_t outside_mq, inside_mq, reopened_mq;
+ int fd;
+
+ drop_caps(_metadata);
+ set_mq_name(outside_name, sizeof(outside_name));
+ ASSERT_LT(0, snprintf(inside_name, sizeof(inside_name),
+ "/selftests-landlock-mq-inside-tid%d",
+ sys_gettid()));
+ mq_unlink(outside_name);
+ mq_unlink(inside_name);
+
+ outside_mq =
+ mq_open(outside_name, O_CREAT | O_EXCL | O_RDONLY, 0600, NULL);
+ if (outside_mq < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, outside_mq);
+
+ fd = mkstemp(file_path);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, close(fd));
+ fd = open(file_path, O_RDONLY | O_CLOEXEC);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, close(fd));
+
+ drop_access_rights(_metadata, &ruleset_attr);
+
+ fd = open(file_path, O_RDONLY | O_CLOEXEC);
+ EXPECT_EQ(-1, fd);
+ EXPECT_EQ(EACCES, errno);
+ if (fd >= 0)
+ EXPECT_EQ(0, close(fd));
+ EXPECT_EQ(0, unlink(file_path));
+
+ /* Create without requesting the denied READ_FILE access. */
+ inside_mq =
+ mq_open(inside_name, O_CREAT | O_EXCL | O_WRONLY, 0600, NULL);
+ ASSERT_LE(0, inside_mq);
+ reopened_mq = mq_open(inside_name, O_RDONLY);
+ EXPECT_EQ(-1, reopened_mq);
+ EXPECT_EQ(EACCES, errno);
+ if (reopened_mq >= 0)
+ EXPECT_EQ(0, mq_close(reopened_mq));
+
+ reopened_mq = mq_open(outside_name, O_RDONLY);
+ EXPECT_EQ(-1, reopened_mq);
+ EXPECT_EQ(EPERM, errno);
+ if (reopened_mq >= 0)
+ EXPECT_EQ(0, mq_close(reopened_mq));
+
+ EXPECT_EQ(0, mq_close(inside_mq));
+ EXPECT_EQ(0, mq_close(outside_mq));
+ EXPECT_EQ(0, mq_unlink(inside_name));
+ EXPECT_EQ(0, mq_unlink(outside_name));
+}
+
+/* Granted filesystem access must still be subject to queue scoping. */
+TEST(posix_mqueue_scope_with_read_file_allowed)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
+ .scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
+ };
+ struct landlock_path_beneath_attr path_beneath = {
+ .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
+ };
+ char outside_name[NAME_MAX], inside_name[NAME_MAX], path[PATH_MAX];
+ struct stat queue_stat, path_stat;
+ mqd_t outside_mq, inside_mq, reopened_mq;
+ int ruleset_fd, fd;
+
+ drop_caps(_metadata);
+ set_mq_name(outside_name, sizeof(outside_name));
+ ASSERT_LT(0, snprintf(inside_name, sizeof(inside_name),
+ "/selftests-landlock-mq-inside-tid%d",
+ sys_gettid()));
+ ASSERT_LT(0,
+ snprintf(path, sizeof(path), "/dev/mqueue%s", outside_name));
+ mq_unlink(outside_name);
+ mq_unlink(inside_name);
+ outside_mq =
+ mq_open(outside_name, O_CREAT | O_EXCL | O_RDONLY, 0600, NULL);
+ if (outside_mq < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, outside_mq);
+ ASSERT_EQ(0, fstat(outside_mq, &queue_stat));
+ if (stat(path, &path_stat) || queue_stat.st_dev != path_stat.st_dev ||
+ queue_stat.st_ino != path_stat.st_ino) {
+ EXPECT_EQ(0, mq_close(outside_mq));
+ EXPECT_EQ(0, mq_unlink(outside_name));
+ SKIP(return, "/dev/mqueue does not expose this IPC namespace");
+ }
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, close(fd));
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ path_beneath.parent_fd = open("/dev/mqueue", O_PATH | O_CLOEXEC);
+ ASSERT_LE(0, path_beneath.parent_fd);
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+ &path_beneath, 0));
+ EXPECT_EQ(0, close(path_beneath.parent_fd));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ inside_mq =
+ mq_open(inside_name, O_CREAT | O_EXCL | O_RDONLY, 0600, NULL);
+ ASSERT_LE(0, inside_mq);
+ reopened_mq = mq_open(inside_name, O_RDONLY);
+ EXPECT_LE(0, reopened_mq);
+ if (reopened_mq >= 0)
+ EXPECT_EQ(0, mq_close(reopened_mq));
+
+ reopened_mq = mq_open(outside_name, O_RDONLY);
+ EXPECT_EQ(-1, reopened_mq);
+ EXPECT_EQ(EPERM, errno);
+ if (reopened_mq >= 0)
+ EXPECT_EQ(0, mq_close(reopened_mq));
+
+ /* Filesystem permission does not override the outside queue's scope. */
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ EXPECT_EQ(-1, fd);
+ EXPECT_EQ(EPERM, errno);
+ if (fd >= 0)
+ EXPECT_EQ(0, close(fd));
+
+ ASSERT_LT(0,
+ snprintf(path, sizeof(path), "/dev/mqueue%s", inside_name));
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ EXPECT_LE(0, fd);
+ if (fd >= 0)
+ EXPECT_EQ(0, close(fd));
+
+ EXPECT_EQ(0, mq_close(inside_mq));
+ EXPECT_EQ(0, mq_close(outside_mq));
+ EXPECT_EQ(0, mq_unlink(inside_name));
+ EXPECT_EQ(0, mq_unlink(outside_name));
+}
+
+/* Both mq_open() and ordinary path-based opens must enforce queue scoping. */
+TEST(posix_mqueue_path_open)
+{
+ char outside_name[NAME_MAX], inside_name[NAME_MAX];
+ char outside_path[PATH_MAX], inside_path[PATH_MAX];
+ struct stat queue_stat, path_stat;
+ mqd_t outside_mq, inside_mq;
+ int fd;
+
+ drop_caps(_metadata);
+ set_mq_name(outside_name, sizeof(outside_name));
+ ASSERT_LT(0,
+ snprintf(inside_name, sizeof(inside_name),
+ "/selftests-landlock-mq-path-tid%d", sys_gettid()));
+ ASSERT_LT(0, snprintf(outside_path, sizeof(outside_path),
+ "/dev/mqueue%s", outside_name));
+ ASSERT_LT(0, snprintf(inside_path, sizeof(inside_path), "/dev/mqueue%s",
+ inside_name));
+ mq_unlink(outside_name);
+ mq_unlink(inside_name);
+ outside_mq =
+ mq_open(outside_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ if (outside_mq < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, outside_mq);
+ ASSERT_EQ(0, fstat(outside_mq, &queue_stat));
+ if (stat(outside_path, &path_stat) ||
+ queue_stat.st_dev != path_stat.st_dev ||
+ queue_stat.st_ino != path_stat.st_ino) {
+ EXPECT_EQ(0, mq_close(outside_mq));
+ EXPECT_EQ(0, mq_unlink(outside_name));
+ SKIP(return, "/dev/mqueue does not expose this IPC namespace");
+ }
+ fd = open(outside_path, O_RDWR | O_CLOEXEC);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, close(fd));
+
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ fd = open(outside_path, O_RDWR | O_CLOEXEC);
+ EXPECT_EQ(-1, fd);
+ EXPECT_EQ(EPERM, errno);
+ if (fd >= 0)
+ EXPECT_EQ(0, close(fd));
+
+ inside_mq = mq_open(inside_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
+ ASSERT_LE(0, inside_mq);
+ fd = open(inside_path, O_RDWR | O_CLOEXEC);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(0, close(fd));
+ EXPECT_EQ(0, mq_close(inside_mq));
+ EXPECT_EQ(0, mq_close(outside_mq));
+ EXPECT_EQ(0, mq_unlink(inside_name));
+ EXPECT_EQ(0, mq_unlink(outside_name));
+}
+
+FIXTURE(posix_mqueue_descriptor)
+{
+ char name[NAME_MAX];
+ mqd_t mq;
+};
+
+FIXTURE_VARIANT(posix_mqueue_descriptor)
+{
+ bool pass_fd;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(posix_mqueue_descriptor, inherited) {
+ /* clang-format on */
+ .pass_fd = false,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(posix_mqueue_descriptor, passed) {
+ /* clang-format on */
+ .pass_fd = true,
+};
+
+FIXTURE_SETUP(posix_mqueue_descriptor)
+{
+ const struct mq_attr attr = {
+ .mq_maxmsg = 1,
+ .mq_msgsize = 1,
+ };
+
+ drop_caps(_metadata);
+ set_mq_name(self->name, sizeof(self->name));
+ mq_unlink(self->name);
+ self->mq = mq_open(self->name, O_CREAT | O_EXCL | O_RDWR | O_NONBLOCK,
+ 0600, &attr);
+ if (self->mq < 0 && errno == ENOSYS)
+ SKIP(return, "POSIX message queues are not supported");
+ ASSERT_LE(0, self->mq);
+}
+
+FIXTURE_TEARDOWN(posix_mqueue_descriptor)
+{
+ EXPECT_EQ(0, mq_close(self->mq));
+ EXPECT_EQ(0, mq_unlink(self->name));
+}
+
+/* Existing descriptors remain usable even when reopening the queue is denied. */
+TEST_F(posix_mqueue_descriptor, use_outside_queue)
+{
+ int sockets[2], status;
+ char ready;
+ pid_t child;
+
+ ASSERT_EQ(0,
+ socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets));
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ mqd_t mq = self->mq, reopened;
+ char message;
+
+ EXPECT_EQ(0, close(sockets[0]));
+ /* Ensure the passed variant cannot use an inherited queue FD. */
+ if (variant->pass_fd)
+ ASSERT_EQ(0, mq_close(mq));
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
+ reopened = mq_open(self->name, O_RDWR);
+ EXPECT_EQ(-1, reopened);
+ EXPECT_EQ(EPERM, errno);
+ if (reopened >= 0)
+ EXPECT_EQ(0, mq_close(reopened));
+
+ /* The parent sends the FD only after the child enters its scope. */
+ ASSERT_EQ(1, write(sockets[1], ".", 1));
+ if (variant->pass_fd) {
+ mq = recv_fd(sockets[1]);
+ ASSERT_LE(0, mq);
+ }
+ ASSERT_EQ(0, mq_send(mq, "q", 1, 0));
+ ASSERT_EQ(1, mq_receive(mq, &message, sizeof(message), NULL));
+ EXPECT_EQ('q', message);
+ EXPECT_EQ(0, mq_close(mq));
+ EXPECT_EQ(0, close(sockets[1]));
+ _exit(_metadata->exit_code);
+ }
+ EXPECT_EQ(0, close(sockets[1]));
+ ASSERT_EQ(1, read(sockets[0], &ready, 1));
+ if (variant->pass_fd)
+ ASSERT_EQ(0, send_fd(sockets[0], self->mq));
+ EXPECT_EQ(0, close(sockets[0]));
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c
index b90f76ed0d9c..c530baa50948 100644
--- a/tools/testing/selftests/landlock/scoped_test.c
+++ b/tools/testing/selftests/landlock/scoped_test.c
@@ -12,7 +12,7 @@
#include "common.h"
-#define ACCESS_LAST LANDLOCK_SCOPE_SIGNAL
+#define ACCESS_LAST LANDLOCK_SCOPE_POSIX_MSG_QUEUE
TEST(ruleset_with_unknown_scope)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 4/4] samples/landlock: Add POSIX message queue scoping to sandboxer
2026-11-02 14:45 [PATCH v2 0/4] Landlock: Restrict POSIX message queue creation and access Oxana Kharitonova
` (2 preceding siblings ...)
2026-11-02 14:45 ` [PATCH v2 3/4] Landlock: Add POSIX message queue scoping Oxana Kharitonova
@ 2026-11-02 14:46 ` Oxana Kharitonova
2026-11-02 15:22 ` sashiko-bot
3 siblings, 1 reply; 11+ messages in thread
From: Oxana Kharitonova @ 2026-11-02 14:46 UTC (permalink / raw)
To: mic, brauner, viro, gnoack, utilityemal77
Cc: linux-security-module, linux-fsdevel, webprosto, landlock
Add "q" to LL_SCOPED to enable LANDLOCK_SCOPE_POSIX_MSG_QUEUE
and "posix_msg_queue" to LL_QUIET_ACCESS to quiet related audit
denials. Update the supported ABI version to 13 and disable the
new scope when running on older kernels.
For example, on a kernel supporting Landlock ABI 13:
LL_FS_RO="/" LL_FS_RW="" LL_SCOPED="q" \
./samples/landlock/sandboxer /bin/sh
The resulting shell and its children cannot open POSIX message
queues created outside their Landlock domain. They can create
queues and open queues created within that domain.
Signed-off-by: Oxana Kharitonova <webprosto@gmail.com>
---
samples/landlock/sandboxer.c | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 1362f08ff14b..e8e05c4d6f63 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -441,10 +441,12 @@ static bool check_ruleset_scope(const char *const env_var,
bool error = false;
bool abstract_scoping = false;
bool signal_scoping = false;
+ bool posix_msg_queue_scoping = false;
/* Scoping is not supported by Landlock ABI */
if (!(ruleset_attr->scoped &
- (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL)))
+ (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL |
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE)))
goto out_unset;
env_type_scope = getenv(env_var);
@@ -461,6 +463,9 @@ static bool check_ruleset_scope(const char *const env_var,
} else if (strcmp("s", ipc_scoping_name) == 0 &&
!signal_scoping) {
signal_scoping = true;
+ } else if (strcmp("q", ipc_scoping_name) == 0 &&
+ !posix_msg_queue_scoping) {
+ posix_msg_queue_scoping = true;
} else {
fprintf(stderr, "Unknown or duplicate scope \"%s\"\n",
ipc_scoping_name);
@@ -477,6 +482,8 @@ static bool check_ruleset_scope(const char *const env_var,
ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
if (!signal_scoping)
ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SIGNAL;
+ if (!posix_msg_queue_scoping)
+ ruleset_attr->scoped &= ~LANDLOCK_SCOPE_POSIX_MSG_QUEUE;
unsetenv(env_var);
return error;
@@ -555,6 +562,9 @@ static int add_quiet_access(const char *const env_var,
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
else if (strcmp(str_access, "signal") == 0)
ruleset_attr->quiet_scoped |= LANDLOCK_SCOPE_SIGNAL;
+ else if (strcmp(str_access, "posix_msg_queue") == 0)
+ ruleset_attr->quiet_scoped |=
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE;
else {
fprintf(stderr, "Unknown quiet access \"%s\"\n",
str_access);
@@ -570,7 +580,7 @@ static int add_quiet_access(const char *const env_var,
return 0;
}
-#define LANDLOCK_ABI_LAST 12
+#define LANDLOCK_ABI_LAST 13
#define XSTR(s) #s
#define STR(s) XSTR(s)
@@ -618,6 +628,7 @@ static const char help[] =
"* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"
" - \"a\" to restrict opening abstract unix sockets\n"
" - \"s\" to restrict sending signals\n"
+ " - \"q\" to restrict opening POSIX message queues\n"
"\n"
"A sandboxer should not log denied access requests to avoid spamming logs, "
"but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
@@ -634,6 +645,7 @@ static const char help[] =
" - \"udp_connect\" to quiet udp connect / send denials\n"
" - \"abstract_unix_socket\" to quiet abstract unix socket denials\n"
" - \"signal\" to quiet signal denials\n"
+ " - \"posix_msg_queue\" to quiet POSIX message queue denials\n"
"\n"
"Example:\n"
ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
@@ -643,7 +655,7 @@ static const char help[] =
ENV_UDP_CONNECT_SEND_NAME "=\"53\" "
ENV_NS_NAME "=\"user:uts:net\" "
ENV_CAP_NAME "=\"" CAP_VALUE_EXAMPLE "\" "
- ENV_SCOPED_NAME "=\"a:s\" "
+ ENV_SCOPED_NAME "=\"a:s:q\" "
"%1$s bash -i\n"
"\n"
"This sandboxer can use Landlock features up to ABI version "
@@ -667,7 +679,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
LANDLOCK_ACCESS_NET_BIND_UDP |
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
- LANDLOCK_SCOPE_SIGNAL,
+ LANDLOCK_SCOPE_SIGNAL |
+ LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
.quiet_access_fs = 0,
.quiet_access_net = 0,
.quiet_scoped = 0,
@@ -780,6 +793,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
ruleset_attr.handled_permissions &=
~(LANDLOCK_PERMISSION_NAMESPACE_USE |
LANDLOCK_PERMISSION_CAPABILITY_USE);
+ __attribute__((fallthrough));
+ case 12:
+ /* Removes POSIX message queue scoping for ABI < 13 */
+ ruleset_attr.scoped &= ~LANDLOCK_SCOPE_POSIX_MSG_QUEUE;
+
/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
fprintf(stderr,
"Hint: You should update the running kernel "
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v2 1/4] ipc: Move MQUEUE_MAGIC to a shared header
2026-11-02 14:45 ` [PATCH v2 1/4] ipc: Move MQUEUE_MAGIC to a shared header Oxana Kharitonova
@ 2026-11-02 14:51 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-02 14:51 UTC (permalink / raw)
Cc: linux-security-module
> Move MQUEUE_MAGIC from ipc/mqueue.c to include/linux/ipc_namespace.h so
> it can be shared by code outside of ipc/mqueue.c without duplicating
> the constant.
>
> Signed-off-by: Oxana Kharitonova <webprosto@gmail.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144708.920232-1-webprosto@gmail.com?part=1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation
2026-11-02 14:45 ` [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation Oxana Kharitonova
@ 2026-11-02 15:06 ` sashiko-bot
2026-11-03 2:42 ` kernel test robot
1 sibling, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-02 15:06 UTC (permalink / raw)
Cc: linux-security-module
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] landlock: incompatible hook_inode_create() signature omits mnt_idmap
- [High] landlock: user-triggerable WARN_ON_ONCE() in hook_inode_create()
- [High] landlock: hook_inode_create() breaks VFS create on mounted mqueuefs
- [Low] landlock: unnamed parameter in hook_inode_create() breaks gnu11 build
--
commit 8666d4a55fba496ff4bfd048b949b769a8d57ec6
Author: Oxana Kharitonova <webprosto@gmail.com>
Landlock: Enforce MAKE_REG for POSIX message queue creation
This commit adds an inode_create LSM hook in Landlock to enforce
LANDLOCK_ACCESS_FS_MAKE_REG when creating POSIX message queues before the
queue is created in the mqueue filesystem. It also documents the behavior and
adds selftests for POSIX message queue creation and opening.
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 2ab67bcad1983..d5dcb19d5eb19 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
[ ... ]
> @@ -1676,6 +1677,35 @@ static int hook_path_truncate(const struct path *const path)
> return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
> }
>
> +/*
> + * Unlike regular file creation, mq_open(2) calls vfs_mkobj() directly and
> + * therefore does not trigger the path_mknod hook. Use the mqueuefs mount
> + * associated with the current IPC namespace to recover the parent path.
> + */
> +static int hook_inode_create(struct inode *const dir,
> + struct dentry *const dentry, const umode_t)
[Severity: High]
Does hook_inode_create() in security/landlock/fs.c have the wrong function
signature for the inode_create LSM hook?
In include/linux/lsm_hook_defs.h, inode_create is declared with four
parameters, starting with const struct mnt_idmap *idmap:
LSM_HOOK(int, 0, inode_create, const struct mnt_idmap *idmap,
struct inode *dir, struct dentry *dentry, umode_t mode)
Because hook_inode_create() omits the leading idmap parameter, registering it
via LSM_HOOK_INIT(inode_create, hook_inode_create) in landlock_hooks fails to
build with -Werror=incompatible-pointer-types when CONFIG_SECURITY_LANDLOCK is
enabled (and would cause the mnt_idmap pointer to be passed as dir at
runtime).
[Severity: Low]
Also in hook_inode_create() in security/landlock/fs.c, the trailing
const umode_t parameter omits the parameter name.
Because the kernel builds with -std=gnu11 and supports GCC 8.1 and later,
while unnamed parameters in function definitions are a C23 feature only
supported starting in GCC 11, won't this fail to compile on GCC 8 through 10
with a "parameter name omitted" error?
> +{
> + struct ipc_namespace *ipc_ns;
> + struct vfsmount *mnt;
> + struct path parent;
> +
> + if (!landlock_is_posix_mqueue_fs(dir))
> + return 0;
> +
> + if (!current->nsproxy)
> + return 0;
> + ipc_ns = current->nsproxy->ipc_ns;
> + if (!ipc_ns)
> + return 0;
> + mnt = ipc_ns->mq_mnt;
> + if (WARN_ON_ONCE(!mnt || mnt->mnt_sb != dir->i_sb))
> + return 0;
[Severity: High]
Can an unprivileged user trigger this WARN_ON_ONCE() in hook_inode_create()
(and bypass the check) by creating a file on a mounted mqueue filesystem from
another IPC namespace?
Although mq_open(2) calls vfs_mkobj(), mqueue_dir_inode_operations in
ipc/mqueue.c also defines .create = mqueue_create:
static const struct inode_operations mqueue_dir_inode_operations = {
.lookup = simple_lookup,
.create = mqueue_create,
.unlink = mqueue_unlink,
};
This means VFS creation paths on a mounted mqueue filesystem also reach
security_inode_create() -> hook_inode_create():
openat(O_CREAT) -> do_filp_open() -> path_openat() -> lookup_open()
-> may_o_create() -> security_inode_create()
mknodat(S_IFREG) -> filename_mknodat() -> vfs_create()
-> security_inode_create()
If a process unshares into a new IPC namespace (for example via
unshare(CLONE_NEWUSER | CLONE_NEWIPC)) while retaining access to a mounted
mqueue filesystem from a previous IPC namespace (such as /dev/mqueue or an
open directory fd), dir->i_sb belongs to the mount's IPC namespace whereas
current->nsproxy->ipc_ns->mq_mnt->mnt_sb belongs to the new IPC namespace.
Calling open() with O_CREAT or mknod() with S_IFREG on that mount will then
cause mnt->mnt_sb != dir->i_sb and fire WARN_ON_ONCE().
> +
> + parent.mnt = mnt;
> + parent.dentry = dentry->d_parent;
> + return current_check_access_path(&parent, LANDLOCK_ACCESS_FS_MAKE_REG);
> +}
[Severity: High]
When a Landlock-restricted task creates a queue via open(O_CREAT) or
mknod(S_IFREG) on a mounted mqueue filesystem (such as /dev/mqueue), does
constructing parent with ipc_ns->mq_mnt in hook_inode_create() break rules
attached to ancestor directories like /dev or /?
During open(O_CREAT) or mknod(S_IFREG) on /dev/mqueue/q, the VFS first checks
security_path_mknod() with the real user mount path, which walks up across the
/dev/mqueue mountpoint to /dev and / via follow_up() in
is_access_to_paths_allowed():
jump_up:
if (walker_path.dentry == walker_path.mnt->mnt_root) {
if (follow_up(&walker_path)) {
/* Ignores hidden mount points. */
goto jump_up;
} else {
break;
}
}
However, may_o_create() and vfs_create() then call security_inode_create() ->
hook_inode_create(), which replaces the mount with the internal kernel mount
ipc_ns->mq_mnt. Because ipc_ns->mq_mnt is unattached, follow_up() returns
false at mnt_root and stops the walk before reaching /dev or /, causing
current_check_access_path() to deny the creation with -EACCES unless a rule
was added directly on /dev/mqueue itself.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144708.920232-1-webprosto@gmail.com?part=2
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 3/4] Landlock: Add POSIX message queue scoping
2026-11-02 14:45 ` [PATCH v2 3/4] Landlock: Add POSIX message queue scoping Oxana Kharitonova
@ 2026-11-02 15:09 ` sashiko-bot
2026-11-03 4:55 ` kernel test robot
1 sibling, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-02 15:09 UTC (permalink / raw)
Cc: linux-security-module
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] landlock: premature creator domain clearing in hook_inode_unlink()
- [Medium] landlock: ignored allocation failure in hook_d_instantiate() leaves queue untagged
- [Medium] selftests/landlock: missing ENOSYS skip checks when CONFIG_POSIX_MQUEUE=n
- [Low] landlock: build failure in landlock_check_posix_mqueue_open() without tracepoints
- [Low] Documentation: landlock: section title underline too short in landlock.rst
--
commit 672099a186033eeb6307163bb1baca6370e0b662
Author: Oxana Kharitonova <webprosto@gmail.com>
Landlock: Add POSIX message queue scoping
This commit introduces Posix message queue scoping in Landlock (ABI 13) to
restrict opening queues to those created within the scoping domain or its
descendants. It also adds audit logging, a denial trace event, documentation,
and selftests.
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index 1fb91884cb8af..fa65fa4aad02e 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
[ ... ]
> @@ -1075,6 +1121,14 @@ fd-reference acquisition (:manpage:`open_tree(2)`, :manpage:`fsmount(2)`) with
> the new ``LANDLOCK_PERMISSION_NAMESPACE_USE`` permission flag and
> ``LANDLOCK_RULE_NAMESPACE`` rule type.
>
> +POSIX message queue scoping (ABI < 13)
> +-------------------------------------
[Severity: Low]
This isn't a bug, but the hyphen underline here is 37 characters while the
heading above it is 38 characters. Should an extra hyphen be added so htmldocs
builds do not emit a title underline warning?
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index d5dcb19d5eb19..e9c52332dfcde 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -53,6 +53,7 @@
> #include "object.h"
> #include "ruleset.h"
> #include "setup.h"
> +#include "task.h"
>
> #include <trace/events/landlock.h>
>
> @@ -1374,6 +1375,80 @@ static void hook_inode_free_security_rcu(void *inode_security)
> WARN_ON_ONCE(inode_sec->object);
> }
>
> +/*
> + * Tag a newly created POSIX message queue with its creator's domain.
> + *
> + * This is the earliest reachable point with both the creator's context and a
> + * fully initialized inode.
> + */
> +static void hook_d_instantiate(struct dentry *const dentry,
> + struct inode *const inode)
> +{
> + struct landlock_domain *dom;
> + struct landlock_object *object;
> +
> + if (!landlock_is_posix_mqueue_inode(inode))
> + return;
> +
> + dom = landlock_get_current_domain();
> + if (!dom)
> + return;
> +
> + object = get_inode_object(inode);
> + if (IS_ERR(object))
> + return;
[Severity: Medium]
What happens if get_inode_object(inode) fails to allocate memory in
hook_d_instantiate() when a task in a scoped domain creates a queue via
mq_open()?
Because security_d_instantiate() is a void hook called from:
mqueue_create_attr()
d_make_persistent()
security_d_instantiate()
hook_d_instantiate()
the allocation failure is ignored and the new queue is linked into mqueuefs
with no creator domain attached. Immediately afterwards, mqueue_file_open()
calls dentry_open() -> hook_file_open() -> landlock_check_posix_mqueue_open(),
which sees creator_domain == NULL, treats the queue as created outside any
Landlock domain, and denies the creator's own mq_open() with EPERM while
leaving the untagged queue behind in the namespace.
Could the object allocation or tagging be performed in a fallible creation
hook (or preallocated before d_instantiate) so that allocation failure aborts
queue creation cleanly instead of leaving an untagged queue?
> +
> + spin_lock(&object->lock);
> + if (WARN_ON_ONCE(object->domain)) {
> + spin_unlock(&object->lock);
> + landlock_put_object(object);
> + return;
> + }
> +
> + landlock_get_domain(dom);
> + object->domain = dom;
> + spin_unlock(&object->lock);
> +}
> +
> +/*
> + * Drop the reference retained by hook_d_instantiate() for a POSIX message
> + * queue once its sole name is unlinked.
> + */
> +static int hook_inode_unlink(struct inode *const dir,
> + struct dentry *const dentry)
> +{
> + const struct inode *const inode = d_backing_inode(dentry);
> + struct landlock_domain *domain;
> + struct landlock_object *object;
> +
> + if (!landlock_is_posix_mqueue_inode(inode))
> + return 0;
> +
> + rcu_read_lock();
> + object = rcu_dereference(landlock_inode(inode)->object);
> + /* The temporary reference protects against concurrent object release. */
> + if (!object || !refcount_inc_not_zero(&object->usage)) {
> + rcu_read_unlock();
> + return 0;
> + }
> + rcu_read_unlock();
> +
> + spin_lock(&object->lock);
> + domain = object->domain;
> + object->domain = NULL;
> + spin_unlock(&object->lock);
> +
> + /* Drop the temporary reference acquired by refcount_inc_not_zero(). */
> + landlock_put_object(object);
> + if (domain) {
> + landlock_put_domain_deferred(domain);
> + /* Drops the reference retained by hook_d_instantiate(). */
> + landlock_put_object(object);
> + }
> +
> + return 0;
> +}
[Severity: High]
Can clearing object->domain inside hook_inode_unlink() strip the creator
domain from a queue that remains linked or open?
In fs/namei.c:vfs_unlink(), security_inode_unlink() is a pre-unlink
permission check:
vfs_unlink()
security_inode_unlink(dir, dentry)
try_break_deleg(dir, ...)
try_break_deleg(target, ...)
dir->i_op->unlink(dir, dentry)
If Landlock runs before another stacked security module (such as SELinux,
AppArmor, Smack, or BPF LSM in the default lsm order) and that subsequent
module denies the unlink, or if try_break_deleg() fails, hook_inode_unlink()
has already cleared object->domain = NULL and dropped the retained references,
even though vfs_unlink() aborts and the queue remains linked in mqueuefs.
Similarly, even when vfs_unlink() succeeds, the queue inode stays alive while
open file descriptors exist and can be reopened by tasks in the creator's
domain via /proc/<pid>/fd/<n>.
Because landlock_check_posix_mqueue_open() treats creator_domain == NULL as
an unsandboxed creator, any subsequent open of that queue by the creator's
own scoped domain (or a scoped ancestor domain) will be denied with EPERM.
Should the creator domain stay attached for the lifetime of the inode rather
than being cleared in hook_inode_unlink()?
> diff --git a/security/landlock/task.c b/security/landlock/task.c
> index d9eae86fc5529..0daa7edff2d7e 100644
> --- a/security/landlock/task.c
> +++ b/security/landlock/task.c
> @@ -522,6 +522,72 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
> return -EPERM;
> }
>
> +static const struct access_masks posix_mqueue_scope = {
> + .scope = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
> +};
> +
> +/**
> + * landlock_check_posix_mqueue_open - Deny opening a POSIX message queue
> + * created by a task from a different (non-ancestor) domain
> + *
> + * @file: The mqueuefs file being opened.
> + *
> + * Return: -EPERM if the open must be denied, 0 otherwise.
> + */
> +int landlock_check_posix_mqueue_open(struct file *const file)
> +{
[ ... ]
> + landlock_log_denial(subject, &(struct landlock_request) {
> + .type = LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE,
> + .other_domain_id = creator_domain ? creator_domain->hierarchy->id : 0,
> + .audit = {
> + .type = LSM_AUDIT_DATA_FILE,
> + .u.file = file,
> + },
> + .layer_plus_one = handle_layer + 1,
> + });
[Severity: Low]
Does security/landlock/task.c build when tracepoints are disabled?
In security/landlock/log.h, other_domain_id in struct landlock_request is
guarded by:
#ifdef CONFIG_TRACEPOINTS
union {
u64 other_domain_id;
...
and in security/landlock/domain.h, creator_domain->hierarchy->id is guarded by
#ifdef CONFIG_SECURITY_LANDLOCK_LOG. Because landlock_log_denial() is a static
inline function when logging is disabled, the compound literal is still
type-checked by the compiler. Should the .other_domain_id initializer here be
wrapped in #ifdef CONFIG_TRACEPOINTS like in hook_unix_stream_connect() and
hook_unix_may_send()?
> diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
> index 4bea8c880a4d3..a15c1e5d277f3 100644
> --- a/tools/testing/selftests/landlock/audit_test.c
> +++ b/tools/testing/selftests/landlock/audit_test.c
[ ... ]
> +/* Checks audit logging and quieting of a denied POSIX message queue open. */
> +TEST_F(posix_mqueue_audit, open)
> +{
> + const struct landlock_ruleset_attr ruleset_attr = {
> + .scoped = LANDLOCK_SCOPE_POSIX_MSG_QUEUE,
> + .quiet_scoped = variant->quiet_scoped,
> + };
> + char mq_name[NAME_MAX], log_match[PATH_MAX];
> + int pipe_parent[2], pipe_child[2], ruleset_fd, status;
> + struct audit_records records;
> + char buf;
> + pid_t child;
> + mqd_t mq;
> +
> + ASSERT_LT(0,
> + snprintf(mq_name, sizeof(mq_name),
> + "/selftests-landlock-audit-mq-tid%d", sys_gettid()));
> + mq_unlink(mq_name);
> + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> +
> + child = fork();
> + ASSERT_LE(0, child);
> + if (child == 0) {
> + EXPECT_EQ(0, close(pipe_parent[1]));
> + EXPECT_EQ(0, close(pipe_child[0]));
> +
> + mq = mq_open(mq_name, O_CREAT | O_EXCL | O_RDWR, 0600, NULL);
> + ASSERT_LE(0, mq);
[ ... ]
> diff --git a/tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c b/tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c
> new file mode 100644
> index 0000000000000..4475746081bc3
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c
[ ... ]
> + if (variant->domain_parent)
> + create_scoped_domain(_metadata, LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
> +
> + mq = mq_open(self->mq_name, O_CREAT | O_RDWR, 0600, NULL);
> + ASSERT_LE(0, mq);
[Severity: Medium]
In tools/testing/selftests/landlock/scoped_posix_msg_queue_test.c, the later
tests check whether mq_open() failed with errno == ENOSYS and call SKIP() when
Posix message queue support is not enabled in the kernel, but the first eight
tests (open_parent_queue, open_child_queue, open_existing_queue_with_create,
unlink_and_recreate_in_sibling_domain, open_queue_after_creator_exits,
open_queue_from_child_after_creator_exits, posix_mqueue_ipc_namespace_lifetime,
and create_and_open_same_domain) along with TEST_F(posix_mqueue_audit, open)
in tools/testing/selftests/landlock/audit_test.c assert that mq_open()
succeeds directly.
Should those tests also skip when mq_open() fails with ENOSYS so they do not
fail on kernels built without Posix message queue support?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144708.920232-1-webprosto@gmail.com?part=3
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 4/4] samples/landlock: Add POSIX message queue scoping to sandboxer
2026-11-02 14:46 ` [PATCH v2 4/4] samples/landlock: Add POSIX message queue scoping to sandboxer Oxana Kharitonova
@ 2026-11-02 15:22 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-02 15:22 UTC (permalink / raw)
Cc: linux-security-module
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] landlock: user-triggerable WARN_ON_ONCE and wrong mount in hook_inode_create()
- [Medium] samples/landlock: commit message example with LL_FS_RW="" denies mq_open()
- [Medium] landlock: hook_inode_unlink() clears object->domain before unlink succeeds
- [Low] landlock: omitted parameter name in hook_inode_create() breaks GCC < 11 build
--
commit fb37ae1137756d3c9de1fe75208b0f3f51d1b11c
Author: Oxana Kharitonova <webprosto@gmail.com>
samples/landlock: Add POSIX message queue scoping to sandboxer
This series adds LANDLOCK_ACCESS_FS_MAKE_REG enforcement for POSIX message
queue creation, introduces LANDLOCK_SCOPE_POSIX_MSG_QUEUE in ABI 13, and
updates the Landlock sample sandboxer to support the new scope and audit
quieting option.
> samples/landlock: Add POSIX message queue scoping to sandboxer
>
> Add "q" to LL_SCOPED to enable LANDLOCK_SCOPE_POSIX_MSG_QUEUE
> and "posix_msg_queue" to LL_QUIET_ACCESS to quiet related audit
> denials. Update the supported ABI version to 13 and disable the
> new scope when running on older kernels.
>
> For example, on a kernel supporting Landlock ABI 13:
>
> LL_FS_RO="/" LL_FS_RW="" LL_SCOPED="q" \
> ./samples/landlock/sandboxer /bin/sh
>
> The resulting shell and its children cannot open POSIX message
> queues created outside their Landlock domain. They can create
> queues and open queues created within that domain.
>
> Signed-off-by: Oxana Kharitonova <webprosto@gmail.com>
[Severity: Medium]
Does the example command in the commit message actually allow the shell and
its children to create or open POSIX message queues?
In samples/landlock/sandboxer.c:main(), ruleset_attr.handled_access_fs is
unconditionally initialized to access_fs_rw:
__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = access_fs_rw,
When LL_FS_RW="" is passed, populate_ruleset_fs() returns early without adding
any read-write rules:
if (num_paths == 1 && path_list[0][0] == '\0') {
/*
* Allows to not use all possible restrictions (e.g. use
* LL_FS_RO without LL_FS_RW).
*/
ret = 0;
goto out_free_name;
}
This leaves LANDLOCK_ACCESS_FS_MAKE_REG and LANDLOCK_ACCESS_FS_WRITE_FILE
handled and denied everywhere. Creating a queue via mq_open() with O_CREAT
invokes hook_inode_create(), which checks LANDLOCK_ACCESS_FS_MAKE_REG on
ipc_ns->mq_mnt and denies creation with EACCES.
In addition, do_mq_open() and hook_inode_create() operate on
current->nsproxy->ipc_ns->mq_mnt, which is an internal SB_KERNMOUNT mount
where path traversal in is_access_to_paths_allowed() stops at mq_mnt->mnt_root
(follow_up() returns false) and never reaches "/". Because of that,
LL_FS_RO="/" does not grant LANDLOCK_ACCESS_FS_READ_FILE to mq_open() either;
wouldn't the mqueue filesystem for the current IPC namespace (such as
/dev/mqueue) need to be mounted and included in LL_FS_RW for this example to
work as described?
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 2ab67bcad1983..e9c52332dfcde 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
[ ... ]
> +/*
> + * Drop the reference retained by hook_d_instantiate() for a POSIX message
> + * queue once its sole name is unlinked.
> + */
> +static int hook_inode_unlink(struct inode *const dir,
> + struct dentry *const dentry)
> +{
> + const struct inode *const inode = d_backing_inode(dentry);
> + struct landlock_domain *domain;
> + struct landlock_object *object;
> +
> + if (!landlock_is_posix_mqueue_inode(inode))
> + return 0;
> +
> + rcu_read_lock();
> + object = rcu_dereference(landlock_inode(inode)->object);
> + /* The temporary reference protects against concurrent object release. */
> + if (!object || !refcount_inc_not_zero(&object->usage)) {
> + rcu_read_unlock();
> + return 0;
> + }
> + rcu_read_unlock();
> +
> + spin_lock(&object->lock);
> + domain = object->domain;
> + object->domain = NULL;
> + spin_unlock(&object->lock);
> +
> + /* Drop the temporary reference acquired by refcount_inc_not_zero(). */
> + landlock_put_object(object);
> + if (domain) {
> + landlock_put_domain_deferred(domain);
> + /* Drops the reference retained by hook_d_instantiate(). */
> + landlock_put_object(object);
> + }
> +
> + return 0;
> +}
[Severity: Medium]
Can clearing object->domain in hook_inode_unlink() strip the creator domain
from a queue before the unlink actually succeeds, or while the queue inode is
still alive?
In vfs_unlink(), security_inode_unlink(dir, dentry) is a pre-operation
permission hook invoked before try_break_deleg() and dir->i_op->unlink().
Because Landlock is ordered before SELinux, Smack, AppArmor, and BPF LSM in
CONFIG_LSM, hook_inode_unlink() runs first, sets object->domain = NULL, and
drops the domain and object references retained by hook_d_instantiate().
If a subsequent stacked LSM hook or delegation break fails, vfs_unlink()
aborts and the queue remains linked in mqueuefs with object->domain == NULL.
Any later mq_open() or open() from the creator's domain with
LANDLOCK_SCOPE_POSIX_MSG_QUEUE enabled will see creator_domain == NULL in
landlock_check_posix_mqueue_open() and fail with -EPERM. The same -EPERM also
occurs if an unlinked queue with an open file descriptor is reopened by the
creator's domain via /proc/self/fd/<fd>.
Should the domain reference instead be released when the underlying inode is
evicted or released rather than inside the pre-unlink permission hook?
[ ... ]
> +/*
> + * Unlike regular file creation, mq_open(2) calls vfs_mkobj() directly and
> + * therefore does not trigger the path_mknod hook. Use the mqueuefs mount
> + * associated with the current IPC namespace to recover the parent path.
> + */
> +static int hook_inode_create(struct inode *const dir,
> + struct dentry *const dentry, const umode_t)
[Severity: Low]
Does omitting the parameter name for const umode_t in hook_inode_create()
break builds with older supported GCC versions?
The kernel compiles C sources with -std=gnu11 and supports GCC >= 8.1.0,
whereas unnamed parameters in function definitions are a C23 feature only
supported by GCC 11 and later (older versions fail with "error: parameter name
omitted").
> +{
> + struct ipc_namespace *ipc_ns;
> + struct vfsmount *mnt;
> + struct path parent;
> +
> + if (!landlock_is_posix_mqueue_fs(dir))
> + return 0;
> +
> + if (!current->nsproxy)
> + return 0;
> + ipc_ns = current->nsproxy->ipc_ns;
> + if (!ipc_ns)
> + return 0;
> + mnt = ipc_ns->mq_mnt;
> + if (WARN_ON_ONCE(!mnt || mnt->mnt_sb != dir->i_sb))
> + return 0;
> +
> + parent.mnt = mnt;
> + parent.dentry = dentry->d_parent;
> + return current_check_access_path(&parent, LANDLOCK_ACCESS_FS_MAKE_REG);
> +}
[Severity: High]
Can hook_inode_create() also be reached via VFS path-based creation on a
mounted mqueue filesystem, triggering the WARN_ON_ONCE() from unprivileged
userspace or checking the wrong mount?
In ipc/mqueue.c, mqueue_dir_inode_operations sets .create = mqueue_create, so
calling open(..., O_CREAT, ...) or mknod(..., S_IFREG, ...) on a mounted
mqueue filesystem (such as /dev/mqueue, which has mode 01777) calls
security_path_mknod() followed by security_inode_create() ->
hook_inode_create().
This appears to cause two regressions:
1. A mounted mqueue superblock belongs to the ipc_namespace active at mount
time (sb->s_fs_info), which need not match current->nsproxy->ipc_ns. An
unprivileged user can call unshare(CLONE_NEWUSER | CLONE_NEWIPC) while keeping
access to the previous IPC namespace's /dev/mqueue mount and call
open("/dev/mqueue/foo", O_CREAT, 0600). In hook_inode_create(),
current->nsproxy->ipc_ns->mq_mnt->mnt_sb != dir->i_sb, triggering the
WARN_ON_ONCE() and returning 0 without enforcing LANDLOCK_ACCESS_FS_MAKE_REG.
2. Even within the same IPC namespace, if a sandboxed process creates a queue
via open("/dev/mqueue/foo", O_CREAT, 0600) or mknod() and its ruleset grants
LANDLOCK_ACCESS_FS_MAKE_REG on an ancestor directory such as "/" or "/dev"
rather than directly on the /dev/mqueue root inode, hook_path_mknod() succeeds
by walking up across the mountpoint via follow_up(). However,
hook_inode_create() then checks parent.mnt = ipc_ns->mq_mnt (an internal
SB_KERNMOUNT mount where follow_up() stops at the root) and denies the
creation with -EACCES.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144708.920232-1-webprosto@gmail.com?part=4
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation
2026-11-02 14:45 ` [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation Oxana Kharitonova
2026-11-02 15:06 ` sashiko-bot
@ 2026-11-03 2:42 ` kernel test robot
1 sibling, 0 replies; 11+ messages in thread
From: kernel test robot @ 2026-11-03 2:42 UTC (permalink / raw)
To: Oxana Kharitonova, mic, brauner, viro, gnoack, utilityemal77
Cc: llvm, oe-kbuild-all, linux-security-module, linux-fsdevel,
webprosto, landlock
Hi Oxana,
kernel test robot noticed the following build warnings:
[auto build test WARNING on shuah-kselftest/next]
[also build test WARNING on linus/master next-20261030]
[cannot apply to shuah-kselftest/fixes trace/for-next brauner-vfs/vfs.all v7.3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Oxana-Kharitonova/ipc-Move-MQUEUE_MAGIC-to-a-shared-header/20261102-144557
base: https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git next
patch link: https://lore.kernel.org/r/20261102144708.920232-3-webprosto%40gmail.com
patch subject: [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation
config: s390-defconfig (https://download.01.org/0day-ci/archive/20261103/202611031036.3r5EL9TN-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261103/202611031036.3r5EL9TN-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611031036.3r5EL9TN-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> security/landlock/fs.c:1686:51: warning: omitting the parameter name in a function definition is a C23 extension [-Wc23-extensions]
1686 | struct dentry *const dentry, const umode_t)
| ^
security/landlock/fs.c:2117:30: error: incompatible function pointer types initializing 'int (*)(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t)' (aka 'int (*)(const struct mnt_idmap *, struct inode *, struct dentry *, unsigned short)') with an expression of type 'int (struct inode *const, struct dentry *const, const umode_t)' (aka 'int (struct inode *const, struct dentry *const, const unsigned short)') [-Wincompatible-function-pointer-types]
2117 | LSM_HOOK_INIT(inode_create, hook_inode_create),
| ^~~~~~~~~~~~~~~~~
include/linux/lsm_hooks.h:141:21: note: expanded from macro 'LSM_HOOK_INIT'
141 | .hook = { .NAME = HOOK } \
| ^~~~
1 warning and 1 error generated.
vim +1686 security/landlock/fs.c
1679
1680 /*
1681 * Unlike regular file creation, mq_open(2) calls vfs_mkobj() directly and
1682 * therefore does not trigger the path_mknod hook. Use the mqueuefs mount
1683 * associated with the current IPC namespace to recover the parent path.
1684 */
1685 static int hook_inode_create(struct inode *const dir,
> 1686 struct dentry *const dentry, const umode_t)
1687 {
1688 struct ipc_namespace *ipc_ns;
1689 struct vfsmount *mnt;
1690 struct path parent;
1691
1692 if (!landlock_is_posix_mqueue_fs(dir))
1693 return 0;
1694
1695 if (!current->nsproxy)
1696 return 0;
1697 ipc_ns = current->nsproxy->ipc_ns;
1698 if (!ipc_ns)
1699 return 0;
1700 mnt = ipc_ns->mq_mnt;
1701 if (WARN_ON_ONCE(!mnt || mnt->mnt_sb != dir->i_sb))
1702 return 0;
1703
1704 parent.mnt = mnt;
1705 parent.dentry = dentry->d_parent;
1706 return current_check_access_path(&parent, LANDLOCK_ACCESS_FS_MAKE_REG);
1707 }
1708
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 3/4] Landlock: Add POSIX message queue scoping
2026-11-02 14:45 ` [PATCH v2 3/4] Landlock: Add POSIX message queue scoping Oxana Kharitonova
2026-11-02 15:09 ` sashiko-bot
@ 2026-11-03 4:55 ` kernel test robot
1 sibling, 0 replies; 11+ messages in thread
From: kernel test robot @ 2026-11-03 4:55 UTC (permalink / raw)
To: Oxana Kharitonova, mic, brauner, viro, gnoack, utilityemal77
Cc: oe-kbuild-all, linux-security-module, linux-fsdevel, webprosto,
landlock
Hi Oxana,
kernel test robot noticed the following build errors:
[auto build test ERROR on shuah-kselftest/next]
[also build test ERROR on linus/master next-20261030]
[cannot apply to shuah-kselftest/fixes trace/for-next brauner-vfs/vfs.all v7.3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Oxana-Kharitonova/ipc-Move-MQUEUE_MAGIC-to-a-shared-header/20261102-144557
base: https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git next
patch link: https://lore.kernel.org/r/20261102144708.920232-4-webprosto%40gmail.com
patch subject: [PATCH v2 3/4] Landlock: Add POSIX message queue scoping
config: nios2-allmodconfig (https://download.01.org/0day-ci/archive/20261103/202611031209.bINuNiz5-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261103/202611031209.bINuNiz5-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611031209.bINuNiz5-lkp@intel.com/
All errors (new ones prefixed by >>):
security/landlock/task.c: In function 'landlock_check_posix_mqueue_open':
>> security/landlock/task.c:579:18: error: 'struct landlock_request' has no member named 'other_domain_id'
579 | .other_domain_id = creator_domain ? creator_domain->hierarchy->id : 0,
| ^~~~~~~~~~~~~~~
vim +579 security/landlock/task.c
528
529 /**
530 * landlock_check_posix_mqueue_open - Deny opening a POSIX message queue
531 * created by a task from a different (non-ancestor) domain
532 *
533 * @file: The mqueuefs file being opened.
534 *
535 * Return: -EPERM if the open must be denied, 0 otherwise.
536 */
537 int landlock_check_posix_mqueue_open(struct file *const file)
538 {
539 const struct inode *const inode = file_inode(file);
540 const struct landlock_cred_security *subject;
541 struct landlock_object *object;
542 struct landlock_domain *creator_domain = NULL;
543 bool is_scoped;
544 size_t handle_layer;
545
546 if (!landlock_is_posix_mqueue_inode(inode))
547 return 0;
548
549 subject = landlock_get_applicable_subject(
550 file->f_cred, posix_mqueue_scope, &handle_layer);
551 if (!subject)
552 return 0;
553
554 rcu_read_lock();
555 object = rcu_dereference(landlock_inode(inode)->object);
556 /* The temporary reference protects against concurrent object release. */
557 if (object && !refcount_inc_not_zero(&object->usage))
558 object = NULL;
559 rcu_read_unlock();
560
561 if (object) {
562 spin_lock(&object->lock);
563 creator_domain = object->domain;
564 landlock_get_domain(creator_domain);
565 spin_unlock(&object->lock);
566 }
567
568 /* Untagged queues were created outside any Landlock domain. */
569 is_scoped = domain_is_scoped(subject->domain, creator_domain,
570 LANDLOCK_SCOPE_POSIX_MSG_QUEUE);
571 if (!is_scoped) {
572 landlock_put_domain_deferred(creator_domain);
573 landlock_put_object(object);
574 return 0;
575 }
576
577 landlock_log_denial(subject, &(struct landlock_request) {
578 .type = LANDLOCK_REQUEST_SCOPE_POSIX_MSG_QUEUE,
> 579 .other_domain_id = creator_domain ? creator_domain->hierarchy->id : 0,
580 .audit = {
581 .type = LSM_AUDIT_DATA_FILE,
582 .u.file = file,
583 },
584 .layer_plus_one = handle_layer + 1,
585 });
586 landlock_put_domain_deferred(creator_domain);
587 landlock_put_object(object);
588 return -EPERM;
589 }
590
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-03 4:56 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 14:45 [PATCH v2 0/4] Landlock: Restrict POSIX message queue creation and access Oxana Kharitonova
2026-11-02 14:45 ` [PATCH v2 1/4] ipc: Move MQUEUE_MAGIC to a shared header Oxana Kharitonova
2026-11-02 14:51 ` sashiko-bot
2026-11-02 14:45 ` [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation Oxana Kharitonova
2026-11-02 15:06 ` sashiko-bot
2026-11-03 2:42 ` kernel test robot
2026-11-02 14:45 ` [PATCH v2 3/4] Landlock: Add POSIX message queue scoping Oxana Kharitonova
2026-11-02 15:09 ` sashiko-bot
2026-11-03 4:55 ` kernel test robot
2026-11-02 14:46 ` [PATCH v2 4/4] samples/landlock: Add POSIX message queue scoping to sandboxer Oxana Kharitonova
2026-11-02 15:22 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.