Linux filesystem development
 help / color / mirror / Atom feed
* [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; 8+ 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] 8+ 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:45 ` [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation Oxana Kharitonova
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 8+ 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] 8+ 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-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, 1 reply; 8+ 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] 8+ 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-03  4:55   ` kernel test robot
  2026-11-03  5:22   ` 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; 8+ 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] 8+ 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
  3 siblings, 0 replies; 8+ 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] 8+ 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-03  2:42   ` kernel test robot
  0 siblings, 0 replies; 8+ 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] 8+ 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-03  4:55   ` kernel test robot
  2026-11-03  5:22   ` kernel test robot
  1 sibling, 0 replies; 8+ 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] 8+ 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-03  4:55   ` kernel test robot
@ 2026-11-03  5:22   ` kernel test robot
  1 sibling, 0 replies; 8+ messages in thread
From: kernel test robot @ 2026-11-03  5:22 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-randconfig-1000-20261103 (https://download.01.org/0day-ci/archive/20261103/202611031330.RRP6ch5h-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/202611031330.RRP6ch5h-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/202611031330.RRP6ch5h-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,
         |                  ^~~~~~~~~~~~~~~
>> security/landlock/task.c:579:78: error: 'struct landlock_hierarchy' has no member named '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] 8+ messages in thread

end of thread, other threads:[~2026-11-03  5:22 UTC | newest]

Thread overview: 8+ 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:45 ` [PATCH v2 2/4] Landlock: Enforce MAKE_REG for POSIX message queue creation Oxana Kharitonova
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-03  4:55   ` kernel test robot
2026-11-03  5:22   ` kernel test robot
2026-11-02 14:46 ` [PATCH v2 4/4] samples/landlock: Add POSIX message queue scoping to sandboxer Oxana Kharitonova

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox