AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/27 06:25 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "SECURITY_LANDLOCK",
    "NAMESPACES",
    "USER_NS",
    "UTS_NS",
    "IPC_NS",
    "PID_NS",
    "NET_NS",
    "TIME_NS",
    "CGROUPS",
    "AUDIT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "sys_landlock_create_ruleset",
    "sys_landlock_add_rule",
    "sys_landlock_restrict_self",
    "security_namespace_init",
    "security_namespace_install"
  ],
  "Reasoning": "The patch adds new Landlock LSM features to restrict capabilities and namespaces. It introduces new rule types (LANDLOCK_RULE_CAPABILITY, LANDLOCK_RULE_NAMESPACE) and new permission flags (LANDLOCK_PERM_CAPABILITY_USE, LANDLOCK_PERM_NAMESPACE_USE). It modifies the landlock_create_ruleset and landlock_add_rule syscalls to handle these new rules and permissions. It also adds new LSM hooks for capable, namespace_init, and namespace_install to enforce these restrictions. This is a functional change to the core kernel and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/07/27 06:25 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 646d02ca32d3ecd5b5730bdadc91cb05f6323826\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Jul 27 06:25:25 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/LSM/landlock.rst b/Documentation/admin-guide/LSM/landlock.rst\nindex 314052bbeb0a96..8d3b20a72f2085 100644\n--- a/Documentation/admin-guide/LSM/landlock.rst\n+++ b/Documentation/admin-guide/LSM/landlock.rst\n@@ -6,7 +6,7 @@ Landlock: system-wide management\n ================================\n \n :Author: Mickaël Salaün\n-:Date: June 2026\n+:Date: July 2026\n \n Landlock can leverage the audit framework to log events.\n \n@@ -63,14 +63,25 @@ AUDIT_LANDLOCK_ACCESS\n         - scope.abstract_unix_socket - Abstract UNIX socket connection denied\n         - scope.signal - Signal sending denied\n \n+    **perm.*** - Permission restrictions (ABI 11+):\n+        - perm.namespace_use - Namespace entry was denied (creation via\n+          :manpage:`unshare(2)` / :manpage:`clone(2)` or joining via\n+          :manpage:`setns(2)`);\n+          ``namespace_type`` indicates the type (hex CLONE_NEW* bitmask),\n+          ``namespace_id`` identifies the target namespace for\n+          :manpage:`setns(2)` operations\n+        - perm.capability_use - Capability use was denied;\n+          ``capability`` indicates the capability number\n+\n     Multiple blockers can appear in a single event (comma-separated) when\n     multiple access rights are missing. For example, creating a regular file\n     in a directory that lacks both ``make_reg`` and ``refer`` rights would show\n     ``blockers=fs.make_reg,fs.refer``.\n \n-    The object identification fields (path, dev, ino for filesystem; opid,\n-    ocomm for signals) depend on the type of access being blocked and provide\n-    context about what resource was involved in the denial.\n+    The object identification fields depend on the type of access being blocked:\n+    ``path``, ``dev``, ``ino`` for filesystem; ``opid``, ``ocomm`` for signals;\n+    ``namespace_type`` and ``namespace_id`` for namespace operations;\n+    ``capability`` for capability use.\n \n \n AUDIT_LANDLOCK_DOMAIN\ndiff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst\nindex c5186526e76f6c..18f14652c20cac 100644\n--- a/Documentation/security/landlock.rst\n+++ b/Documentation/security/landlock.rst\n@@ -7,7 +7,7 @@ Landlock LSM: kernel documentation\n ==================================\n \n :Author: Mickaël Salaün\n-:Date: March 2026\n+:Date: July 2026\n \n Landlock's goal is to create scoped access-control (i.e. sandboxing).  To\n harden a whole system, this feature should be available to any process,\n@@ -129,6 +129,165 @@ The reasoning is:\n   restrictions, because access within the same scope is already\n   allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.\n \n+Composability with user namespaces\n+----------------------------------\n+\n+Landlock domain-based scoping and the kernel's user-namespace-based capability\n+scoping enforce isolation over independent hierarchies.  Landlock checks domain\n+ancestry; the kernel's ``ns_capable()`` checks user namespace ancestry.  These\n+hierarchies are orthogonal: Landlock enforcement is deterministic with respect\n+to its own configuration, regardless of namespace or capability state, and vice\n+versa.  This orthogonality is a design invariant that must hold for all Landlock\n+access controls.\n+\n+Design philosophy\n+-----------------\n+\n+Landlock's goal is to restrict a sandboxed process's access to three kinds of\n+resources: data (files, sockets, pipes), other processes (signals, ptrace), and\n+kernel-internal resources whose use widens the kernel attack surface\n+(capabilities, namespace types).  Each access right or permission gates one or\n+more operations that grant such access; restricting the operations is how\n+Landlock restricts the underlying access.\n+\n+When designing a new access control, identify the protected resource kind\n+first (data, processes, or kernel-internal resources).  The operations to\n+restrict follow from the protected resource, by identifying which kernel code\n+paths grant access to the resource and at which place in the code the access to\n+the resource can be gated.  Do not design a permission around\n+\"restrict the unshare(2) syscall\" or similar mechanism-centric framings; design\n+it around \"restrict the process from acquiring access to namespace types\" (the\n+protected resource), letting the operation set follow.\n+\n+Ruleset restriction models\n+--------------------------\n+\n+Landlock provides three restriction models that differ in how rules identify the\n+resource being restricted.\n+\n+In general, the ``struct landlock_ruleset_attr`` specifies the operations to be\n+denied by default under the enforced policy.  The *rules* added to the ruleset\n+define the exceptions to these restrictions, allow-listing specific conditions\n+under which these operations are still permitted.\n+\n+Per-object access rights (``handled_access_*``)\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Per-object access rights control operations on a specific resource instance,\n+identified in the rule key by a value drawn from an open-ended space: a file\n+hierarchy referenced by ``parent_fd``, or a network port identified by its\n+16-bit number.\n+\n+Each ``handled_access_*`` field declares a set of access rights, operations\n+which are to be denied by default once the ruleset is enforced.\n+\n+The rule body declares which of the multiple distinct operations on that object\n+instance are allowed (open, read, write, truncate; bind, connect).\n+\n+Operations are grouped by object type in the respective ``handled_access_*``\n+field: a new operation on an existing type extends that field, and a new object\n+type gets its own ``handled_access_*`` field.\n+\n+Per-category permissions (``handled_perm``)\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Per-category permissions control the process's exercise of category members,\n+where the category is a small kernel-defined enumeration (a Linux capability\n+number ``CAP_*``, a namespace type ``CLONE_NEW*``).  Unlike per-object access\n+rights, which restrict specific operations on a single resource instance,\n+per-category permissions gate the prerequisite operation itself (exercising a\n+capability, acquiring access to a namespace), so gating it transitively covers a\n+broad set of downstream operations (e.g. denying ``CAP_SYS_ADMIN`` blocks all\n+admin operations such as mounts).\n+\n+These category members are the LSM-level access-control objects (the entities\n+the process is authorized against) even though they are enum values rather than\n+externally-instantiated kernel data structures.  Per-category permissions apply\n+where the controlled operation collapses to \"may the process use this category\n+member at all\" (use a capability; acquire access to a namespace), so the rule\n+body lists which category members the process may exercise.\n+\n+Each ``LANDLOCK_PERM_*`` flag maps to its own rule type and covers every kernel\n+path that exercises a member.  When a ruleset handles a permission, all uses of\n+category members are denied unless explicitly allowed by a rule.\n+\n+Logging of a denied member can be suppressed at the same granularity as the\n+restriction itself: per rule and per member.  A rule names the members whose\n+denial should not be audited; suppression only affects the audit record, never\n+the denial, and only applies when the layer owning the rule is the one that\n+denied the member.\n+\n+See Documentation/userspace-api/landlock.rst for the concrete syscall paths\n+covered by each permission.\n+\n+The category enum is owned by the corresponding kernel subsystem (capabilities,\n+namespaces, etc.).  Userspace policy authors query category member availability\n+via the relevant non-Landlock interfaces:\n+\n+* For capabilities: ``\u003clinux/capability.h\u003e``,\n+  ``/proc/sys/kernel/cap_last_cap``, ``prctl(PR_CAPBSET_READ)``.\n+* For namespaces: ``\u003clinux/sched.h\u003e``, ``/proc/$$/ns/*``,\n+  :manpage:`unshare(2)` runtime probe.\n+\n+The Landlock ABI version does not encode this availability; ABI versioning\n+describes which Landlock features (rule types, access rights, scopes,\n+permissions) the kernel implements, not which category members the kernel knows\n+about.\n+\n+Forward compatibility for new category members follows a simple rule set:\n+\n+* New members in future kernels are automatically denied: rules whitelist\n+  specific values, and a member not in any rule is denied.\n+* Kernel-side compatibility for split categories is handled by the owning\n+  subsystem (e.g., when ``CAP_BPF`` was split from ``CAP_SYS_ADMIN``, either\n+  capability became sufficient for the affected operations, so a rule allowing\n+  ``CAP_SYS_ADMIN`` continues to allow operations now gated by\n+  ``CAP_SYS_ADMIN || CAP_BPF``).\n+* Unknown values in the rule body are silently accepted rather than rejected.\n+  Rejecting them would tie Landlock policy semantics to the running kernel's\n+  category-member set: a rule built against future headers would fail to load\n+  on older kernels, forcing policy authors to know each kernel's enumeration.\n+  Acceptance is fail-safe in both directions: a rule referring to a value the\n+  running kernel does not yet know has no effect (deny-by-default still applies\n+  to that operation), and a rule written against future headers loads\n+  identically across kernels so the same policy keeps the same restrictions.\n+  When a value becomes real on a future kernel, the policy activates as written\n+  by the author.\n+* In contrast, unknown ``LANDLOCK_PERM_*`` flags in ``handled_perm`` are\n+  rejected (``-EINVAL``), since Landlock owns that bit space.\n+\n+Cross-domain scopes (``scoped``)\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Scopes restrict **cross-domain interactions** categorically, without rules.\n+Setting a scope flag (e.g.  ``LANDLOCK_SCOPE_SIGNAL``) denies the operation to\n+targets outside the Landlock domain or its children.  Like per-category\n+permissions, scopes provide complete coverage of the controlled operation.\n+\n+Choosing a model for a new feature\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+* If the new feature controls operations on resource objects supplied by the\n+  sandbox author, extend or add a per-object access right\n+  (``handled_access_*``).\n+* If the new feature controls a per-category operation gated by an enum (a\n+  Linux capability, a namespace type, a socket family, etc.), use a\n+  per-category permission (``handled_perm``).  When several such enums could\n+  classify the operation, prefer the enum the originating subsystem already\n+  uses for capability/access checks (e.g. ``CAP_*`` for ``capable()`` hooks,\n+  ``CLONE_NEW*`` for namespace hooks).\n+* When an operation is gated by multiple kernel-defined enums (a classic\n+  example being ``CAP_SYS_ADMIN`` plus a ``CLONE_NEW*`` flag for non-user\n+  namespace creation), define one per-category permission per enum dimension.\n+  Sandbox authors handle each dimension's permission in ``handled_perm`` and\n+  add rules for each; the kernel enforces each dimension at its own LSM hook.\n+  ``LANDLOCK_PERM_NAMESPACE_USE`` and ``LANDLOCK_PERM_CAPABILITY_USE`` follow\n+  this pattern.\n+* If the new feature restricts a categorical cross-domain interaction with no\n+  per-target granularity, use a cross-domain scope (``scoped``).\n+* For all three models, confirm a single LSM hook (or small set of related\n+  hooks) covers every kernel path that exercises the operation.\n+\n Tests\n =====\n \n@@ -150,6 +309,18 @@ Filesystem\n .. kernel-doc:: security/landlock/fs.h\n     :identifiers:\n \n+Namespace\n+---------\n+\n+.. kernel-doc:: security/landlock/ns.h\n+    :identifiers:\n+\n+Capability\n+----------\n+\n+.. kernel-doc:: security/landlock/cap.h\n+    :identifiers:\n+\n Process credential\n ------------------\n \ndiff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst\nindex 5a63d4476c1c75..652f55e8cd8cc5 100644\n--- a/Documentation/userspace-api/landlock.rst\n+++ b/Documentation/userspace-api/landlock.rst\n@@ -8,7 +8,7 @@ Landlock: unprivileged access control\n =====================================\n \n :Author: Mickaël Salaün\n-:Date: June 2026\n+:Date: July 2026\n \n The goal of Landlock is to enable restriction of ambient rights (e.g. global\n filesystem or network access) for a set of processes.  Because Landlock\n@@ -29,21 +29,30 @@ If Landlock is not currently supported, we need to\n Landlock rules\n ==============\n \n-A Landlock rule describes an action on an object which the process intends to\n-perform.  A set of rules is aggregated in a ruleset, which can then restrict\n-the thread enforcing it, and its future children.\n+A Landlock rule describes the actions a process is allowed to perform on a\n+specific resource.  A set of rules is aggregated in a ruleset, which can then\n+restrict the thread enforcing it, and its future children.\n \n-The two existing types of rules are:\n+The existing types of rules are:\n \n Filesystem rules\n-    For these rules, the object is a file hierarchy,\n-    and the related filesystem actions are defined with\n-    `filesystem access rights`.\n+    The rule key is a file hierarchy, and the actions it allows are\n+    defined with `filesystem access rights`.\n \n Network rules (since ABI v4 for TCP and v10 for UDP)\n     For these rules, the object is a TCP or UDP port,\n     and the related actions are defined with `network access rights`.\n \n+Capability rules (since ABI v11)\n+    The rule body lists which Linux capabilities (see\n+    :manpage:`capabilities(7)`) the process may exercise; the action is\n+    defined with `permission flags`.\n+\n+Namespace rules (since ABI v11)\n+    The rule body lists which namespace types (see\n+    :manpage:`namespaces(7)`) the process may use; the action is defined\n+    with `permission flags`.\n+\n Defining and enforcing a security policy\n ----------------------------------------\n \n@@ -87,6 +96,9 @@ to be explicit about the denied-by-default access rights.\n         .scoped =\n             LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |\n             LANDLOCK_SCOPE_SIGNAL,\n+        .handled_perm =\n+            LANDLOCK_PERM_CAPABILITY_USE |\n+            LANDLOCK_PERM_NAMESPACE_USE,\n     };\n \n Because we may not know which kernel version an application will be executed\n@@ -140,6 +152,11 @@ version, and only use the available subset of access rights:\n         ruleset_attr.handled_access_net \u0026=\n             ~(LANDLOCK_ACCESS_NET_BIND_UDP |\n               LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);\n+        __attribute__((fallthrough));\n+    case 10:\n+        /* Removes LANDLOCK_PERM_* for ABI \u003c 11 */\n+        ruleset_attr.handled_perm \u0026= ~(LANDLOCK_PERM_NAMESPACE_USE |\n+                                       LANDLOCK_PERM_CAPABILITY_USE);\n     }\n \n This enables the creation of an inclusive ruleset that will contain our rules.\n@@ -242,6 +259,55 @@ the program explicitly called :manpage:`bind(2)` on port 0.\n         err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,\n                                 \u0026udp_bind, 0);\n \n+Capability and namespace rules use a different attribute layout:\n+``perm`` identifies the permission category (a single\n+``LANDLOCK_PERM_*`` flag) and a type-specific value field carries the bitmask to\n+allow within it.  See `Capability and namespace restrictions`_ for the model.\n+\n+For capability access-control, we can add rules that allow specific\n+capabilities (see :manpage:`capabilities(7)` for the list of ``CAP_*``\n+values).  For instance, to allow ``CAP_SYS_CHROOT`` (so the sandboxed\n+process can call :manpage:`chroot(2)` inside a user namespace):\n+\n+.. code-block:: c\n+\n+    struct landlock_capability_attr cap_attr = {\n+        .perm = LANDLOCK_PERM_CAPABILITY_USE,\n+        .allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_CHROOT),\n+    };\n+\n+    cap_attr.perm \u0026= ruleset_attr.handled_perm;\n+    if (cap_attr.perm)\n+        err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+                                \u0026cap_attr, 0);\n+\n+For namespace access-control, we can add rules that allow using specific\n+namespace types (see :manpage:`namespaces(7)` for the list of ``CLONE_NEW*``\n+values): creating them via :manpage:`unshare(2)` / :manpage:`clone(2)` /\n+:manpage:`clone3(2)`, joining them via :manpage:`setns(2)`, or acquiring an fd\n+reference via :manpage:`open_tree(2)` / :manpage:`fsmount(2)`.  For instance,\n+to allow creating user namespaces (which grants all capabilities inside the new\n+namespace):\n+\n+.. code-block:: c\n+\n+    struct landlock_namespace_attr ns_attr = {\n+        .perm = LANDLOCK_PERM_NAMESPACE_USE,\n+        .allowed_namespace_types = CLONE_NEWUSER,\n+    };\n+\n+    ns_attr.perm \u0026= ruleset_attr.handled_perm;\n+    if (ns_attr.perm)\n+        err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+                                \u0026ns_attr, 0);\n+\n+Together, these two rules allow an unprivileged process to create a user\n+namespace and call :manpage:`chroot(2)` inside it, while denying all other\n+capabilities and namespace types.  User namespace creation is the one operation\n+that does not require ``CAP_SYS_ADMIN``, so no capability rule is needed for it.\n+See `Capability and namespace restrictions`_ for details on capability\n+requirements.\n+\n When passing a non-zero ``flags`` argument to ``landlock_restrict_self()``, a\n similar backwards compatibility check is needed for the restrict flags\n (see sys_landlock_restrict_self() documentation for available flags):\n@@ -420,9 +486,135 @@ The operations which can be scoped are:\n     A :manpage:`sendto(2)` on a socket which was previously connected will not\n     be restricted.  This works for both datagram and stream sockets.\n \n-IPC scoping does not support exceptions via :manpage:`landlock_add_rule(2)`.\n-If an operation is scoped within a domain, no rules can be added to allow access\n-to resources or processes outside of the scope.\n+Scoping does not support exceptions via :manpage:`landlock_add_rule(2)`.  If an\n+operation is scoped within a domain, no rules can be added to allow access to\n+resources or processes outside of the scope.\n+\n+Capability and namespace restrictions\n+-------------------------------------\n+\n+``handled_perm`` declares per-category permissions: each permission selects\n+which members of a kernel-defined category (CAP_* capabilities, CLONE_NEW*\n+namespace types) the process may use.  Unlike per-object access rights\n+(``handled_access_*``) or cross-domain scopes (``scoped``), per-category\n+permissions constrain the sandboxed process's own use of these enums; members\n+not allowed by a rule are denied by default.\n+\n+``LANDLOCK_PERM_NAMESPACE_USE`` gates *acquisition of access* to\n+namespaces: creation via :manpage:`unshare(2)` / :manpage:`clone(2)`\n+/ :manpage:`clone3(2)`, entry via :manpage:`setns(2)`, and fd-reference\n+acquisition via :manpage:`open_tree(2)` / :manpage:`fsmount(2)`.  Namespaces\n+the process is already a member of when the domain is enforced are implicitly\n+allowed (the process could not continue running otherwise); rules describe which\n+new namespace types the process may acquire.  ``LANDLOCK_PERM_CAPABILITY_USE``\n+gates every exercise of a capability after the domain is enforced, regardless\n+of how the capability was obtained (inherited credentials, ``CLONE_NEWUSER``\n+grant, ``setuid``/file-cap-bearing :manpage:`execve(2)`, etc.).  Configuring\n+both together restricts what privileges are available *and* the namespaces in\n+which they take effect, which matters because user namespace creation has no\n+capability check and grants all capabilities within the new namespace: gating\n+only one of the two leaves a kernel attack-surface widening path open.  When a\n+domain handles both permissions, an operation is allowed only if each\n+independently allows it: :manpage:`unshare(2)` with ``CLONE_NEWNET`` then\n+requires both a rule allowing ``CLONE_NEWNET`` and a rule allowing\n+``CAP_SYS_ADMIN``.\n+\n+``LANDLOCK_PERM_CAPABILITY_USE`` complements :manpage:`prctl(2)`\n+``PR_SET_NO_NEW_PRIVS`` but does not replace it.  ``PR_SET_NO_NEW_PRIVS``\n+prevents privilege *acquisition* via :manpage:`execve(2)` (setuid, file\n+capability xattrs, privilege-elevating LSM transitions) and is a prerequisite\n+for unprivileged Landlock self-sandboxing.  ``LANDLOCK_PERM_CAPABILITY_USE``\n+restricts *exercise* of capabilities the process already holds, including those\n+gained via ``CLONE_NEWUSER`` which ``PR_SET_NO_NEW_PRIVS`` does not block.\n+Sandboxes typically set both.\n+\n+Rules are added with ``LANDLOCK_RULE_CAPABILITY`` and \u0026struct\n+landlock_capability_attr (each rule lists ``CAP_*`` values to allow), and with\n+``LANDLOCK_RULE_NAMESPACE`` and \u0026struct landlock_namespace_attr (each rule\n+lists ``CLONE_NEW*`` flags to allow).  Landlock is purely restrictive: it can\n+only deny what the traditional check would have allowed, never grant additional\n+privileges.\n+\n+Denials of specific capabilities or namespace types can be excluded from audit\n+logging per rule, by listing them in the ``quiet_capabilities`` or\n+``quiet_namespace_types`` field of the rule (independently of the allowed\n+members).  Quieting requires the permission to be handled, is member-granular,\n+and only suppresses the audit record of a denial attributed to the rule's own\n+layer; it never changes the denial itself.  Sandboxes should quiet only\n+specific members known to be requested but expected to be denied (for example\n+for compatibility with older callers), never a blanket set: a blanket set\n+follows the running kernel's known members and would hide surprising or future\n+denials.\n+\n+Rule bodies silently accept values unknown to the current kernel (capabilities\n+above ``CAP_LAST_CAP``, unrecognised ``CLONE_NEW*`` bits): they have no runtime\n+effect, so a rule compiled against future kernel headers loads without error on\n+older kernels.  Future kernels gain new members denied by default until a rule\n+explicitly allows them.\n+\n+The single ``LANDLOCK_PERM_NAMESPACE_USE`` bit gates every kernel path that\n+grants the calling process access to a namespace of the controlled types,\n+whether by becoming a member of the namespace or by holding a file descriptor\n+that references it.  The covered syscall paths are:\n+\n+* :manpage:`unshare(2)` with ``CLONE_NEW*``: the caller becomes a member of a\n+  newly-created namespace.\n+* :manpage:`clone(2)` (or :manpage:`clone3(2)`) with ``CLONE_NEW*``: the\n+  child becomes a member of a newly-created namespace.\n+* :manpage:`setns(2)`: the caller becomes a member of an existing namespace\n+  referenced by file descriptor.\n+* :manpage:`open_tree(2)` with ``OPEN_TREE_NAMESPACE``: the caller obtains a\n+  file descriptor referring to a newly-created mount namespace.\n+* :manpage:`open_tree(2)` with ``OPEN_TREE_CLONE``: the caller obtains a file\n+  descriptor referring to a newly-created anonymous mount namespace.\n+* :manpage:`fsmount(2)` with ``FSMOUNT_NAMESPACE``: the caller obtains a file\n+  descriptor referring to a newly-created mount namespace.\n+* :manpage:`fsmount(2)` (default): the caller obtains a file descriptor\n+  referring to a newly-created anonymous mount namespace.\n+\n+Anonymous mount namespaces (created by ``open_tree(OPEN_TREE_CLONE)`` and the\n+default :manpage:`fsmount(2)`) are intentionally covered by the bit even though\n+the calling process does not become a member of them.  Without this coverage, a\n+sandboxed process could combine ``open_tree(OPEN_TREE_CLONE)`` with\n+:manpage:`move_mount(2)` to graft mounts from a freshly-allocated mount\n+namespace into its current namespace, bypassing a ``CLONE_NEWNS`` restriction\n+(these mount operations require ``CAP_SYS_ADMIN``, which a sandboxed process\n+typically obtains by first creating a user namespace).\n+\n+In practice, unprivileged processes first create a user namespace (which\n+requires no capability and grants all capabilities within it), then use those\n+capabilities to create other namespace types.  All non-user namespace types\n+require ``CAP_SYS_ADMIN`` for both creation and :manpage:`setns(2)` entry; mount\n+namespace entry additionally requires ``CAP_SYS_CHROOT``.  For\n+:manpage:`setns(2)`, capabilities are checked relative to the target namespace,\n+so a process in an ancestor user namespace naturally satisfies them; this\n+includes joining user namespaces, which requires ``CAP_SYS_ADMIN``.  When\n+``LANDLOCK_PERM_CAPABILITY_USE`` is also handled, each of these capabilities\n+must be explicitly allowed by a rule.\n+\n+Creating a non-user namespace always requires ``CAP_SYS_ADMIN``, and\n+``LANDLOCK_PERM_CAPABILITY_USE`` (when handled) gates that check regardless of\n+which user namespace it targets.  This is true whether the namespace is created\n+on its own, combined with ``CLONE_NEWUSER`` in a single :manpage:`unshare(2)`\n+call, or in a separate :manpage:`unshare(2)` call after a user namespace: in\n+all cases the kernel checks ``CAP_SYS_ADMIN`` against the owning user\n+namespace, and Landlock's capability hook denies it unless a rule allows\n+``CAP_SYS_ADMIN``.  Combining ``CLONE_NEWUSER`` with another ``CLONE_NEW*``\n+flag therefore does not avoid the capability check; only user namespace\n+creation itself needs no capability rule.\n+\n+When creating child user namespaces, it is recommended to also create a\n+dedicated Landlock domain with restrictions relevant to each namespace context.\n+\n+Note that ``LANDLOCK_PERM_CAPABILITY_USE`` restricts the *use* of capabilities,\n+not their presence in the process's credential.  Capability sets can change\n+after a domain is enforced through user namespace entry or :manpage:`capset(2)`;\n+privileged sandboxes that did not set ``PR_SET_NO_NEW_PRIVS`` may also gain\n+capabilities through :manpage:`execve(2)` of binaries with file capabilities.\n+In all cases, :manpage:`capget(2)` will report the credential's capability sets,\n+but any denied capability will fail with ``EPERM`` when exercised.  Do not rely\n+on :manpage:`capget(2)` to determine whether the policy permits a given\n+capability; only the actual operation will return ``EPERM`` upon denial.\n \n Truncating files\n ----------------\n@@ -585,7 +777,7 @@ Access rights\n -------------\n \n .. kernel-doc:: include/uapi/linux/landlock.h\n-    :identifiers: fs_access net_access scope\n+    :identifiers: fs_access net_access scope perm\n \n Creating a new ruleset\n ----------------------\n@@ -604,7 +796,8 @@ Extending a ruleset\n \n .. kernel-doc:: include/uapi/linux/landlock.h\n     :identifiers: landlock_rule_type landlock_path_beneath_attr\n-                  landlock_net_port_attr\n+                  landlock_net_port_attr landlock_capability_attr\n+                  landlock_namespace_attr\n \n Enforcing a ruleset\n -------------------\n@@ -789,6 +982,23 @@ when at least one sys_landlock_add_rule() call is made for it with the\n ``LANDLOCK_ADD_RULE_QUIET`` flag, additional add-rule calls for the same\n object without this flag do not clear it.\n \n+Capability (ABI \u003c 11)\n+---------------------\n+\n+Starting with the Landlock ABI version 11, it is possible to restrict\n+:manpage:`capabilities(7)` with the new ``LANDLOCK_PERM_CAPABILITY_USE``\n+permission flag and ``LANDLOCK_RULE_CAPABILITY`` rule type.\n+\n+Namespace (ABI \u003c 11)\n+--------------------\n+\n+Starting with the Landlock ABI version 11, it is possible to restrict namespace\n+use (see :manpage:`namespaces(7)`) across creation (:manpage:`unshare(2)`,\n+:manpage:`clone(2)`, :manpage:`clone3(2)`), entry (:manpage:`setns(2)`), and\n+fd-reference acquisition (:manpage:`open_tree(2)`, :manpage:`fsmount(2)`) with\n+the new ``LANDLOCK_PERM_NAMESPACE_USE`` permission flag and\n+``LANDLOCK_RULE_NAMESPACE`` rule type.\n+\n .. _kernel_support:\n \n Kernel support\ndiff --git a/fs/namespace.c b/fs/namespace.c\nindex 3d5cd5bf3b0537..0602f133f3a04c 100644\n--- a/fs/namespace.c\n+++ b/fs/namespace.c\n@@ -4186,8 +4186,7 @@ static void dec_mnt_namespaces(struct ucounts *ucounts)\n \n static void free_mnt_ns(struct mnt_namespace *ns)\n {\n-\tif (!is_anon_ns(ns))\n-\t\tns_common_free(ns);\n+\tns_common_free(ns);\n \tdec_mnt_namespaces(ns-\u003eucounts);\n \tmnt_ns_tree_remove(ns);\n }\ndiff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h\nindex 584db296e43b20..526a8e7471c80c 100644\n--- a/include/linux/lsm_audit.h\n+++ b/include/linux/lsm_audit.h\n@@ -78,6 +78,7 @@ struct common_audit_data {\n #define LSM_AUDIT_DATA_NOTIFICATION 16\n #define LSM_AUDIT_DATA_ANONINODE\t17\n #define LSM_AUDIT_DATA_NLMSGTYPE\t18\n+#define LSM_AUDIT_DATA_NS\t\t19\n \tunion \t{\n \t\tstruct path path;\n \t\tstruct dentry *dentry;\n@@ -100,6 +101,10 @@ struct common_audit_data {\n \t\tint reason;\n \t\tconst char *anonclass;\n \t\tu16 nlmsg_type;\n+\t\tstruct {\n+\t\t\tu32 ns_type;\n+\t\t\tu64 ns_id;\n+\t\t} ns;\n \t} u;\n \t/* this union contains LSM specific data */\n \tunion {\ndiff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h\nindex 65c9609ec20770..0da6c7e8f65957 100644\n--- a/include/linux/lsm_hook_defs.h\n+++ b/include/linux/lsm_hook_defs.h\n@@ -265,6 +265,9 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2,\n LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p,\n \t struct inode *inode)\n LSM_HOOK(int, 0, userns_create, const struct cred *cred)\n+LSM_HOOK(int, 0, namespace_init, struct ns_common *ns)\n+LSM_HOOK(void, LSM_RET_VOID, namespace_free, struct ns_common *ns)\n+LSM_HOOK(int, 0, namespace_install, const struct nsset *nsset, struct ns_common *ns)\n LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag)\n LSM_HOOK(void, LSM_RET_VOID, ipc_getlsmprop, struct kern_ipc_perm *ipcp,\n \t struct lsm_prop *prop)\ndiff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h\nindex b4f8cad53ddb03..5cff1306952952 100644\n--- a/include/linux/lsm_hooks.h\n+++ b/include/linux/lsm_hooks.h\n@@ -112,6 +112,7 @@ struct lsm_blob_sizes {\n \tunsigned int lbs_ipc;\n \tunsigned int lbs_key;\n \tunsigned int lbs_msg_msg;\n+\tunsigned int lbs_ns;\n \tunsigned int lbs_perf_event;\n \tunsigned int lbs_task;\n \tunsigned int lbs_xattr_count; /* num xattr slots in new_xattrs array */\ndiff --git a/include/linux/ns/ns_common_types.h b/include/linux/ns/ns_common_types.h\nindex ea45c54e4435ed..5cfe0ce3c8818e 100644\n--- a/include/linux/ns/ns_common_types.h\n+++ b/include/linux/ns/ns_common_types.h\n@@ -116,6 +116,9 @@ struct ns_common {\n \tstruct dentry *stashed;\n \tconst struct proc_ns_operations *ops;\n \tunsigned int inum;\n+#ifdef CONFIG_SECURITY\n+\tvoid *ns_security;\n+#endif\n \tunion {\n \t\tstruct ns_tree;\n \t\tstruct rcu_head ns_rcu;\ndiff --git a/include/linux/security.h b/include/linux/security.h\nindex 153e9043058f88..bf002ed14ac83c 100644\n--- a/include/linux/security.h\n+++ b/include/linux/security.h\n@@ -67,6 +67,7 @@ enum fs_value_type;\n struct watch;\n struct watch_notification;\n struct lsm_ctx;\n+struct nsset;\n \n /* Default (no) options for the capable function */\n #define CAP_OPT_NONE 0x0\n@@ -80,6 +81,7 @@ struct lsm_ctx;\n \n struct ctl_table;\n struct audit_krule;\n+struct ns_common;\n struct user_namespace;\n struct timezone;\n \n@@ -540,6 +542,9 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,\n \t\t\tunsigned long arg4, unsigned long arg5);\n void security_task_to_inode(struct task_struct *p, struct inode *inode);\n int security_create_user_ns(const struct cred *cred);\n+int security_namespace_init(struct ns_common *ns);\n+void security_namespace_free(struct ns_common *ns);\n+int security_namespace_install(const struct nsset *nsset, struct ns_common *ns);\n int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag);\n void security_ipc_getlsmprop(struct kern_ipc_perm *ipcp, struct lsm_prop *prop);\n int security_msg_msg_alloc(struct msg_msg *msg);\n@@ -1431,6 +1436,21 @@ static inline int security_create_user_ns(const struct cred *cred)\n \treturn 0;\n }\n \n+static inline int security_namespace_init(struct ns_common *ns)\n+{\n+\treturn 0;\n+}\n+\n+static inline void security_namespace_free(struct ns_common *ns)\n+{\n+}\n+\n+static inline int security_namespace_install(const struct nsset *nsset,\n+\t\t\t\t\t     struct ns_common *ns)\n+{\n+\treturn 0;\n+}\n+\n static inline int security_ipc_permission(struct kern_ipc_perm *ipcp,\n \t\t\t\t\t  short flag)\n {\ndiff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h\nindex 7ffe2ef127ee74..393dd62fcde14f 100644\n--- a/include/uapi/linux/landlock.h\n+++ b/include/uapi/linux/landlock.h\n@@ -78,6 +78,15 @@ struct landlock_ruleset_attr {\n \t * @quiet_scoped: Bitmask of scoped actions which should not be logged.\n \t */\n \t__u64 quiet_scoped;\n+\t/**\n+\t * @handled_perm: Bitmask of permissions (cf. `Permission flags`_) that\n+\t * this ruleset handles.  Each permission controls a per-category\n+\t * operation gated by an enum (CLONE_NEW* for namespace types, CAP_* for\n+\t * capabilities); all uses of category members are denied unless\n+\t * explicitly allowed by a rule.  See\n+\t * Documentation/security/landlock.rst for the rationale.\n+\t */\n+\t__u64 handled_perm;\n };\n \n /**\n@@ -215,6 +224,15 @@ enum landlock_rule_type {\n \t * landlock_net_port_attr .\n \t */\n \tLANDLOCK_RULE_NET_PORT,\n+\t/**\n+\t * @LANDLOCK_RULE_NAMESPACE: Type of a \u0026struct landlock_namespace_attr .\n+\t */\n+\tLANDLOCK_RULE_NAMESPACE,\n+\t/**\n+\t * @LANDLOCK_RULE_CAPABILITY: Type of a \u0026struct\n+\t * landlock_capability_attr .\n+\t */\n+\tLANDLOCK_RULE_CAPABILITY,\n };\n \n /**\n@@ -268,6 +286,74 @@ struct landlock_net_port_attr {\n \t__u64 port;\n };\n \n+/**\n+ * struct landlock_namespace_attr - Namespace type definition\n+ *\n+ * Argument of sys_landlock_add_rule() with %LANDLOCK_RULE_NAMESPACE.\n+ */\n+struct landlock_namespace_attr {\n+\t/**\n+\t * @perm: Must be set to %LANDLOCK_PERM_NAMESPACE_USE.\n+\t */\n+\t__u64 perm;\n+\t/**\n+\t * @allowed_namespace_types: Bitmask of namespace types (``CLONE_NEW*``\n+\t * flags) to allow under this rule.  Unknown bits are silently ignored\n+\t * for forward compatibility.\n+\t */\n+\t__u64 allowed_namespace_types;\n+\t/**\n+\t * @quiet_namespace_types: Bitmask of namespace types (``CLONE_NEW*``\n+\t * flags) whose denial by this layer should not be logged, even if\n+\t * logging would normally take place per landlock_restrict_self() flags.\n+\t * Only denials attributed to this layer are suppressed (see `permission\n+\t * flags`_).  Bits also set in @allowed_namespace_types have no effect,\n+\t * since an allowed type is never denied.  Unknown bits are silently\n+\t * ignored.\n+\t *\n+\t * At least one of @allowed_namespace_types or @quiet_namespace_types\n+\t * must be non-zero, otherwise the call returns ``-ENOMSG``.  The\n+\t * non-zero check runs on the raw input before unknown-bit masking, so a\n+\t * rule that sets only bits unknown to the running kernel succeeds but\n+\t * has no runtime effect.\n+\t */\n+\t__u64 quiet_namespace_types;\n+};\n+\n+/**\n+ * struct landlock_capability_attr - Capability definition\n+ *\n+ * Argument of sys_landlock_add_rule() with %LANDLOCK_RULE_CAPABILITY.\n+ */\n+struct landlock_capability_attr {\n+\t/**\n+\t * @perm: Must be set to %LANDLOCK_PERM_CAPABILITY_USE.\n+\t */\n+\t__u64 perm;\n+\t/**\n+\t * @allowed_capabilities: Bitmask of capabilities (``1ULL \u003c\u003c CAP_*``) to\n+\t * allow under this rule.  Bits above ``CAP_LAST_CAP`` are silently\n+\t * ignored for forward compatibility.\n+\t */\n+\t__u64 allowed_capabilities;\n+\t/**\n+\t * @quiet_capabilities: Bitmask of capabilities (``1ULL \u003c\u003c CAP_*``)\n+\t * whose denial by this layer should not be logged, even if logging\n+\t * would normally take place per landlock_restrict_self() flags.  Only\n+\t * denials attributed to this layer are suppressed (see `permission\n+\t * flags`_).  Bits also set in @allowed_capabilities have no effect,\n+\t * since an allowed capability is never denied.  Bits above\n+\t * ``CAP_LAST_CAP`` are silently ignored.\n+\t *\n+\t * At least one of @allowed_capabilities or @quiet_capabilities must be\n+\t * non-zero, otherwise the call returns ``-ENOMSG``.  The non-zero check\n+\t * runs on the raw input before unknown-bit masking, so a rule that sets\n+\t * only bits unknown to the running kernel (above ``CAP_LAST_CAP``)\n+\t * succeeds but has no runtime effect.\n+\t */\n+\t__u64 quiet_capabilities;\n+};\n+\n /**\n  * DOC: fs_access\n  *\n@@ -491,6 +577,45 @@ struct landlock_net_port_attr {\n /* clang-format off */\n #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET\t\t(1ULL \u003c\u003c 0)\n #define LANDLOCK_SCOPE_SIGNAL\t\t                (1ULL \u003c\u003c 1)\n-/* clang-format on*/\n+/* clang-format on */\n+\n+/**\n+ * DOC: perm\n+ *\n+ * Permission flags\n+ * ~~~~~~~~~~~~~~~~\n+ *\n+ * These flags restrict per-category operations gated by enums (CLONE_NEW* for\n+ * namespace types, CAP_* for capabilities).  Each flag covers every kernel path\n+ * that exercises a member of the category.  Handled permissions that are not\n+ * explicitly allowed by a rule are denied by default.  Rule values reference\n+ * constants from other kernel subsystems; unknown values are silently accepted\n+ * for forward compatibility since the allow-list denies them by default.  See\n+ * Documentation/security/landlock.rst for design details.\n+ *\n+ * When a ruleset handles multiple permissions whose operations overlap (e.g. a\n+ * non-user namespace needs both its namespace type and CAP_SYS_ADMIN), the\n+ * operation is allowed only if each handled permission independently allows it.\n+ * See Documentation/userspace-api/landlock.rst.\n+ *\n+ * - %LANDLOCK_PERM_NAMESPACE_USE: Restrict the use of specific namespace\n+ *   types: creation (:manpage:`unshare(2)`, :manpage:`clone(2)`,\n+ *   :manpage:`clone3(2)`), joining (:manpage:`setns(2)`), and acquiring an\n+ *   fd reference (:manpage:`open_tree(2)`, :manpage:`fsmount(2)`).  A\n+ *   process in a Landlock domain that handles this permission is denied\n+ *   from using namespace types that are not explicitly allowed by a\n+ *   %LANDLOCK_RULE_NAMESPACE rule.\n+ * - %LANDLOCK_PERM_CAPABILITY_USE: Restrict the use of specific Linux\n+ *   capabilities.  A process in a Landlock domain that handles this\n+ *   permission is denied from exercising capabilities that are not\n+ *   explicitly allowed by a %LANDLOCK_RULE_CAPABILITY rule.  This hook\n+ *   is purely restrictive: it can deny capabilities that the kernel\n+ *   would otherwise grant, but it can never grant capabilities that the\n+ *   kernel already denied.\n+ */\n+/* clang-format off */\n+#define LANDLOCK_PERM_NAMESPACE_USE\t\t\t(1ULL \u003c\u003c 0)\n+#define LANDLOCK_PERM_CAPABILITY_USE\t\t\t(1ULL \u003c\u003c 1)\n+/* clang-format on */\n \n #endif /* _UAPI_LINUX_LANDLOCK_H */\ndiff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h\nindex a25e38d1c8747d..ea0f0267d90f7b 100644\n--- a/include/uapi/linux/nsfs.h\n+++ b/include/uapi/linux/nsfs.h\n@@ -55,6 +55,7 @@ enum init_ns_ino {\n \tMNT_NS_INIT_INO\t\t= 0xEFFFFFF8U,\n #ifdef __KERNEL__\n \tMNT_NS_ANON_INO\t\t= 0xEFFFFFF7U,\n+\tMNT_NS_INO_SPECIAL_MAX\t= MNT_NS_ANON_INO,\n #endif\n };\n \ndiff --git a/kernel/nscommon.c b/kernel/nscommon.c\nindex 3166c1fd844afd..e72426bba29ab1 100644\n--- a/kernel/nscommon.c\n+++ b/kernel/nscommon.c\n@@ -4,6 +4,7 @@\n #include \u003clinux/ns_common.h\u003e\n #include \u003clinux/nstree.h\u003e\n #include \u003clinux/proc_ns.h\u003e\n+#include \u003clinux/security.h\u003e\n #include \u003clinux/user_namespace.h\u003e\n #include \u003clinux/vfsdebug.h\u003e\n \n@@ -59,6 +60,9 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope\n \n \trefcount_set(\u0026ns-\u003e__ns_ref, 1);\n \tns-\u003estashed = NULL;\n+#ifdef CONFIG_SECURITY\n+\tns-\u003ens_security = NULL;\n+#endif\n \tns-\u003eops = ops;\n \tns-\u003ens_id = 0;\n \tns-\u003ens_type = ns_type;\n@@ -77,6 +81,14 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope\n \t\tret = proc_alloc_inum(\u0026ns-\u003einum);\n \tif (ret)\n \t\treturn ret;\n+\n+\tret = security_namespace_init(ns);\n+\tif (ret) {\n+\t\tif (!inum)\n+\t\t\tproc_free_inum(ns-\u003einum);\n+\t\treturn ret;\n+\t}\n+\n \t/*\n \t * Tree ref starts at 0. It's incremented when namespace enters\n \t * active use (installed in nsproxy) and decremented when all\n@@ -91,7 +103,10 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope\n \n void __ns_common_free(struct ns_common *ns)\n {\n-\tproc_free_inum(ns-\u003einum);\n+\tsecurity_namespace_free(ns);\n+\n+\tif (ns-\u003einum \u003e MNT_NS_INO_SPECIAL_MAX)\n+\t\tproc_free_inum(ns-\u003einum);\n }\n \n struct ns_common *__must_check ns_owner(struct ns_common *ns)\ndiff --git a/kernel/nsproxy.c b/kernel/nsproxy.c\nindex d9d3d5973bf523..0f1b208d8eeff1 100644\n--- a/kernel/nsproxy.c\n+++ b/kernel/nsproxy.c\n@@ -385,6 +385,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)\n \n static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)\n {\n+\tint ret;\n+\n+\tret = security_namespace_install(nsset, ns);\n+\tif (ret)\n+\t\treturn ret;\n+\n \treturn ns-\u003eops-\u003einstall(nsset, ns);\n }\n \ndiff --git a/samples/landlock/Makefile b/samples/landlock/Makefile\nindex 5d601e51c2ebf2..b30239c8a281ce 100644\n--- a/samples/landlock/Makefile\n+++ b/samples/landlock/Makefile\n@@ -3,6 +3,7 @@\n userprogs-always-y := sandboxer\n \n userccflags += -I usr/include\n+userldlibs += -lcap\n \n .PHONY: all clean\n \ndiff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c\nindex ac71019e62122a..b528ef41e2f561 100644\n--- a/samples/landlock/sandboxer.c\n+++ b/samples/landlock/sandboxer.c\n@@ -14,15 +14,17 @@\n #include \u003cfcntl.h\u003e\n #include \u003clinux/landlock.h\u003e\n #include \u003clinux/socket.h\u003e\n+#include \u003csched.h\u003e\n+#include \u003cstdbool.h\u003e\n #include \u003cstddef.h\u003e\n #include \u003cstdio.h\u003e\n #include \u003cstdlib.h\u003e\n #include \u003cstring.h\u003e\n+#include \u003csys/capability.h\u003e\n #include \u003csys/prctl.h\u003e\n #include \u003csys/stat.h\u003e\n #include \u003csys/syscall.h\u003e\n #include \u003cunistd.h\u003e\n-#include \u003cstdbool.h\u003e\n \n #if defined(__GLIBC__)\n #include \u003clinux/prctl.h\u003e\n@@ -62,6 +64,10 @@ static inline int landlock_restrict_self(const int ruleset_fd,\n #define ENV_TCP_BIND_NAME \"LL_TCP_BIND\"\n #define ENV_TCP_CONNECT_NAME \"LL_TCP_CONNECT\"\n #define ENV_NET_QUIET_NAME \"LL_NET_QUIET\"\n+#define ENV_CAP_NAME \"LL_CAP\"\n+#define ENV_CAP_QUIET_NAME \"LL_CAP_QUIET\"\n+#define ENV_NS_NAME \"LL_NS\"\n+#define ENV_NS_QUIET_NAME \"LL_NS_QUIET\"\n #define ENV_SCOPED_NAME \"LL_SCOPED\"\n #define ENV_QUIET_ACCESS_NAME \"LL_QUIET_ACCESS\"\n #define ENV_FORCE_LOG_NAME \"LL_FORCE_LOG\"\n@@ -232,6 +238,161 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,\n \treturn ret;\n }\n \n+static __u64 str2ns(const char *const name)\n+{\n+\tstatic const struct {\n+\t\tconst char *name;\n+\t\t__u64 value;\n+\t} ns_map[] = {\n+\t\t/* clang-format off */\n+\t\t{ \"cgroup\",\tCLONE_NEWCGROUP },\n+\t\t{ \"ipc\",\tCLONE_NEWIPC },\n+\t\t{ \"mnt\",\tCLONE_NEWNS },\n+\t\t{ \"net\",\tCLONE_NEWNET },\n+\t\t{ \"pid\",\tCLONE_NEWPID },\n+\t\t{ \"time\",\tCLONE_NEWTIME },\n+\t\t{ \"user\",\tCLONE_NEWUSER },\n+\t\t{ \"uts\",\tCLONE_NEWUTS },\n+\t\t/* clang-format on */\n+\t};\n+\tsize_t i;\n+\n+\tfor (i = 0; i \u003c sizeof(ns_map) / sizeof(ns_map[0]); i++) {\n+\t\tif (strcmp(name, ns_map[i].name) == 0)\n+\t\t\treturn ns_map[i].value;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Parses a colon-delimited list of capability names into a bitmask.  Returns 0\n+ * on success (mask 0 when the variable is unset or empty), or 1 on a parse\n+ * error.\n+ */\n+static int parse_cap_list(const char *const env_var, __u64 *const mask)\n+{\n+\tint ret = 1;\n+\tchar *env_cap_name, *env_cap_name_next, *strcap;\n+\n+\t*mask = 0;\n+\tenv_cap_name = getenv(env_var);\n+\tif (!env_cap_name)\n+\t\treturn 0;\n+\tenv_cap_name = strdup(env_cap_name);\n+\tunsetenv(env_var);\n+\n+\tenv_cap_name_next = env_cap_name;\n+\twhile ((strcap = strsep(\u0026env_cap_name_next, ENV_DELIMITER))) {\n+\t\tcap_value_t cap;\n+\n+\t\tif (strcmp(strcap, \"\") == 0)\n+\t\t\tcontinue;\n+\n+\t\tif (cap_from_name(strcap, \u0026cap)) {\n+\t\t\tfprintf(stderr, \"Failed to parse capability \\\"%s\\\"\\n\",\n+\t\t\t\tstrcap);\n+\t\t\tgoto out_free_name;\n+\t\t}\n+\t\t*mask |= 1ULL \u003c\u003c cap;\n+\t}\n+\tret = 0;\n+\n+out_free_name:\n+\tfree(env_cap_name);\n+\treturn ret;\n+}\n+\n+static int populate_ruleset_cap(const char *const allowed_env,\n+\t\t\t\tconst char *const quiet_env,\n+\t\t\t\tconst int ruleset_fd)\n+{\n+\tstruct landlock_capability_attr cap_attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\n+\tif (parse_cap_list(allowed_env, \u0026cap_attr.allowed_capabilities))\n+\t\treturn 1;\n+\tif (parse_cap_list(quiet_env, \u0026cap_attr.quiet_capabilities))\n+\t\treturn 1;\n+\n+\tif (!cap_attr.allowed_capabilities \u0026\u0026 !cap_attr.quiet_capabilities)\n+\t\treturn 0;\n+\n+\tif (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, \u0026cap_attr,\n+\t\t\t      0)) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Failed to update the ruleset with capabilities: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Parses a colon-delimited list of namespace type names into a bitmask.\n+ * Returns 0 on success (mask 0 when the variable is unset or empty), or 1 on a\n+ * parse error.\n+ */\n+static int parse_ns_list(const char *const env_var, __u64 *const mask)\n+{\n+\tint ret = 1;\n+\tchar *env_ns_name, *env_ns_name_next, *strns;\n+\n+\t*mask = 0;\n+\tenv_ns_name = getenv(env_var);\n+\tif (!env_ns_name)\n+\t\treturn 0;\n+\tenv_ns_name = strdup(env_ns_name);\n+\tunsetenv(env_var);\n+\n+\tenv_ns_name_next = env_ns_name;\n+\twhile ((strns = strsep(\u0026env_ns_name_next, ENV_DELIMITER))) {\n+\t\t__u64 ns_type;\n+\n+\t\tif (strcmp(strns, \"\") == 0)\n+\t\t\tcontinue;\n+\n+\t\tns_type = str2ns(strns);\n+\t\tif (!ns_type) {\n+\t\t\tfprintf(stderr, \"Unknown namespace type \\\"%s\\\"\\n\",\n+\t\t\t\tstrns);\n+\t\t\tgoto out_free_name;\n+\t\t}\n+\t\t*mask |= ns_type;\n+\t}\n+\tret = 0;\n+\n+out_free_name:\n+\tfree(env_ns_name);\n+\treturn ret;\n+}\n+\n+static int populate_ruleset_ns(const char *const allowed_env,\n+\t\t\t       const char *const quiet_env,\n+\t\t\t       const int ruleset_fd)\n+{\n+\tstruct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t};\n+\n+\tif (parse_ns_list(allowed_env, \u0026ns_attr.allowed_namespace_types))\n+\t\treturn 1;\n+\tif (parse_ns_list(quiet_env, \u0026ns_attr.quiet_namespace_types))\n+\t\treturn 1;\n+\n+\tif (!ns_attr.allowed_namespace_types \u0026\u0026 !ns_attr.quiet_namespace_types)\n+\t\treturn 0;\n+\n+\tif (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, \u0026ns_attr,\n+\t\t\t      0)) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Failed to update the ruleset with namespace types: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n /* Returns true on error, false otherwise. */\n static bool check_ruleset_scope(const char *const env_var,\n \t\t\t\tstruct landlock_ruleset_attr *ruleset_attr)\n@@ -369,7 +530,7 @@ static int add_quiet_access(const char *const env_var,\n \treturn 0;\n }\n \n-#define LANDLOCK_ABI_LAST 10\n+#define LANDLOCK_ABI_LAST 11\n \n #define XSTR(s) #s\n #define STR(s) XSTR(s)\n@@ -397,6 +558,17 @@ static const char help[] =\n \t\"* \" ENV_UDP_CONNECT_SEND_NAME \": remote UDP ports allowed to connect \"\n \t\"or send to (client: use as destination port / server: receive only from it)\\n\"\n \t\"(caution: sending requires being able to bind to a local source port)\\n\"\n+\t\"* \" ENV_CAP_NAME \": capabilities allowed to use, as names \"\n+\t\"or numbers (e.g. cap_net_bind_service, cap_sys_admin, 18)\\n\"\n+\t\"* \" ENV_CAP_QUIET_NAME \": capabilities whose denial should not be logged, \"\n+\t\"same value format as \" ENV_CAP_NAME \" (quieting a member that is also \"\n+\t\"allowed is inert)\\n\"\n+\t\"* \" ENV_NS_NAME \": namespace types allowed to use \"\n+\t\"(cgroup, ipc, mnt, net, pid, time, user, uts)\\n\"\n+\t\"* \" ENV_NS_QUIET_NAME \": namespace types whose denial should not be \"\n+\t\"logged, same value format as \" ENV_NS_NAME \"\\n\"\n+\t\"  (quieting a member that is also allowed is inert, as an allowed \"\n+\t\"member is never denied)\\n\"\n \t\"* \" ENV_SCOPED_NAME \": actions denied on the outside of the landlock domain\\n\"\n \t\"  - \\\"a\\\" to restrict opening abstract unix sockets\\n\"\n \t\"  - \\\"s\\\" to restrict sending signals\\n\"\n@@ -423,6 +595,8 @@ static const char help[] =\n \tENV_TCP_BIND_NAME \"=\\\"9418\\\" \"\n \tENV_TCP_CONNECT_NAME \"=\\\"80:443\\\" \"\n \tENV_UDP_CONNECT_SEND_NAME \"=\\\"53\\\" \"\n+\tENV_CAP_NAME \"=\\\"cap_sys_admin\\\" \"\n+\tENV_NS_NAME \"=\\\"user:uts:net\\\" \"\n \tENV_SCOPED_NAME \"=\\\"a:s\\\" \"\n \t\"%1$s bash -i\\n\"\n \t\"\\n\"\n@@ -451,6 +625,8 @@ int main(const int argc, char *const argv[], char *const *const envp)\n \t\t.quiet_access_fs = 0,\n \t\t.quiet_access_net = 0,\n \t\t.quiet_scoped = 0,\n+\t\t.handled_perm = LANDLOCK_PERM_CAPABILITY_USE |\n+\t\t\t\tLANDLOCK_PERM_NAMESPACE_USE,\n \t};\n \tbool quiet_supported = true;\n \tint supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;\n@@ -545,7 +721,11 @@ int main(const int argc, char *const argv[], char *const *const envp)\n \t\t\t  LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);\n \t\t/* Removes quiet flags for ABI \u003c 10 later on. */\n \t\tquiet_supported = false;\n-\n+\t\t__attribute__((fallthrough));\n+\tcase 10:\n+\t\t/* Removes LANDLOCK_PERM_* for ABI \u003c 11 */\n+\t\truleset_attr.handled_perm \u0026= ~(LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\t\t       LANDLOCK_PERM_CAPABILITY_USE);\n \t\t/* Must be printed for any ABI \u003c LANDLOCK_ABI_LAST. */\n \t\tfprintf(stderr,\n \t\t\t\"Hint: You should update the running kernel \"\n@@ -590,6 +770,14 @@ int main(const int argc, char *const argv[], char *const *const envp)\n \t\t\t~LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;\n \t}\n \n+\t/* Removes capability handling if not set by a user. */\n+\tif (!getenv(ENV_CAP_NAME) \u0026\u0026 !getenv(ENV_CAP_QUIET_NAME))\n+\t\truleset_attr.handled_perm \u0026= ~LANDLOCK_PERM_CAPABILITY_USE;\n+\n+\t/* Removes namespace handling if not set by a user. */\n+\tif (!getenv(ENV_NS_NAME) \u0026\u0026 !getenv(ENV_NS_QUIET_NAME))\n+\t\truleset_attr.handled_perm \u0026= ~LANDLOCK_PERM_NAMESPACE_USE;\n+\n \tif (check_ruleset_scope(ENV_SCOPED_NAME, \u0026ruleset_attr))\n \t\treturn 1;\n \n@@ -673,6 +861,12 @@ int main(const int argc, char *const argv[], char *const *const envp)\n \t\t}\n \t}\n \n+\tif (populate_ruleset_cap(ENV_CAP_NAME, ENV_CAP_QUIET_NAME, ruleset_fd))\n+\t\tgoto err_close_ruleset;\n+\n+\tif (populate_ruleset_ns(ENV_NS_NAME, ENV_NS_QUIET_NAME, ruleset_fd))\n+\t\tgoto err_close_ruleset;\n+\n \tif (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {\n \t\tperror(\"Failed to restrict privileges\");\n \t\tgoto err_close_ruleset;\ndiff --git a/security/landlock/Makefile b/security/landlock/Makefile\nindex ffa7646d99f3ef..1927b81fea93d7 100644\n--- a/security/landlock/Makefile\n+++ b/security/landlock/Makefile\n@@ -8,7 +8,9 @@ landlock-y := \\\n \tcred.o \\\n \ttask.o \\\n \tfs.o \\\n-\ttsync.o\n+\ttsync.o \\\n+\tns.o \\\n+\tcap.o\n \n landlock-$(CONFIG_INET) += net.o\n \ndiff --git a/security/landlock/access.h b/security/landlock/access.h\nindex d926078bf0a5f5..cb456eee8c32ff 100644\n--- a/security/landlock/access.h\n+++ b/security/landlock/access.h\n@@ -19,9 +19,9 @@\n \n /*\n  * All access rights that are denied by default whether they are handled or not\n- * by a ruleset/layer.  This must be ORed with all ruleset-\u003eaccess_masks[]\n- * entries when we need to get the absolute handled access masks, see\n- * landlock_upgrade_handled_access_masks().\n+ * by a ruleset/layer.  This must be ORed with the .handled field of all\n+ * ruleset-\u003elayers[] entries when we need to get the absolute handled access\n+ * masks, see landlock_upgrade_handled_layer_config().\n  */\n /* clang-format off */\n #define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \\\n@@ -42,14 +42,17 @@ static_assert(BITS_PER_TYPE(access_mask_t) \u003e= LANDLOCK_NUM_ACCESS_FS);\n static_assert(BITS_PER_TYPE(access_mask_t) \u003e= LANDLOCK_NUM_ACCESS_NET);\n /* Makes sure all scoped rights can be stored. */\n static_assert(BITS_PER_TYPE(access_mask_t) \u003e= LANDLOCK_NUM_SCOPE);\n+/* Makes sure all permission types can be stored. */\n+static_assert(BITS_PER_TYPE(access_mask_t) \u003e= LANDLOCK_NUM_PERM);\n /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */\n static_assert(sizeof(unsigned long) \u003e= sizeof(access_mask_t));\n \n-/* Ruleset access masks. */\n+/* Handled access masks (bitfields only). */\n struct access_masks {\n \taccess_mask_t fs : LANDLOCK_NUM_ACCESS_FS;\n \taccess_mask_t net : LANDLOCK_NUM_ACCESS_NET;\n \taccess_mask_t scope : LANDLOCK_NUM_SCOPE;\n+\taccess_mask_t perm : LANDLOCK_NUM_PERM;\n } __packed __aligned(sizeof(u32));\n \n union access_masks_all {\n@@ -61,6 +64,62 @@ union access_masks_all {\n static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==\n \t      sizeof(typeof_member(union access_masks_all, all)));\n \n+/**\n+ * struct perm_masks - Per-layer allowed bitmasks for permission types\n+ *\n+ * Compact bitfield struct holding the allowed bitmasks for permission types\n+ * that use flat (non-tree) per-layer storage.  All fields share a single 64-bit\n+ * storage unit.\n+ */\n+struct perm_masks {\n+\t/**\n+\t * @caps: Allowed capabilities.  Each bit corresponds to a ``CAP_*``\n+\t * value (e.g. ``CAP_NET_RAW`` = bit 13).  Bits are stored directly\n+\t * (sequential mapping) and masked with ``CAP_VALID_MASK`` at rule-add\n+\t * time.\n+\t */\n+\tu64 caps : LANDLOCK_NUM_PERM_CAP;\n+\t/**\n+\t * @ns: Allowed namespace types.  Each bit corresponds to a sequential\n+\t * index assigned by the ``_LANDLOCK_NS_*`` enum (derived from\n+\t * ``FOR_EACH_NS_TYPE``).  Bits are converted from ``CLONE_NEW*`` flags\n+\t * at rule-add time via ``landlock_ns_types_to_bits()`` and at\n+\t * enforcement time via ``landlock_ns_type_to_bit()``.\n+\t */\n+\tu64 ns : LANDLOCK_NUM_PERM_NS;\n+} __packed __aligned(sizeof(u64));\n+\n+static_assert(sizeof(struct perm_masks) == sizeof(u64));\n+/* All perm_masks bitfields must fit in a single u64. */\n+static_assert(LANDLOCK_NUM_PERM_CAP + LANDLOCK_NUM_PERM_NS \u003c=\n+\t      BITS_PER_TYPE(u64));\n+\n+/**\n+ * struct layer_config - Per-layer access configuration\n+ *\n+ * Wraps the handled-access bitfields together with per-layer allowed bitmasks.\n+ * This is the element type of the \u0026struct landlock_ruleset.layers FAM.\n+ *\n+ * Unlike filesystem and network access rights, which are tracked per-object in\n+ * red-black trees, namespace types and capabilities use flat bitmasks because\n+ * their keyspaces are small and bounded (~8 namespace types, 41 capabilities).\n+ * A single rule adds to the allowed set via bitwise OR; at enforcement time\n+ * each layer is checked directly (no tree lookup needed).\n+ */\n+struct layer_config {\n+\t/**\n+\t * @allowed: Per-layer allowed bitmasks for permission types.  Placed\n+\t * before @handled so the wider, more-aligned member comes first,\n+\t * avoiding internal padding.\n+\t */\n+\tstruct perm_masks allowed;\n+\t/**\n+\t * @handled: Bitmask of access rights handled (i.e. restricted) by this\n+\t * layer.\n+\t */\n+\tstruct access_masks handled;\n+};\n+\n /**\n  * struct layer_mask - The access rights and rule flags for a layer.\n  *\n@@ -123,17 +182,17 @@ static_assert(BITS_PER_TYPE(deny_masks_t) \u003e=\n static_assert(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS) == 1);\n \n /* Upgrades with all initially denied by default access rights. */\n-static inline struct access_masks\n-landlock_upgrade_handled_access_masks(struct access_masks access_masks)\n+static inline struct layer_config\n+landlock_upgrade_handled_layer_config(struct layer_config layer_config)\n {\n \t/*\n \t * All access rights that are denied by default whether they are\n \t * explicitly handled or not.\n \t */\n-\tif (access_masks.fs)\n-\t\taccess_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;\n+\tif (layer_config.handled.fs)\n+\t\tlayer_config.handled.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;\n \n-\treturn access_masks;\n+\treturn layer_config;\n }\n \n /* Checks the subset relation between access masks. */\ndiff --git a/security/landlock/audit.c b/security/landlock/audit.c\nindex 50536c5685262e..19a8f462678fe4 100644\n--- a/security/landlock/audit.c\n+++ b/security/landlock/audit.c\n@@ -14,10 +14,12 @@\n \n #include \"access.h\"\n #include \"audit.h\"\n+#include \"cap.h\"\n #include \"common.h\"\n #include \"cred.h\"\n #include \"domain.h\"\n #include \"limits.h\"\n+#include \"ns.h\"\n #include \"ruleset.h\"\n \n static const char *const fs_access_strings[] = {\n@@ -82,6 +84,14 @@ get_blocker(const enum landlock_request_type type,\n \tcase LANDLOCK_REQUEST_SCOPE_SIGNAL:\n \t\tWARN_ON_ONCE(access_bit != -1);\n \t\treturn \"scope.signal\";\n+\n+\tcase LANDLOCK_REQUEST_NAMESPACE:\n+\t\tWARN_ON_ONCE(access_bit != -1);\n+\t\treturn \"perm.namespace_use\";\n+\n+\tcase LANDLOCK_REQUEST_CAPABILITY:\n+\t\tWARN_ON_ONCE(access_bit != -1);\n+\t\treturn \"perm.capability_use\";\n \t}\n \n \tWARN_ON_ONCE(1);\n@@ -625,7 +635,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,\n \t\t */\n \t\tconst access_mask_t quiet_mask =\n \t\t\tpick_access_mask_for_request_type(\n-\t\t\t\trequest-\u003etype, youngest_denied-\u003equiet_masks);\n+\t\t\t\trequest-\u003etype, youngest_denied-\u003equiet_access);\n \n \t\tquiet_applicable_to_access = (quiet_mask \u0026 missing) == missing;\n \t} else {\n@@ -634,7 +644,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,\n \t\t * We check request-\u003etype to distinguish between the two cases.\n \t\t */\n \t\tconst access_mask_t quiet_mask =\n-\t\t\tyoungest_denied-\u003equiet_masks.scope;\n+\t\t\tyoungest_denied-\u003equiet_access.scope;\n \n \t\tswitch (request-\u003etype) {\n \t\tcase LANDLOCK_REQUEST_SCOPE_SIGNAL:\n@@ -646,6 +656,23 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,\n \t\t\t\t!!(quiet_mask \u0026\n \t\t\t\t   LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);\n \t\t\tbreak;\n+\t\t/*\n+\t\t * Capability and namespace denials are per-member (single\n+\t\t * axis): the denied member is quiet when its bit is set in the\n+\t\t * denying layer's quiet_perm mask.  The member is recovered\n+\t\t * from the audit data recorded by the hook.\n+\t\t */\n+\t\tcase LANDLOCK_REQUEST_CAPABILITY:\n+\t\t\tquiet_applicable_to_access =\n+\t\t\t\t!!(youngest_denied-\u003equiet_perm.caps \u0026\n+\t\t\t\t   landlock_cap_to_bit(request-\u003eaudit.u.cap));\n+\t\t\tbreak;\n+\t\tcase LANDLOCK_REQUEST_NAMESPACE:\n+\t\t\tquiet_applicable_to_access =\n+\t\t\t\t!!(youngest_denied-\u003equiet_perm.ns \u0026\n+\t\t\t\t   landlock_ns_type_to_bit(\n+\t\t\t\t\t   request-\u003eaudit.u.ns.ns_type));\n+\t\t\tbreak;\n \t\t/*\n \t\t * Leave LANDLOCK_REQUEST_PTRACE and\n \t\t * LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they\ndiff --git a/security/landlock/audit.h b/security/landlock/audit.h\nindex 620f8a24291d5d..1e00b12d08a7df 100644\n--- a/security/landlock/audit.h\n+++ b/security/landlock/audit.h\n@@ -21,6 +21,8 @@ enum landlock_request_type {\n \tLANDLOCK_REQUEST_NET_ACCESS,\n \tLANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,\n \tLANDLOCK_REQUEST_SCOPE_SIGNAL,\n+\tLANDLOCK_REQUEST_NAMESPACE,\n+\tLANDLOCK_REQUEST_CAPABILITY,\n };\n \n /*\ndiff --git a/security/landlock/cap.c b/security/landlock/cap.c\nnew file mode 100644\nindex 00000000000000..d8bdbaea444056\n--- /dev/null\n+++ b/security/landlock/cap.c\n@@ -0,0 +1,137 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Landlock - Capability hooks\n+ *\n+ * Copyright © 2026 Cloudflare, Inc.\n+ */\n+\n+#include \u003clinux/capability.h\u003e\n+#include \u003clinux/cred.h\u003e\n+#include \u003clinux/lsm_audit.h\u003e\n+#include \u003clinux/lsm_hooks.h\u003e\n+#include \u003cuapi/linux/landlock.h\u003e\n+\n+#include \"audit.h\"\n+#include \"cap.h\"\n+#include \"cred.h\"\n+#include \"limits.h\"\n+#include \"ruleset.h\"\n+#include \"setup.h\"\n+\n+static const struct access_masks cap_perm = {\n+\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+};\n+\n+/**\n+ * hook_capable - Deny capability use for Landlock-sandboxed processes\n+ *\n+ * @cred: Credentials being checked.\n+ * @ns: User namespace for the capability check.\n+ * @cap: Capability number (CAP_*).\n+ * @opts: Capability check options.  CAP_OPT_NOAUDIT suppresses audit logging.\n+ *\n+ * Pure bitmask check: denies the capability if it is not in the layer's allowed\n+ * set.  This hook is purely restrictive: commoncap is registered with\n+ * LSM_ORDER_FIRST so cap_capable() always runs first, which means Landlock can\n+ * deny capabilities that commoncap would allow, but never grant capabilities\n+ * that commoncap denied.\n+ *\n+ * Return: 0 if allowed, -EPERM if capability use is denied.\n+ */\n+static int hook_capable(const struct cred *cred, struct user_namespace *ns,\n+\t\t\tint cap, unsigned int opts)\n+{\n+\tconst struct landlock_cred_security *subject;\n+\tsize_t denied_layer;\n+\n+\tsubject = landlock_get_applicable_subject(cred, cap_perm, NULL);\n+\tif (!subject)\n+\t\treturn 0;\n+\n+\tdenied_layer = landlock_perm_is_denied(subject-\u003edomain,\n+\t\t\t\t\t       LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t\t\t\t       landlock_cap_to_bit(cap));\n+\tif (!denied_layer)\n+\t\treturn 0;\n+\n+\tif (!(opts \u0026 CAP_OPT_NOAUDIT))\n+\t\tlandlock_log_denial(subject,\n+\t\t\t\t    \u0026(struct landlock_request){\n+\t\t\t\t\t    .type = LANDLOCK_REQUEST_CAPABILITY,\n+\t\t\t\t\t    .audit.type = LSM_AUDIT_DATA_CAP,\n+\t\t\t\t\t    .audit.u.cap = cap,\n+\t\t\t\t\t    .layer_plus_one = denied_layer,\n+\t\t\t\t    });\n+\n+\treturn -EPERM;\n+}\n+\n+static struct security_hook_list landlock_hooks[] __ro_after_init = {\n+\tLSM_HOOK_INIT(capable, hook_capable),\n+};\n+\n+__init void landlock_add_cap_hooks(void)\n+{\n+\tsecurity_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),\n+\t\t\t   \u0026landlock_lsmid);\n+}\n+\n+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST\n+\n+#include \u003ckunit/test.h\u003e\n+\n+static void test_cap_to_bit(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, BIT_ULL(0), landlock_cap_to_bit(0));\n+\tKUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW),\n+\t\t\tlandlock_cap_to_bit(CAP_NET_RAW));\n+\tKUNIT_EXPECT_EQ(test, BIT_ULL(CAP_SYS_ADMIN),\n+\t\t\tlandlock_cap_to_bit(CAP_SYS_ADMIN));\n+\tKUNIT_EXPECT_EQ(test, BIT_ULL(CAP_LAST_CAP),\n+\t\t\tlandlock_cap_to_bit(CAP_LAST_CAP));\n+}\n+\n+static void test_cap_to_bit_invalid(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, 0ULL, landlock_cap_to_bit(-1));\n+\tKUNIT_EXPECT_EQ(test, 0ULL, landlock_cap_to_bit(CAP_LAST_CAP + 1));\n+}\n+\n+static void test_caps_to_bits_valid(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, (u64)CAP_VALID_MASK,\n+\t\t\tlandlock_caps_to_bits(CAP_VALID_MASK));\n+\tKUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW),\n+\t\t\tlandlock_caps_to_bits(BIT_ULL(CAP_NET_RAW)));\n+}\n+\n+static void test_caps_to_bits_unknown(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, 0ULL,\n+\t\t\tlandlock_caps_to_bits(BIT_ULL(CAP_LAST_CAP + 1)));\n+}\n+\n+static void test_caps_to_bits_zero(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, 0ULL, landlock_caps_to_bits(0));\n+}\n+\n+static struct kunit_case test_cases[] = {\n+\t/* clang-format off */\n+\tKUNIT_CASE(test_cap_to_bit),\n+\tKUNIT_CASE(test_cap_to_bit_invalid),\n+\tKUNIT_CASE(test_caps_to_bits_valid),\n+\tKUNIT_CASE(test_caps_to_bits_unknown),\n+\tKUNIT_CASE(test_caps_to_bits_zero),\n+\t{}\n+\t/* clang-format on */\n+};\n+\n+static struct kunit_suite test_suite = {\n+\t.name = \"landlock_cap\",\n+\t.test_cases = test_cases,\n+};\n+\n+kunit_test_suite(test_suite);\n+\n+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */\ndiff --git a/security/landlock/cap.h b/security/landlock/cap.h\nnew file mode 100644\nindex 00000000000000..3f12fd7d978822\n--- /dev/null\n+++ b/security/landlock/cap.h\n@@ -0,0 +1,50 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Landlock - Capability hooks\n+ *\n+ * Copyright © 2026 Cloudflare, Inc.\n+ */\n+\n+#ifndef _SECURITY_LANDLOCK_CAP_H\n+#define _SECURITY_LANDLOCK_CAP_H\n+\n+#include \u003clinux/bitops.h\u003e\n+#include \u003clinux/bug.h\u003e\n+#include \u003clinux/capability.h\u003e\n+#include \u003clinux/compiler_attributes.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+/**\n+ * landlock_cap_to_bit - Convert a capability number to a compact bitmask\n+ *\n+ * @cap: Capability number (CAP_*).\n+ *\n+ * Return: BIT_ULL(@cap), or 0 if @cap is invalid (with a WARN).\n+ */\n+static inline __attribute_const__ u64 landlock_cap_to_bit(const int cap)\n+{\n+\tif (WARN_ON_ONCE(!cap_valid(cap)))\n+\t\treturn 0;\n+\n+\treturn BIT_ULL(cap);\n+}\n+\n+/**\n+ * landlock_caps_to_bits - Validate and mask a capability bitmask\n+ *\n+ * @capabilities: Bitmask of capabilities (e.g. from user space).\n+ *\n+ * Return: @capabilities masked to known capabilities.  Warns if unknown bits\n+ * are present (callers must pre-mask for user input).\n+ */\n+static inline __attribute_const__ u64\n+landlock_caps_to_bits(const u64 capabilities)\n+{\n+\t/* Callers pre-mask (CAP_VALID_MASK); the WARN guards future callers. */\n+\tWARN_ON_ONCE(capabilities \u0026 ~CAP_VALID_MASK);\n+\treturn capabilities \u0026 CAP_VALID_MASK;\n+}\n+\n+__init void landlock_add_cap_hooks(void);\n+\n+#endif /* _SECURITY_LANDLOCK_CAP_H */\ndiff --git a/security/landlock/cred.h b/security/landlock/cred.h\nindex f287c56b5fd4b2..35d6bfac88997e 100644\n--- a/security/landlock/cred.h\n+++ b/security/landlock/cred.h\n@@ -139,7 +139,7 @@ landlock_get_applicable_subject(const struct cred *const cred,\n \tfor (layer_level = domain-\u003enum_layers - 1; layer_level \u003e= 0;\n \t     layer_level--) {\n \t\tunion access_masks_all layer = {\n-\t\t\t.masks = domain-\u003eaccess_masks[layer_level],\n+\t\t\t.masks = domain-\u003elayers[layer_level].handled,\n \t\t};\n \n \t\tif (layer.all \u0026 masks_all.all) {\n@@ -153,6 +153,59 @@ landlock_get_applicable_subject(const struct cred *const cred,\n \treturn NULL;\n }\n \n+/**\n+ * landlock_perm_is_denied - Check if a permission bitmask request is denied\n+ *\n+ * @domain: The enforced domain.\n+ * @perm_bit: The LANDLOCK_PERM_* flag to check.  Must have exactly one\n+ *            bit set.\n+ * @request_value: Compact bitmask to look for (e.g. result of\n+ *                 `landlock_ns_type_to_bit(CLONE_NEWNET)`).  Must have\n+ *                 exactly one bit set.\n+ *\n+ * Iterate from the youngest layer to the oldest.  For each layer that handles\n+ * @perm_bit, check whether @request_value is present in the layer's allowed\n+ * bitmask.  Return on the first (youngest) denying layer.\n+ *\n+ * Return: The youngest denying layer + 1, or 0 if allowed.\n+ */\n+static inline size_t\n+landlock_perm_is_denied(const struct landlock_ruleset *const domain,\n+\t\t\tconst access_mask_t perm_bit, const u64 request_value)\n+{\n+\tssize_t layer;\n+\n+\tBUILD_BUG_ON(sizeof(perm_bit) \u003e sizeof(u32));\n+\n+\tif (WARN_ON_ONCE(hweight32(perm_bit) != 1) ||\n+\t    WARN_ON_ONCE(hweight64(request_value) != 1))\n+\t\treturn domain-\u003enum_layers;\n+\n+\tfor (layer = domain-\u003enum_layers - 1; layer \u003e= 0; layer--) {\n+\t\tu64 allowed;\n+\n+\t\tif (!(domain-\u003elayers[layer].handled.perm \u0026 perm_bit))\n+\t\t\tcontinue;\n+\n+\t\tswitch (perm_bit) {\n+\t\tcase LANDLOCK_PERM_NAMESPACE_USE:\n+\t\t\tallowed = domain-\u003elayers[layer].allowed.ns;\n+\t\t\tbreak;\n+\t\tcase LANDLOCK_PERM_CAPABILITY_USE:\n+\t\t\tallowed = domain-\u003elayers[layer].allowed.caps;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tWARN_ONCE(1, \"Unknown permission %u\\n\",\n+\t\t\t\t  (unsigned int)perm_bit);\n+\t\t\treturn layer + 1;\n+\t\t}\n+\n+\t\tif (!(allowed \u0026 request_value))\n+\t\t\treturn layer + 1;\n+\t}\n+\treturn 0;\n+}\n+\n __init void landlock_add_cred_hooks(void);\n \n #endif /* _SECURITY_LANDLOCK_CRED_H */\ndiff --git a/security/landlock/domain.h b/security/landlock/domain.h\nindex 2a1660e3dea7e0..c2b72795b0d624 100644\n--- a/security/landlock/domain.h\n+++ b/security/landlock/domain.h\n@@ -112,10 +112,16 @@ struct landlock_hierarchy {\n \t\t */\n \t\tlog_new_exec : 1;\n \t/**\n-\t * @quiet_masks: Bitmasks of access that should be quieted (i.e. not\n+\t * @quiet_access: Bitmasks of access that should be quieted (i.e. not\n \t * logged) if the related object is marked as quiet.\n \t */\n-\tstruct access_masks quiet_masks;\n+\tstruct access_masks quiet_access;\n+\t/**\n+\t * @quiet_perm: Per-member quiet bitmasks for permission types\n+\t * (capabilities and namespace types).  A member denied by this layer is\n+\t * not logged when its bit is set here.\n+\t */\n+\tstruct perm_masks quiet_perm;\n #endif /* CONFIG_AUDIT */\n };\n \ndiff --git a/security/landlock/limits.h b/security/landlock/limits.h\nindex 08d5f2f6d321a7..6cdd649005f48b 100644\n--- a/security/landlock/limits.h\n+++ b/security/landlock/limits.h\n@@ -11,7 +11,9 @@\n #define _SECURITY_LANDLOCK_LIMITS_H\n \n #include \u003clinux/bitops.h\u003e\n+#include \u003clinux/capability.h\u003e\n #include \u003clinux/limits.h\u003e\n+#include \u003clinux/ns/ns_common_types.h\u003e\n #include \u003cuapi/linux/landlock.h\u003e\n \n /* clang-format off */\n@@ -31,6 +33,13 @@\n #define LANDLOCK_MASK_SCOPE\t\t((LANDLOCK_LAST_SCOPE \u003c\u003c 1) - 1)\n #define LANDLOCK_NUM_SCOPE\t\t__const_hweight64(LANDLOCK_MASK_SCOPE)\n \n+#define LANDLOCK_LAST_PERM\t\tLANDLOCK_PERM_CAPABILITY_USE\n+#define LANDLOCK_MASK_PERM\t\t((LANDLOCK_LAST_PERM \u003c\u003c 1) - 1)\n+#define LANDLOCK_NUM_PERM\t\t__const_hweight64(LANDLOCK_MASK_PERM)\n+\n+#define LANDLOCK_NUM_PERM_NS\t\t__const_hweight64((u64)(CLONE_NS_ALL))\n+#define LANDLOCK_NUM_PERM_CAP\t\t(CAP_LAST_CAP + 1)\n+\n #define LANDLOCK_NUM_ACCESS_MAX \\\n \tMAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE)\n \ndiff --git a/security/landlock/ns.c b/security/landlock/ns.c\nnew file mode 100644\nindex 00000000000000..9d704b0ec7fffc\n--- /dev/null\n+++ b/security/landlock/ns.c\n@@ -0,0 +1,156 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Landlock - Namespace hooks\n+ *\n+ * Copyright © 2026 Cloudflare, Inc.\n+ */\n+\n+#include \u003clinux/lsm_audit.h\u003e\n+#include \u003clinux/lsm_hooks.h\u003e\n+#include \u003clinux/ns/ns_common_types.h\u003e\n+#include \u003clinux/ns_common.h\u003e\n+#include \u003clinux/nsproxy.h\u003e\n+#include \u003cuapi/linux/landlock.h\u003e\n+\n+#include \"audit.h\"\n+#include \"cred.h\"\n+#include \"limits.h\"\n+#include \"ns.h\"\n+#include \"ruleset.h\"\n+#include \"setup.h\"\n+\n+/* Ensures the audit ns_id field can hold ns_common.ns_id without truncation. */\n+static_assert(sizeof(((struct common_audit_data *)NULL)-\u003eu.ns.ns_id) \u003e=\n+\t      sizeof(((struct ns_common *)NULL)-\u003ens_id));\n+\n+static const struct access_masks ns_perm = {\n+\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+};\n+\n+/**\n+ * check_ns_type - Check namespace entry permission\n+ *\n+ * @ns: The namespace being allocated or installed.\n+ *\n+ * Shared check for namespace_init (creation via unshare(2) or clone(2)) and\n+ * namespace_install (entry via setns(2)): denies when the namespace type is not\n+ * in the domain's allowed set.  At allocation time @ns-\u003ens_id is still zero and\n+ * is logged as such.\n+ *\n+ * Return: 0 if allowed, -EPERM if denied.\n+ */\n+static int check_ns_type(struct ns_common *const ns)\n+{\n+\tconst struct landlock_cred_security *subject;\n+\tsize_t denied_layer;\n+\n+\tsubject =\n+\t\tlandlock_get_applicable_subject(current_cred(), ns_perm, NULL);\n+\tif (!subject)\n+\t\treturn 0;\n+\n+\tdenied_layer = landlock_perm_is_denied(\n+\t\tsubject-\u003edomain, LANDLOCK_PERM_NAMESPACE_USE,\n+\t\tlandlock_ns_type_to_bit(ns-\u003ens_type));\n+\tif (!denied_layer)\n+\t\treturn 0;\n+\n+\tlandlock_log_denial(subject, \u0026(struct landlock_request){\n+\t\t\t\t\t     .type = LANDLOCK_REQUEST_NAMESPACE,\n+\t\t\t\t\t     .audit.type = LSM_AUDIT_DATA_NS,\n+\t\t\t\t\t     .audit.u.ns.ns_type = ns-\u003ens_type,\n+\t\t\t\t\t     .audit.u.ns.ns_id = ns-\u003ens_id,\n+\t\t\t\t\t     .layer_plus_one = denied_layer,\n+\t\t\t\t     });\n+\treturn -EPERM;\n+}\n+\n+static int hook_namespace_init(struct ns_common *const ns)\n+{\n+\treturn check_ns_type(ns);\n+}\n+\n+static int hook_namespace_install(const struct nsset *const nsset,\n+\t\t\t\t  struct ns_common *const ns)\n+{\n+\treturn check_ns_type(ns);\n+}\n+\n+static struct security_hook_list landlock_hooks[] __ro_after_init = {\n+\tLSM_HOOK_INIT(namespace_init, hook_namespace_init),\n+\tLSM_HOOK_INIT(namespace_install, hook_namespace_install),\n+};\n+\n+__init void landlock_add_ns_hooks(void)\n+{\n+\tsecurity_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),\n+\t\t\t   \u0026landlock_lsmid);\n+}\n+\n+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST\n+\n+#include \u003ckunit/test.h\u003e\n+\n+/* clang-format off */\n+#define _TEST_NS_BIT(struct_name, flag) \\\n+\tdo { \\\n+\t\tconst u64 bit = landlock_ns_type_to_bit(flag); \\\n+\t\tKUNIT_EXPECT_NE(test, 0ULL, bit); \\\n+\t\tKUNIT_EXPECT_EQ(test, 0ULL, seen \u0026 bit); \\\n+\t\tseen |= bit; \\\n+\t} while (0);\n+/* clang-format on */\n+\n+static void test_ns_type_to_bit(struct kunit *const test)\n+{\n+\tu64 seen = 0;\n+\n+\tFOR_EACH_NS_TYPE(_TEST_NS_BIT)\n+\n+\tKUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_PERM_NS - 1, 0), seen);\n+}\n+\n+static void test_ns_type_to_bit_unknown(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, 0ULL, landlock_ns_type_to_bit(CLONE_THREAD));\n+}\n+\n+static void test_ns_types_to_bits_all(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_PERM_NS - 1, 0),\n+\t\t\tlandlock_ns_types_to_bits(CLONE_NS_ALL));\n+}\n+\n+/* clang-format off */\n+#define _TEST_NS_SINGLE(struct_name, flag) \\\n+\tKUNIT_EXPECT_EQ(test, landlock_ns_type_to_bit(flag), \\\n+\t\t\tlandlock_ns_types_to_bits(flag));\n+/* clang-format on */\n+\n+static void test_ns_types_to_bits_single(struct kunit *const test)\n+{\n+\tFOR_EACH_NS_TYPE(_TEST_NS_SINGLE)\n+}\n+\n+static void test_ns_types_to_bits_zero(struct kunit *const test)\n+{\n+\tKUNIT_EXPECT_EQ(test, 0ULL, landlock_ns_types_to_bits(0));\n+}\n+\n+static struct kunit_case test_cases[] = {\n+\tKUNIT_CASE(test_ns_type_to_bit),\n+\tKUNIT_CASE(test_ns_type_to_bit_unknown),\n+\tKUNIT_CASE(test_ns_types_to_bits_all),\n+\tKUNIT_CASE(test_ns_types_to_bits_single),\n+\tKUNIT_CASE(test_ns_types_to_bits_zero),\n+\t{}\n+};\n+\n+static struct kunit_suite test_suite = {\n+\t.name = \"landlock_ns\",\n+\t.test_cases = test_cases,\n+};\n+\n+kunit_test_suite(test_suite);\n+\n+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */\ndiff --git a/security/landlock/ns.h b/security/landlock/ns.h\nnew file mode 100644\nindex 00000000000000..d78b36d8ce8239\n--- /dev/null\n+++ b/security/landlock/ns.h\n@@ -0,0 +1,74 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Landlock - Namespace hooks\n+ *\n+ * Copyright © 2026 Cloudflare, Inc.\n+ */\n+\n+#ifndef _SECURITY_LANDLOCK_NS_H\n+#define _SECURITY_LANDLOCK_NS_H\n+\n+#include \u003clinux/bitops.h\u003e\n+#include \u003clinux/bug.h\u003e\n+#include \u003clinux/compiler_attributes.h\u003e\n+#include \u003clinux/ns/ns_common_types.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#include \"limits.h\"\n+\n+/* _LANDLOCK_NS_CLONE_NEWCGROUP, */\n+#define _LANDLOCK_NS_ENUM(struct_name, flag) _LANDLOCK_NS_##flag,\n+\n+/* _LANDLOCK_NS_CLONE_NEWCGROUP = 0, */\n+enum {\n+\tFOR_EACH_NS_TYPE(_LANDLOCK_NS_ENUM) _LANDLOCK_NUM_NS_TYPES,\n+};\n+\n+static_assert(_LANDLOCK_NUM_NS_TYPES == LANDLOCK_NUM_PERM_NS);\n+\n+/*\n+ * case CLONE_NEWCGROUP:\n+ *         return BIT_ULL(_LANDLOCK_NS_CLONE_NEWCGROUP);\n+ */\n+/* clang-format off */\n+#define _LANDLOCK_NS_CASE(struct_name, flag) \\\n+\tcase flag: \\\n+\t\treturn BIT_ULL(_LANDLOCK_NS_##flag);\n+/* clang-format on */\n+\n+static inline __attribute_const__ u64\n+landlock_ns_type_to_bit(const unsigned long ns_type)\n+{\n+\tswitch (ns_type) {\n+\t\tFOR_EACH_NS_TYPE(_LANDLOCK_NS_CASE)\n+\t}\n+\tWARN_ONCE(1, \"Unknown namespace type 0x%lx\\n\", ns_type);\n+\treturn 0;\n+}\n+\n+/*\n+ * if (ns_types \u0026 CLONE_NEWCGROUP)\n+ *         bits |= BIT_ULL(_LANDLOCK_NS_CLONE_NEWCGROUP);\n+ */\n+/* clang-format off */\n+#define _LANDLOCK_NS_CONVERT(struct_name, flag) \\\n+\tdo { \\\n+\t\tif (ns_types \u0026 (flag)) \\\n+\t\t\tbits |= BIT_ULL(_LANDLOCK_NS_##flag); \\\n+\t} while (0);\n+/* clang-format on */\n+\n+static inline __attribute_const__ u64\n+landlock_ns_types_to_bits(const u64 ns_types)\n+{\n+\tu64 bits = 0;\n+\n+\t/* Callers pre-mask (CLONE_NS_ALL); the WARN guards future callers. */\n+\tWARN_ON_ONCE(ns_types \u0026 ~CLONE_NS_ALL);\n+\tFOR_EACH_NS_TYPE(_LANDLOCK_NS_CONVERT)\n+\treturn bits;\n+}\n+\n+__init void landlock_add_ns_hooks(void);\n+\n+#endif /* _SECURITY_LANDLOCK_NS_H */\ndiff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c\nindex 4dd09ea22c8444..45687f348bc547 100644\n--- a/security/landlock/ruleset.c\n+++ b/security/landlock/ruleset.c\n@@ -33,7 +33,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)\n {\n \tstruct landlock_ruleset *new_ruleset;\n \n-\tnew_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers,\n+\tnew_ruleset = kzalloc_flex(*new_ruleset, layers, num_layers,\n \t\t\t\t   GFP_KERNEL_ACCOUNT);\n \tif (!new_ruleset)\n \t\treturn ERR_PTR(-ENOMEM);\n@@ -47,22 +47,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)\n \n \tnew_ruleset-\u003enum_layers = num_layers;\n \t/*\n-\t * hierarchy = NULL\n-\t * num_rules = 0\n-\t * access_masks[] = 0\n+\t * - hierarchy = NULL\n+\t * - num_rules = 0\n+\t * - layers[] = 0\n \t */\n \treturn new_ruleset;\n }\n \n-struct landlock_ruleset *\n-landlock_create_ruleset(const access_mask_t fs_access_mask,\n-\t\t\tconst access_mask_t net_access_mask,\n-\t\t\tconst access_mask_t scope_mask)\n+struct landlock_ruleset *landlock_create_ruleset(\n+\tconst access_mask_t fs_access_mask, const access_mask_t net_access_mask,\n+\tconst access_mask_t scope_mask, const access_mask_t perm_mask)\n {\n \tstruct landlock_ruleset *new_ruleset;\n \n \t/* Informs about useless ruleset. */\n-\tif (!fs_access_mask \u0026\u0026 !net_access_mask \u0026\u0026 !scope_mask)\n+\tif (!fs_access_mask \u0026\u0026 !net_access_mask \u0026\u0026 !scope_mask \u0026\u0026 !perm_mask)\n \t\treturn ERR_PTR(-ENOMSG);\n \tnew_ruleset = create_ruleset(1);\n \tif (IS_ERR(new_ruleset))\n@@ -73,6 +72,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,\n \t\tlandlock_add_net_access_mask(new_ruleset, net_access_mask, 0);\n \tif (scope_mask)\n \t\tlandlock_add_scope_mask(new_ruleset, scope_mask, 0);\n+\tif (perm_mask)\n+\t\tlandlock_add_perm_mask(new_ruleset, perm_mask, 0);\n \treturn new_ruleset;\n }\n \n@@ -387,8 +388,8 @@ static int merge_ruleset(struct landlock_ruleset *const dst,\n \t\terr = -EINVAL;\n \t\tgoto out_unlock;\n \t}\n-\tdst-\u003eaccess_masks[dst-\u003enum_layers - 1] =\n-\t\tlandlock_upgrade_handled_access_masks(src-\u003eaccess_masks[0]);\n+\tdst-\u003elayers[dst-\u003enum_layers - 1] =\n+\t\tlandlock_upgrade_handled_layer_config(src-\u003elayers[0]);\n \n \t/* Merges the @src inode tree. */\n \terr = merge_tree(dst, src, LANDLOCK_KEY_INODE);\n@@ -402,6 +403,18 @@ static int merge_ruleset(struct landlock_ruleset *const dst,\n \t\tgoto out_unlock;\n #endif /* IS_ENABLED(CONFIG_INET) */\n \n+#ifdef CONFIG_AUDIT\n+\tdst-\u003ehierarchy-\u003equiet_access = src-\u003equiet_access;\n+\t/*\n+\t * quiet_perm is accumulated per rule by landlock_add_rule() under\n+\t * ruleset-\u003elock, so it must be snapshotted here, in the same critical\n+\t * section as the allowed mask; a later, separate copy would race\n+\t * landlock_add_rule() and split the snapshot.  The immutable\n+\t * quiet_access above shares this site for consistency.\n+\t */\n+\tdst-\u003ehierarchy-\u003equiet_perm = src-\u003equiet_perm;\n+#endif /* CONFIG_AUDIT */\n+\n out_unlock:\n \tmutex_unlock(\u0026src-\u003elock);\n \tmutex_unlock(\u0026dst-\u003elock);\n@@ -470,8 +483,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,\n \t\tgoto out_unlock;\n \t}\n \t/* Copies the parent layer stack and leaves a space for the new layer. */\n-\tmemcpy(child-\u003eaccess_masks, parent-\u003eaccess_masks,\n-\t       flex_array_size(parent, access_masks, parent-\u003enum_layers));\n+\tmemcpy(child-\u003elayers, parent-\u003elayers,\n+\t       flex_array_size(parent, layers, parent-\u003enum_layers));\n \n \tif (WARN_ON_ONCE(!parent-\u003ehierarchy)) {\n \t\terr = -EINVAL;\n@@ -587,10 +600,6 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,\n \tif (err)\n \t\treturn ERR_PTR(err);\n \n-#ifdef CONFIG_AUDIT\n-\tnew_dom-\u003ehierarchy-\u003equiet_masks = ruleset-\u003equiet_masks;\n-#endif /* CONFIG_AUDIT */\n-\n \treturn no_free_ptr(new_dom);\n }\n \ndiff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h\nindex 0437adf1742876..005ac3550088ed 100644\n--- a/security/landlock/ruleset.h\n+++ b/security/landlock/ruleset.h\n@@ -157,7 +157,7 @@ struct landlock_ruleset {\n \t\t * section.  This is only used by\n \t\t * landlock_put_ruleset_deferred() when @usage reaches zero.\n \t\t * The fields @lock, @usage, @num_rules, @num_layers,\n-\t\t * @quiet_masks and @access_masks are then unused.\n+\t\t * @quiet_access, @quiet_perm and @layers are then unused.\n \t\t */\n \t\tstruct work_struct work_free;\n \t\tstruct {\n@@ -184,32 +184,40 @@ struct landlock_ruleset {\n \t\t\t */\n \t\t\tu32 num_layers;\n \t\t\t/**\n-\t\t\t * @quiet_masks: Stores the quiet flags for an unmerged\n+\t\t\t * @quiet_access: Stores the quiet flags for an unmerged\n \t\t\t * ruleset.  For a merged domain, this is stored in each\n \t\t\t * layer's struct landlock_hierarchy instead.\n \t\t\t */\n-\t\t\tstruct access_masks quiet_masks;\n+\t\t\tstruct access_masks quiet_access;\n \t\t\t/**\n-\t\t\t * @access_masks: Contains the subset of filesystem and\n-\t\t\t * network actions that are restricted by a ruleset.\n+\t\t\t * @quiet_perm: Per-member quiet bitmasks for permission\n+\t\t\t * types (capabilities and namespace types) of an\n+\t\t\t * unmerged ruleset.  A denied member whose bit is set\n+\t\t\t * here is not logged when this layer is the one that\n+\t\t\t * denies it.  For a merged domain, this is stored in\n+\t\t\t * each layer's struct landlock_hierarchy instead.\n+\t\t\t */\n+\t\t\tstruct perm_masks quiet_perm;\n+\t\t\t/**\n+\t\t\t * @layers: Per-layer access configuration, including\n+\t\t\t * handled access masks and allowed permission bitmasks.\n \t\t\t * A domain saves all layers of merged rulesets in a\n \t\t\t * stack (FAM), starting from the first layer to the\n \t\t\t * last one.  These layers are used when merging\n-\t\t\t * rulesets, for user space backward compatibility\n-\t\t\t * (i.e. future-proof), and to properly handle merged\n-\t\t\t * rulesets without overlapping access rights.  These\n-\t\t\t * layers are set once and never changed for the\n-\t\t\t * lifetime of the ruleset.\n+\t\t\t * rulesets, for user space backward compatibility (i.e.\n+\t\t\t * future-proof), and to properly handle merged rulesets\n+\t\t\t * without overlapping access rights.  These layers are\n+\t\t\t * set once and never changed for the lifetime of the\n+\t\t\t * ruleset.\n \t\t\t */\n-\t\t\tstruct access_masks access_masks[];\n+\t\t\tstruct layer_config layers[] __counted_by(num_layers);\n \t\t};\n \t};\n };\n \n-struct landlock_ruleset *\n-landlock_create_ruleset(const access_mask_t access_mask_fs,\n-\t\t\tconst access_mask_t access_mask_net,\n-\t\t\tconst access_mask_t scope_mask);\n+struct landlock_ruleset *landlock_create_ruleset(\n+\tconst access_mask_t access_mask_fs, const access_mask_t access_mask_net,\n+\tconst access_mask_t scope_mask, const access_mask_t perm_mask);\n \n void landlock_put_ruleset(struct landlock_ruleset *const ruleset);\n void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);\n@@ -241,7 +249,8 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)\n  *\n  * @domain: Landlock ruleset (used as a domain)\n  *\n- * Return: An access_masks result of the OR of all the domain's access masks.\n+ * Return: An access_masks result of the OR of all the domain's handled access\n+ * masks.\n  */\n static inline struct access_masks\n landlock_union_access_masks(const struct landlock_ruleset *const domain)\n@@ -251,7 +260,7 @@ landlock_union_access_masks(const struct landlock_ruleset *const domain)\n \n \tfor (layer_level = 0; layer_level \u003c domain-\u003enum_layers; layer_level++) {\n \t\tunion access_masks_all layer = {\n-\t\t\t.masks = domain-\u003eaccess_masks[layer_level],\n+\t\t\t.masks = domain-\u003elayers[layer_level].handled,\n \t\t};\n \n \t\tmatches.all |= layer.all;\n@@ -269,7 +278,7 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,\n \n \t/* Should already be checked in sys_landlock_create_ruleset(). */\n \tWARN_ON_ONCE(fs_access_mask != fs_mask);\n-\truleset-\u003eaccess_masks[layer_level].fs |= fs_mask;\n+\truleset-\u003elayers[layer_level].handled.fs |= fs_mask;\n }\n \n static inline void\n@@ -281,7 +290,7 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,\n \n \t/* Should already be checked in sys_landlock_create_ruleset(). */\n \tWARN_ON_ONCE(net_access_mask != net_mask);\n-\truleset-\u003eaccess_masks[layer_level].net |= net_mask;\n+\truleset-\u003elayers[layer_level].handled.net |= net_mask;\n }\n \n static inline void\n@@ -292,7 +301,7 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset,\n \n \t/* Should already be checked in sys_landlock_create_ruleset(). */\n \tWARN_ON_ONCE(scope_mask != mask);\n-\truleset-\u003eaccess_masks[layer_level].scope |= mask;\n+\truleset-\u003elayers[layer_level].handled.scope |= mask;\n }\n \n static inline access_mask_t\n@@ -300,7 +309,7 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,\n \t\t\t    const u16 layer_level)\n {\n \t/* Handles all initially denied by default access rights. */\n-\treturn ruleset-\u003eaccess_masks[layer_level].fs |\n+\treturn ruleset-\u003elayers[layer_level].handled.fs |\n \t       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;\n }\n \n@@ -308,14 +317,32 @@ static inline access_mask_t\n landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,\n \t\t\t     const u16 layer_level)\n {\n-\treturn ruleset-\u003eaccess_masks[layer_level].net;\n+\treturn ruleset-\u003elayers[layer_level].handled.net;\n }\n \n static inline access_mask_t\n landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,\n \t\t\tconst u16 layer_level)\n {\n-\treturn ruleset-\u003eaccess_masks[layer_level].scope;\n+\treturn ruleset-\u003elayers[layer_level].handled.scope;\n+}\n+\n+static inline void\n+landlock_add_perm_mask(struct landlock_ruleset *const ruleset,\n+\t\t       const access_mask_t perm_mask, const u16 layer_level)\n+{\n+\taccess_mask_t mask = perm_mask \u0026 LANDLOCK_MASK_PERM;\n+\n+\t/* Should already be checked in sys_landlock_create_ruleset(). */\n+\tWARN_ON_ONCE(perm_mask != mask);\n+\truleset-\u003elayers[layer_level].handled.perm |= mask;\n+}\n+\n+static inline access_mask_t\n+landlock_get_perm_mask(const struct landlock_ruleset *const ruleset,\n+\t\t       const u16 layer_level)\n+{\n+\treturn ruleset-\u003elayers[layer_level].handled.perm;\n }\n \n bool landlock_unmask_layers(const struct landlock_rule *const rule,\ndiff --git a/security/landlock/setup.c b/security/landlock/setup.c\nindex 47dac1736f100a..971419d663bb86 100644\n--- a/security/landlock/setup.c\n+++ b/security/landlock/setup.c\n@@ -11,12 +11,14 @@\n #include \u003clinux/lsm_hooks.h\u003e\n #include \u003cuapi/linux/lsm.h\u003e\n \n+#include \"cap.h\"\n #include \"common.h\"\n #include \"cred.h\"\n #include \"errata.h\"\n #include \"fs.h\"\n #include \"id.h\"\n #include \"net.h\"\n+#include \"ns.h\"\n #include \"setup.h\"\n #include \"task.h\"\n \n@@ -68,6 +70,8 @@ static int __init landlock_init(void)\n \tlandlock_add_task_hooks();\n \tlandlock_add_fs_hooks();\n \tlandlock_add_net_hooks();\n+\tlandlock_add_ns_hooks();\n+\tlandlock_add_cap_hooks();\n \tlandlock_init_id();\n \tlandlock_initialized = true;\n \tpr_info(\"Up and running.\\n\");\ndiff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c\nindex 36b02892c62f52..6d0b02dae2ac59 100644\n--- a/security/landlock/syscalls.c\n+++ b/security/landlock/syscalls.c\n@@ -20,6 +20,7 @@\n #include \u003clinux/fs.h\u003e\n #include \u003clinux/limits.h\u003e\n #include \u003clinux/mount.h\u003e\n+#include \u003clinux/ns/ns_common_types.h\u003e\n #include \u003clinux/path.h\u003e\n #include \u003clinux/sched.h\u003e\n #include \u003clinux/security.h\u003e\n@@ -29,11 +30,13 @@\n #include \u003clinux/uaccess.h\u003e\n #include \u003cuapi/linux/landlock.h\u003e\n \n+#include \"cap.h\"\n #include \"cred.h\"\n #include \"domain.h\"\n #include \"fs.h\"\n #include \"limits.h\"\n #include \"net.h\"\n+#include \"ns.h\"\n #include \"ruleset.h\"\n #include \"setup.h\"\n #include \"tsync.h\"\n@@ -95,7 +98,10 @@ static void build_check_abi(void)\n \tstruct landlock_ruleset_attr ruleset_attr;\n \tstruct landlock_path_beneath_attr path_beneath_attr;\n \tstruct landlock_net_port_attr net_port_attr;\n+\tstruct landlock_namespace_attr namespace_attr;\n+\tstruct landlock_capability_attr capability_attr;\n \tsize_t ruleset_size, path_beneath_size, net_port_size;\n+\tsize_t namespace_size, capability_size;\n \n \t/*\n \t * For each user space ABI structures, first checks that there is no\n@@ -108,8 +114,9 @@ static void build_check_abi(void)\n \truleset_size += sizeof(ruleset_attr.quiet_access_fs);\n \truleset_size += sizeof(ruleset_attr.quiet_access_net);\n \truleset_size += sizeof(ruleset_attr.quiet_scoped);\n+\truleset_size += sizeof(ruleset_attr.handled_perm);\n \tBUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);\n-\tBUILD_BUG_ON(sizeof(ruleset_attr) != 48);\n+\tBUILD_BUG_ON(sizeof(ruleset_attr) != 56);\n \n \tpath_beneath_size = sizeof(path_beneath_attr.allowed_access);\n \tpath_beneath_size += sizeof(path_beneath_attr.parent_fd);\n@@ -120,6 +127,18 @@ static void build_check_abi(void)\n \tnet_port_size += sizeof(net_port_attr.port);\n \tBUILD_BUG_ON(sizeof(net_port_attr) != net_port_size);\n \tBUILD_BUG_ON(sizeof(net_port_attr) != 16);\n+\n+\tnamespace_size = sizeof(namespace_attr.perm);\n+\tnamespace_size += sizeof(namespace_attr.allowed_namespace_types);\n+\tnamespace_size += sizeof(namespace_attr.quiet_namespace_types);\n+\tBUILD_BUG_ON(sizeof(namespace_attr) != namespace_size);\n+\tBUILD_BUG_ON(sizeof(namespace_attr) != 24);\n+\n+\tcapability_size = sizeof(capability_attr.perm);\n+\tcapability_size += sizeof(capability_attr.allowed_capabilities);\n+\tcapability_size += sizeof(capability_attr.quiet_capabilities);\n+\tBUILD_BUG_ON(sizeof(capability_attr) != capability_size);\n+\tBUILD_BUG_ON(sizeof(capability_attr) != 24);\n }\n \n /* Ruleset handling */\n@@ -169,7 +188,7 @@ static const struct file_operations ruleset_fops = {\n  * If the change involves a fix that requires userspace awareness, also update\n  * the errata documentation in Documentation/userspace-api/landlock.rst .\n  */\n-const int landlock_abi_version = 10;\n+const int landlock_abi_version = 11;\n \n /**\n  * sys_landlock_create_ruleset - Create a new ruleset\n@@ -270,16 +289,22 @@ SYSCALL_DEFINE3(landlock_create_ruleset,\n \t    ruleset_attr.scoped)\n \t\treturn -EINVAL;\n \n+\t/* Checks permission content (and 32-bits cast). */\n+\tif ((ruleset_attr.handled_perm | LANDLOCK_MASK_PERM) !=\n+\t    LANDLOCK_MASK_PERM)\n+\t\treturn -EINVAL;\n+\n \t/* Checks arguments and transforms to kernel struct. */\n \truleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,\n \t\t\t\t\t  ruleset_attr.handled_access_net,\n-\t\t\t\t\t  ruleset_attr.scoped);\n+\t\t\t\t\t  ruleset_attr.scoped,\n+\t\t\t\t\t  ruleset_attr.handled_perm);\n \tif (IS_ERR(ruleset))\n \t\treturn PTR_ERR(ruleset);\n \n-\truleset-\u003equiet_masks.fs = ruleset_attr.quiet_access_fs;\n-\truleset-\u003equiet_masks.net = ruleset_attr.quiet_access_net;\n-\truleset-\u003equiet_masks.scope = ruleset_attr.quiet_scoped;\n+\truleset-\u003equiet_access.fs = ruleset_attr.quiet_access_fs;\n+\truleset-\u003equiet_access.net = ruleset_attr.quiet_access_net;\n+\truleset-\u003equiet_access.scope = ruleset_attr.quiet_scoped;\n \n \t/* Creates anonymous FD referring to the ruleset. */\n \truleset_fd = anon_inode_getfd(\"[landlock-ruleset]\", \u0026ruleset_fops,\n@@ -367,12 +392,12 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,\n \t\treturn -ENOMSG;\n \n \t/* Checks that allowed_access matches the @ruleset constraints. */\n-\tmask = ruleset-\u003eaccess_masks[0].fs;\n+\tmask = ruleset-\u003elayers[0].handled.fs;\n \tif ((path_beneath_attr.allowed_access | mask) != mask)\n \t\treturn -EINVAL;\n \n \t/* Checks for useless quiet flag. */\n-\tif (flags \u0026 LANDLOCK_ADD_RULE_QUIET \u0026\u0026 !ruleset-\u003equiet_masks.fs)\n+\tif (flags \u0026 LANDLOCK_ADD_RULE_QUIET \u0026\u0026 !ruleset-\u003equiet_access.fs)\n \t\treturn -EINVAL;\n \n \t/* Gets and checks the new rule. */\n@@ -413,7 +438,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,\n \t\treturn -EINVAL;\n \n \t/* Checks for useless quiet flag. */\n-\tif (flags \u0026 LANDLOCK_ADD_RULE_QUIET \u0026\u0026 !ruleset-\u003equiet_masks.net)\n+\tif (flags \u0026 LANDLOCK_ADD_RULE_QUIET \u0026\u0026 !ruleset-\u003equiet_access.net)\n \t\treturn -EINVAL;\n \n \t/* Denies inserting a rule with port greater than 65535. */\n@@ -425,13 +450,142 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,\n \t\t\t\t\tnet_port_attr.allowed_access, flags);\n }\n \n+static int add_rule_namespace(struct landlock_ruleset *const ruleset,\n+\t\t\t      const void __user *const rule_attr,\n+\t\t\t      const u32 flags)\n+{\n+\tstruct landlock_namespace_attr ns_attr;\n+\tint res;\n+\taccess_mask_t mask;\n+\n+\t/*\n+\t * Namespace rules support no add-rule flags.  In particular,\n+\t * LANDLOCK_ADD_RULE_QUIET is filesystem/network only.\n+\t */\n+\tif (flags)\n+\t\treturn -EINVAL;\n+\n+\t/* Copies raw user space buffer. */\n+\tres = copy_from_user(\u0026ns_attr, rule_attr, sizeof(ns_attr));\n+\tif (res)\n+\t\treturn -EFAULT;\n+\n+\t/* Informs about useless rule: empty perm. */\n+\tif (!ns_attr.perm)\n+\t\treturn -ENOMSG;\n+\n+\t/*\n+\t * The perm selector must match LANDLOCK_PERM_NAMESPACE_USE.  The valid\n+\t * set is a single bit today, so this is an exact match now; the check\n+\t * broadens to a subset test once another supported permission is added.\n+\t */\n+\tif (ns_attr.perm != LANDLOCK_PERM_NAMESPACE_USE)\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * Checks that perm matches the ruleset constraints.  This also makes\n+\t * quieting require the category to be handled.\n+\t */\n+\tmask = landlock_get_perm_mask(ruleset, 0);\n+\tif (!(mask \u0026 LANDLOCK_PERM_NAMESPACE_USE))\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * Informs about useless rule: neither allows nor quiets anything.  A\n+\t * quiet-only rule (empty allowed set) is legal.\n+\t */\n+\tif (!ns_attr.allowed_namespace_types \u0026\u0026 !ns_attr.quiet_namespace_types)\n+\t\treturn -ENOMSG;\n+\n+\t/*\n+\t * Stores only the namespace types this kernel knows about.  Unknown\n+\t * bits are silently accepted for forward compatibility: user space\n+\t * compiled against newer headers can pass new CLONE_NEW* flags without\n+\t * getting EINVAL on older kernels.  Unknown bits have no effect because\n+\t * no hook checks them.  The quiet bitmask suppresses logging of denials\n+\t * attributed to this layer; see landlock_log_denial().\n+\t */\n+\tmutex_lock(\u0026ruleset-\u003elock);\n+\truleset-\u003elayers[0].allowed.ns |= landlock_ns_types_to_bits(\n+\t\tns_attr.allowed_namespace_types \u0026 CLONE_NS_ALL);\n+\truleset-\u003equiet_perm.ns |= landlock_ns_types_to_bits(\n+\t\tns_attr.quiet_namespace_types \u0026 CLONE_NS_ALL);\n+\tmutex_unlock(\u0026ruleset-\u003elock);\n+\treturn 0;\n+}\n+\n+static int add_rule_capability(struct landlock_ruleset *const ruleset,\n+\t\t\t       const void __user *const rule_attr,\n+\t\t\t       const u32 flags)\n+{\n+\tstruct landlock_capability_attr cap_attr;\n+\tint res;\n+\taccess_mask_t mask;\n+\n+\t/*\n+\t * Capability rules support no add-rule flags.  In particular,\n+\t * LANDLOCK_ADD_RULE_QUIET is filesystem/network only.\n+\t */\n+\tif (flags)\n+\t\treturn -EINVAL;\n+\n+\t/* Copies raw user space buffer. */\n+\tres = copy_from_user(\u0026cap_attr, rule_attr, sizeof(cap_attr));\n+\tif (res)\n+\t\treturn -EFAULT;\n+\n+\t/* Informs about useless rule: empty perm. */\n+\tif (!cap_attr.perm)\n+\t\treturn -ENOMSG;\n+\n+\t/*\n+\t * The perm selector must match LANDLOCK_PERM_CAPABILITY_USE.  The valid\n+\t * set is a single bit today, so this is an exact match now; the check\n+\t * broadens to a subset test once another supported permission is added.\n+\t */\n+\tif (cap_attr.perm != LANDLOCK_PERM_CAPABILITY_USE)\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * Checks that perm matches the ruleset constraints.  This also makes\n+\t * quieting require the category to be handled.\n+\t */\n+\tmask = landlock_get_perm_mask(ruleset, 0);\n+\tif (!(mask \u0026 LANDLOCK_PERM_CAPABILITY_USE))\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * Informs about useless rule: neither allows nor quiets anything.  A\n+\t * quiet-only rule (empty allowed set) is legal.\n+\t */\n+\tif (!cap_attr.allowed_capabilities \u0026\u0026 !cap_attr.quiet_capabilities)\n+\t\treturn -ENOMSG;\n+\n+\t/*\n+\t * Stores only the capabilities this kernel knows about.  Unknown bits\n+\t * are silently accepted for forward compatibility: user space compiled\n+\t * against newer headers can pass new CAP_* bits without getting EINVAL\n+\t * on older kernels.  Unknown bits have no effect because no hook checks\n+\t * them.  The quiet bitmask suppresses logging of denials attributed to\n+\t * this layer; see landlock_log_denial().\n+\t */\n+\tmutex_lock(\u0026ruleset-\u003elock);\n+\truleset-\u003elayers[0].allowed.caps |= landlock_caps_to_bits(\n+\t\tcap_attr.allowed_capabilities \u0026 CAP_VALID_MASK);\n+\truleset-\u003equiet_perm.caps |= landlock_caps_to_bits(\n+\t\tcap_attr.quiet_capabilities \u0026 CAP_VALID_MASK);\n+\tmutex_unlock(\u0026ruleset-\u003elock);\n+\treturn 0;\n+}\n+\n /**\n  * sys_landlock_add_rule - Add a new rule to a ruleset\n  *\n  * @ruleset_fd: File descriptor tied to the ruleset that should be extended\n  *\t\twith the new rule.\n  * @rule_type: Identify the structure type pointed to by @rule_attr:\n- *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.\n+ *             %LANDLOCK_RULE_PATH_BENEATH, %LANDLOCK_RULE_NET_PORT,\n+ *             %LANDLOCK_RULE_NAMESPACE, or %LANDLOCK_RULE_CAPABILITY.\n  * @rule_attr: Pointer to a rule (matching the @rule_type).\n  * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET.\n  *\n@@ -485,6 +639,10 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,\n \t\treturn add_rule_path_beneath(ruleset, rule_attr, flags);\n \tcase LANDLOCK_RULE_NET_PORT:\n \t\treturn add_rule_net_port(ruleset, rule_attr, flags);\n+\tcase LANDLOCK_RULE_NAMESPACE:\n+\t\treturn add_rule_namespace(ruleset, rule_attr, flags);\n+\tcase LANDLOCK_RULE_CAPABILITY:\n+\t\treturn add_rule_capability(ruleset, rule_attr, flags);\n \tdefault:\n \t\treturn -EINVAL;\n \t}\ndiff --git a/security/lsm_audit.c b/security/lsm_audit.c\nindex 737f5a263a8f79..404ccbbbf94cbe 100644\n--- a/security/lsm_audit.c\n+++ b/security/lsm_audit.c\n@@ -403,6 +403,10 @@ void audit_log_lsm_data(struct audit_buffer *ab,\n \tcase LSM_AUDIT_DATA_NLMSGTYPE:\n \t\taudit_log_format(ab, \" nl-msgtype=%hu\", a-\u003eu.nlmsg_type);\n \t\tbreak;\n+\tcase LSM_AUDIT_DATA_NS:\n+\t\taudit_log_format(ab, \" namespace_type=0x%x namespace_id=%llu\",\n+\t\t\t\t a-\u003eu.ns.ns_type, a-\u003eu.ns.ns_id);\n+\t\tbreak;\n \t} /* switch (a-\u003etype) */\n }\n \ndiff --git a/security/lsm_init.c b/security/lsm_init.c\nindex 7c0fd17f1601f2..dcd2a228c4f632 100644\n--- a/security/lsm_init.c\n+++ b/security/lsm_init.c\n@@ -303,6 +303,7 @@ static void __init lsm_prepare(struct lsm_info *lsm)\n \tlsm_blob_size_update(\u0026blobs-\u003elbs_ipc, \u0026blob_sizes.lbs_ipc);\n \tlsm_blob_size_update(\u0026blobs-\u003elbs_key, \u0026blob_sizes.lbs_key);\n \tlsm_blob_size_update(\u0026blobs-\u003elbs_msg_msg, \u0026blob_sizes.lbs_msg_msg);\n+\tlsm_blob_size_update(\u0026blobs-\u003elbs_ns, \u0026blob_sizes.lbs_ns);\n \tlsm_blob_size_update(\u0026blobs-\u003elbs_perf_event,\n \t\t\t     \u0026blob_sizes.lbs_perf_event);\n \tlsm_blob_size_update(\u0026blobs-\u003elbs_sock, \u0026blob_sizes.lbs_sock);\n@@ -450,6 +451,7 @@ int __init security_init(void)\n \t\tlsm_pr(\"blob(ipc) size %d\\n\", blob_sizes.lbs_ipc);\n \t\tlsm_pr(\"blob(key) size %d\\n\", blob_sizes.lbs_key);\n \t\tlsm_pr(\"blob(msg_msg)_size %d\\n\", blob_sizes.lbs_msg_msg);\n+\t\tlsm_pr(\"blob(ns) size %d\\n\", blob_sizes.lbs_ns);\n \t\tlsm_pr(\"blob(sock) size %d\\n\", blob_sizes.lbs_sock);\n \t\tlsm_pr(\"blob(superblock) size %d\\n\", blob_sizes.lbs_superblock);\n \t\tlsm_pr(\"blob(perf_event) size %d\\n\", blob_sizes.lbs_perf_event);\ndiff --git a/security/security.c b/security/security.c\nindex 71aea8fdf01429..e0b43ab60d233e 100644\n--- a/security/security.c\n+++ b/security/security.c\n@@ -26,6 +26,7 @@\n #include \u003clinux/string.h\u003e\n #include \u003clinux/xattr.h\u003e\n #include \u003clinux/msg.h\u003e\n+#include \u003clinux/ns_common.h\u003e\n #include \u003clinux/overflow.h\u003e\n #include \u003clinux/perf_event.h\u003e\n #include \u003clinux/fs.h\u003e\n@@ -381,6 +382,19 @@ static int lsm_superblock_alloc(struct super_block *sb)\n \t\t\t      GFP_KERNEL);\n }\n \n+/**\n+ * lsm_ns_alloc - allocate a composite namespace blob\n+ * @ns: the namespace that needs a blob\n+ *\n+ * Allocate the namespace blob for all the modules\n+ *\n+ * Returns 0, or -ENOMEM if memory can't be allocated.\n+ */\n+static int lsm_ns_alloc(struct ns_common *ns)\n+{\n+\treturn lsm_blob_alloc(\u0026ns-\u003ens_security, blob_sizes.lbs_ns, GFP_KERNEL);\n+}\n+\n /**\n  * lsm_fill_user_ctx - Fill a user space lsm_ctx structure\n  * @uctx: a userspace LSM context to be filled\n@@ -3358,6 +3372,64 @@ int security_create_user_ns(const struct cred *cred)\n \treturn call_int_hook(userns_create, cred);\n }\n \n+/**\n+ * security_namespace_init() - Initialize LSM security data for a namespace\n+ * @ns: the namespace being initialized\n+ *\n+ * Initialize the LSM security blob attached to the namespace. The namespace type\n+ * is available via ns-\u003ens_type, and the owning user namespace (if any)\n+ * via ns-\u003eops-\u003eowner(ns).\n+ *\n+ * Return: Returns 0 if successful, otherwise \u003c 0 error code.\n+ */\n+int security_namespace_init(struct ns_common *ns)\n+{\n+\tint rc;\n+\n+\trc = lsm_ns_alloc(ns);\n+\tif (unlikely(rc))\n+\t\treturn rc;\n+\n+\trc = call_int_hook(namespace_init, ns);\n+\tif (unlikely(rc))\n+\t\tsecurity_namespace_free(ns);\n+\n+\treturn rc;\n+}\n+\n+/**\n+ * security_namespace_free() - Release LSM security data from a namespace\n+ * @ns: the namespace being freed\n+ *\n+ * Release security data attached to the namespace. Called before the\n+ * namespace structure is freed.\n+ */\n+void security_namespace_free(struct ns_common *ns)\n+{\n+\tif (!ns-\u003ens_security)\n+\t\treturn;\n+\n+\tcall_void_hook(namespace_free, ns);\n+\n+\tkfree(ns-\u003ens_security);\n+\tns-\u003ens_security = NULL;\n+}\n+\n+/**\n+ * security_namespace_install() - Check permission to install a namespace\n+ * @nsset: the target nsset being configured\n+ * @ns: the namespace being installed\n+ *\n+ * Check permission before allowing a namespace to be installed into the\n+ * process's set of namespaces via setns(2).\n+ *\n+ * Return: Returns 0 if permission is granted, otherwise \u003c 0 error code.\n+ */\n+int security_namespace_install(const struct nsset *nsset, struct ns_common *ns)\n+{\n+\treturn call_int_hook(namespace_install, nsset, ns);\n+}\n+\n /**\n  * security_ipc_permission() - Check if sysv ipc access is allowed\n  * @ipcp: ipc permission structure\ndiff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c\nindex cbd3c1669951a4..bcb5bdafd9ed5e 100644\n--- a/tools/testing/selftests/landlock/base_test.c\n+++ b/tools/testing/selftests/landlock/base_test.c\n@@ -76,7 +76,7 @@ TEST(abi_version)\n \tconst struct landlock_ruleset_attr ruleset_attr = {\n \t\t.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,\n \t};\n-\tASSERT_EQ(10, landlock_create_ruleset(NULL, 0,\n+\tASSERT_EQ(11, landlock_create_ruleset(NULL, 0,\n \t\t\t\t\t      LANDLOCK_CREATE_RULESET_VERSION));\n \n \tASSERT_EQ(-1, landlock_create_ruleset(\u0026ruleset_attr, 0,\n@@ -142,6 +142,24 @@ TEST(errata)\n \tASSERT_EQ(EINVAL, errno);\n }\n \n+#define PERM_LAST LANDLOCK_PERM_CAPABILITY_USE\n+\n+TEST(ruleset_with_unknown_perm)\n+{\n+\t__u64 perm_mask;\n+\n+\tfor (perm_mask = 1ULL \u003c\u003c 63; perm_mask != PERM_LAST; perm_mask \u003e\u003e= 1) {\n+\t\tstruct landlock_ruleset_attr ruleset_attr = {\n+\t\t\t.handled_perm = perm_mask,\n+\t\t};\n+\n+\t\t/* Unknown handled_perm values must be rejected. */\n+\t\tASSERT_EQ(-1, landlock_create_ruleset(\u0026ruleset_attr,\n+\t\t\t\t\t\t      sizeof(ruleset_attr), 0));\n+\t\tASSERT_EQ(EINVAL, errno);\n+\t}\n+}\n+\n /* Tests ordering of syscall argument checks. */\n TEST(create_ruleset_checks_ordering)\n {\ndiff --git a/tools/testing/selftests/landlock/cap_test.c b/tools/testing/selftests/landlock/cap_test.c\nnew file mode 100644\nindex 00000000000000..a86fd72b5639f3\n--- /dev/null\n+++ b/tools/testing/selftests/landlock/cap_test.c\n@@ -0,0 +1,889 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Landlock tests - Capability restriction\n+ *\n+ * Copyright © 2026 Cloudflare, Inc.\n+ */\n+\n+#define _GNU_SOURCE\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003clinux/capability.h\u003e\n+#include \u003clinux/landlock.h\u003e\n+#include \u003csched.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/mount.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003csys/xattr.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"audit.h\"\n+#include \"common.h\"\n+\n+static bool list_has_xattr(const char *list, ssize_t len, const char *name)\n+{\n+\tconst char *p;\n+\n+\tfor (p = list; p \u003c list + len; p += strlen(p) + 1)\n+\t\tif (strcmp(p, name) == 0)\n+\t\t\treturn true;\n+\treturn false;\n+}\n+\n+static int create_cap_ruleset(void)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\n+\treturn landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+}\n+\n+static int add_cap_rule(int ruleset_fd, __u64 cap)\n+{\n+\tconst struct landlock_capability_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c cap),\n+\t};\n+\n+\treturn landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, \u0026attr,\n+\t\t\t\t 0);\n+}\n+\n+static int add_cap_rule_full(int ruleset_fd, __u64 allowed, __u64 quiet)\n+{\n+\tconst struct landlock_capability_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = allowed,\n+\t\t.quiet_capabilities = quiet,\n+\t};\n+\n+\treturn landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, \u0026attr,\n+\t\t\t\t 0);\n+}\n+\n+TEST(add_rule_bad_attr)\n+{\n+\tconst struct landlock_ruleset_attr ns_only_attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t};\n+\tint ruleset_fd;\n+\tstruct landlock_capability_attr attr = {};\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\t/* Empty perm selector returns ENOMSG. */\n+\tattr.perm = 0;\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c CAP_NET_RAW);\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(ENOMSG, errno);\n+\n+\t/* Useless rule: neither allowed nor quiet capabilities set. */\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_capabilities = 0;\n+\tattr.quiet_capabilities = 0;\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(ENOMSG, errno);\n+\n+\t/* Quiet-only rule (empty allowed set) is legal. */\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_capabilities = 0;\n+\tattr.quiet_capabilities = (1ULL \u003c\u003c CAP_NET_RAW);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/* Allow and quiet different members in the same rule. */\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN);\n+\tattr.quiet_capabilities = (1ULL \u003c\u003c CAP_NET_RAW);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/* perm with unhandled bit. */\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE | LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c CAP_NET_RAW);\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(EINVAL, errno);\n+\n+\t/* perm with wrong type. */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c CAP_NET_RAW);\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(EINVAL, errno);\n+\n+\t/*\n+\t * Unknown capability bits (e.g. bit 63) are silently accepted for\n+\t * forward compatibility.  Only known bits are stored.\n+\t */\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_capabilities = 1ULL \u003c\u003c 63;\n+\tattr.quiet_capabilities = 0;\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/*\n+\t * LANDLOCK_ADD_RULE_QUIET (and any other flag) is filesystem/network\n+\t * only and must be rejected for capability rules, even with an\n+\t * otherwise-valid attr (cases.md Q-E5).\n+\t */\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c CAP_NET_RAW);\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t\t\u0026attr, LANDLOCK_ADD_RULE_QUIET));\n+\tASSERT_EQ(EINVAL, errno);\n+\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * Ruleset handles PERM_NAMESPACE_USE but not PERM_CAPABILITY_USE:\n+\t * adding a capability rule must be rejected.\n+\t */\n+\truleset_fd =\n+\t\tlandlock_create_ruleset(\u0026ns_only_attr, sizeof(ns_only_attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c CAP_NET_RAW);\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(EINVAL, errno);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+}\n+\n+/*\n+ * Unknown capability values above CAP_LAST_CAP are silently accepted\n+ * (allow-list: they have no effect since the kernel never checks them).\n+ */\n+TEST(add_rule_unknown)\n+{\n+\tint ruleset_fd;\n+\tstruct landlock_capability_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\t/* Just above CAP_LAST_CAP should succeed. */\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c (CAP_LAST_CAP + 1));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/* High values (below bit 63) should succeed. */\n+\tattr.allowed_capabilities = (1ULL \u003c\u003c 62);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+}\n+\n+/*\n+ * A rule that lists only capability bits unknown to the running kernel is\n+ * accepted by landlock_add_rule() but has no runtime effect: once the domain is\n+ * enforced, any actual CAP_* capability is still denied by the per-category\n+ * deny-by-default behaviour.  This documents the forward-compatibility\n+ * contract: unknown bits are silently accepted so the same policy can be loaded\n+ * across kernels, but they never grant a capability that the running kernel\n+ * knows nothing about.\n+ */\n+TEST(add_rule_unknown_no_runtime_effect)\n+{\n+\tconst struct landlock_ruleset_attr ruleset_attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\tstruct landlock_capability_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t/* Only unknown bits above CAP_LAST_CAP. */\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c (CAP_LAST_CAP + 1)) |\n+\t\t\t\t\t(1ULL \u003c\u003c 62),\n+\t};\n+\tint ruleset_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd =\n+\t\tlandlock_create_ruleset(\u0026ruleset_attr, sizeof(ruleset_attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/*\n+\t * Isolate hostname changes and establish a baseline: with CAP_SYS_ADMIN\n+\t * and absent the domain, sethostname(2) succeeds.  This ensures the\n+\t * EPERM below is attributable to Landlock rather than to a missing\n+\t * capability, and fails if the hook always allows.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tASSERT_EQ(0, unshare(CLONE_NEWUTS));\n+\tASSERT_EQ(0, sethostname(\"baseline\", 8));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * CAP_SYS_ADMIN is a real, known capability but was not authorised by\n+\t * the rule above; deny-by-default applies.  sethostname(2) requires\n+\t * CAP_SYS_ADMIN.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, sethostname(\"test\", 4));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/* clang-format off */\n+FIXTURE(cap_enforce) {};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(cap_enforce)\n+{\n+\tconst bool is_sandboxed;\n+\tconst bool handle_caps;\n+\tconst __u64 allowed_cap;\n+\tconst int expected_sysadmin;\n+\tconst int expected_chroot;\n+};\n+\n+/*\n+ * Unsandboxed baseline: no Landlock domain is enforced.  Both capabilities\n+ * should work normally.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_enforce, unsandboxed) {\n+\t.is_sandboxed = false,\n+\t.handle_caps = false,\n+\t.allowed_cap = 0,\n+\t.expected_sysadmin = 0,\n+\t.expected_chroot = 0,\n+};\n+/* clang-format on */\n+\n+/*\n+ * Denied: capabilities are handled but no rule allows them.  All capability\n+ * checks must be denied by Landlock even if the capability is effective.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_enforce, denied) {\n+\t.is_sandboxed = true,\n+\t.handle_caps = true,\n+\t.allowed_cap = 0,\n+\t.expected_sysadmin = EPERM,\n+\t.expected_chroot = EPERM,\n+};\n+/* clang-format on */\n+\n+/*\n+ * Allowed: CAP_SYS_ADMIN is allowed by rule, CAP_SYS_CHROOT is not.  Only the\n+ * explicitly allowed capability should succeed.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_enforce, allowed) {\n+\t.is_sandboxed = true,\n+\t.handle_caps = true,\n+\t.allowed_cap = CAP_SYS_ADMIN,\n+\t.expected_sysadmin = 0,\n+\t.expected_chroot = EPERM,\n+};\n+/* clang-format on */\n+\n+/*\n+ * Unhandled: the ruleset does not handle LANDLOCK_PERM_CAPABILITY_USE at all\n+ * (only handles FS access).  Both capabilities should work since the domain\n+ * does not restrict them.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_enforce, unhandled) {\n+\t.is_sandboxed = true,\n+\t.handle_caps = false,\n+\t.allowed_cap = 0,\n+\t.expected_sysadmin = 0,\n+\t.expected_chroot = 0,\n+};\n+/* clang-format on */\n+\n+FIXTURE_SETUP(cap_enforce)\n+{\n+\tdisable_caps(_metadata);\n+}\n+\n+FIXTURE_TEARDOWN(cap_enforce)\n+{\n+}\n+\n+/*\n+ * Capability enforcement: tests the four fundamental enforcement scenarios\n+ * (unsandboxed baseline, denied, allowed, unhandled) using two independent\n+ * capability checks (sethostname for CAP_SYS_ADMIN, chroot for CAP_SYS_CHROOT).\n+ */\n+TEST_F(cap_enforce, use)\n+{\n+\tint ruleset_fd;\n+\n+\t/* Isolate hostname changes from other tests. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tASSERT_EQ(0, unshare(CLONE_NEWUTS));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\tif (variant-\u003eis_sandboxed) {\n+\t\tif (variant-\u003ehandle_caps) {\n+\t\t\truleset_fd = create_cap_ruleset();\n+\t\t} else {\n+\t\t\tconst struct landlock_ruleset_attr attr = {\n+\t\t\t\t.handled_access_fs =\n+\t\t\t\t\tLANDLOCK_ACCESS_FS_READ_FILE,\n+\t\t\t};\n+\n+\t\t\truleset_fd =\n+\t\t\t\tlandlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\t\t}\n+\t\tASSERT_LE(0, ruleset_fd);\n+\n+\t\tif (variant-\u003eallowed_cap)\n+\t\t\tASSERT_EQ(0, add_cap_rule(ruleset_fd,\n+\t\t\t\t\t\t  variant-\u003eallowed_cap));\n+\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\t/* Test CAP_SYS_ADMIN via sethostname. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tif (variant-\u003eexpected_sysadmin) {\n+\t\tEXPECT_EQ(-1, sethostname(\"test\", 4));\n+\t\tEXPECT_EQ(variant-\u003eexpected_sysadmin, errno);\n+\t} else {\n+\t\tEXPECT_EQ(0, sethostname(\"test\", 4));\n+\t}\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* Test CAP_SYS_CHROOT via chroot. */\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tif (variant-\u003eexpected_chroot) {\n+\t\tEXPECT_EQ(-1, chroot(\"/\"));\n+\t\tEXPECT_EQ(variant-\u003eexpected_chroot, errno);\n+\t} else {\n+\t\tEXPECT_EQ(0, chroot(\"/\"));\n+\t}\n+}\n+\n+/*\n+ * Layer stacking: both layers must allow CAP_SYS_ADMIN for the capability to be\n+ * exercisable.  Variants cover the three per-layer combinations that exercise\n+ * distinct walker paths (allow/deny, allow/allow, deny/allow), an unsandboxed\n+ * baseline, and a mixed-layer case where one layer does not handle\n+ * PERM_CAPABILITY_USE at all.\n+ */\n+/* clang-format off */\n+FIXTURE(cap_stacking) {};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(cap_stacking)\n+{\n+\tconst bool is_sandboxed;\n+\tconst bool first_layer_allows;\n+\tconst bool second_layer_allows;\n+\tconst bool second_layer_is_fs_only;\n+\tconst int expected_sysadmin;\n+\tconst int expected_chroot;\n+};\n+\n+/*\n+ * Unsandboxed baseline: no Landlock layers are stacked.  Both capabilities\n+ * should work normally.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_stacking, unsandboxed) {\n+\t.is_sandboxed = false,\n+\t.first_layer_allows = false,\n+\t.second_layer_allows = false,\n+\t.expected_sysadmin = 0,\n+\t.expected_chroot = 0,\n+};\n+/* clang-format on */\n+\n+/* Layer 1 allows CAP_SYS_ADMIN, layer 2 denies -\u003e denied. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_stacking, deny) {\n+\t.is_sandboxed = true,\n+\t.first_layer_allows = true,\n+\t.second_layer_allows = false,\n+\t.expected_sysadmin = EPERM,\n+\t.expected_chroot = EPERM,\n+};\n+/* clang-format on */\n+\n+/* Both layers allow CAP_SYS_ADMIN -\u003e sysadmin succeeds, chroot still denied. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_stacking, allow) {\n+\t.is_sandboxed = true,\n+\t.first_layer_allows = true,\n+\t.second_layer_allows = true,\n+\t.expected_sysadmin = 0,\n+\t.expected_chroot = EPERM,\n+};\n+/* clang-format on */\n+\n+/*\n+ * Layer 1 denies CAP_SYS_ADMIN, layer 2 allows -\u003e still denied: a child layer\n+ * cannot grant what an ancestor layer withheld.  Complements the\n+ * parent-allows/child-denies variant; together they verify the walker checks\n+ * both layers and accepts only the (allow, allow) cell.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_stacking, parent_denies) {\n+\t.is_sandboxed = true,\n+\t.first_layer_allows = false,\n+\t.second_layer_allows = true,\n+\t.expected_sysadmin = EPERM,\n+\t.expected_chroot = EPERM,\n+};\n+/* clang-format on */\n+\n+/*\n+ * Mixed layers: first layer handles PERM_CAPABILITY_USE (denies all caps),\n+ * second layer is FS-only (does not handle it).  The perm walker iterates from\n+ * youngest (layer 1) to oldest (layer 0) and must skip the FS-only layer to\n+ * find the denying layer beneath.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(cap_stacking, mixed_layers) {\n+\t/* clang-format on */\n+\t.is_sandboxed = true,\n+\t.first_layer_allows = false,\n+\t.second_layer_is_fs_only = true,\n+\t.expected_sysadmin = EPERM,\n+\t.expected_chroot = EPERM,\n+};\n+\n+FIXTURE_SETUP(cap_stacking)\n+{\n+\tdisable_caps(_metadata);\n+}\n+\n+FIXTURE_TEARDOWN(cap_stacking)\n+{\n+}\n+\n+TEST_F(cap_stacking, two_layers)\n+{\n+\tint ruleset_fd;\n+\n+\tif (variant-\u003eis_sandboxed) {\n+\t\t/*\n+\t\t * First layer: handles PERM_CAPABILITY_USE; rule added per\n+\t\t * variant.\n+\t\t */\n+\t\truleset_fd = create_cap_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tif (variant-\u003efirst_layer_allows)\n+\t\t\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN));\n+\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t\tif (variant-\u003esecond_layer_is_fs_only) {\n+\t\t\t/*\n+\t\t\t * Second layer: FS-only (does not handle\n+\t\t\t * PERM_CAPABILITY_USE).  The perm walker must skip this\n+\t\t\t * layer.\n+\t\t\t */\n+\t\t\tconst struct landlock_ruleset_attr fs_attr = {\n+\t\t\t\t.handled_access_fs =\n+\t\t\t\t\tLANDLOCK_ACCESS_FS_READ_FILE,\n+\t\t\t};\n+\n+\t\t\truleset_fd = landlock_create_ruleset(\n+\t\t\t\t\u0026fs_attr, sizeof(fs_attr), 0);\n+\t\t} else {\n+\t\t\t/* Second layer: cap allow or deny. */\n+\t\t\truleset_fd = create_cap_ruleset();\n+\t\t\tif (variant-\u003esecond_layer_allows)\n+\t\t\t\tASSERT_EQ(0, add_cap_rule(ruleset_fd,\n+\t\t\t\t\t\t\t  CAP_SYS_ADMIN));\n+\t\t}\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\t/* Test CAP_SYS_ADMIN via sethostname. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tif (variant-\u003eexpected_sysadmin) {\n+\t\tEXPECT_EQ(-1, sethostname(\"test\", 4));\n+\t\tEXPECT_EQ(variant-\u003eexpected_sysadmin, errno);\n+\t} else {\n+\t\tEXPECT_EQ(0, sethostname(\"test\", 4));\n+\t}\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* Test CAP_SYS_CHROOT via chroot. */\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tif (variant-\u003eexpected_chroot) {\n+\t\tEXPECT_EQ(-1, chroot(\"/\"));\n+\t\tEXPECT_EQ(variant-\u003eexpected_chroot, errno);\n+\t} else {\n+\t\tEXPECT_EQ(0, chroot(\"/\"));\n+\t}\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+}\n+\n+/*\n+ * Verify that LANDLOCK_PERM_CAPABILITY_USE enforces when the domain is applied\n+ * without no_new_privs, using CAP_SYS_ADMIN for landlock_restrict_self()\n+ * authorization instead.  Privileged processes (e.g. container managers) can\n+ * sandbox themselves this way.\n+ */\n+TEST(cap_without_nnp)\n+{\n+\tint ruleset_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\t/* Allow CAP_SYS_CHROOT but not CAP_SYS_ADMIN. */\n+\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_CHROOT));\n+\n+\t/*\n+\t * Enforce WITHOUT NNP: landlock_restrict_self() succeeds when the\n+\t * caller has CAP_SYS_ADMIN (checked before the new domain takes\n+\t * effect).\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * CAP_SYS_ADMIN is still in effective set but Landlock denies it:\n+\t * cap_capable() returns 0, then hook_capable() returns -EPERM.\n+\t */\n+\tEXPECT_EQ(-1, sethostname(\"test\", 4));\n+\tEXPECT_EQ(EPERM, errno);\n+\n+\t/* CAP_SYS_CHROOT is allowed by the rule. */\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(0, chroot(\"/\"));\n+}\n+\n+/*\n+ * Verify that capabilities gained through user namespace ownership are still\n+ * restricted by LANDLOCK_PERM_CAPABILITY_USE.  When a process creates a user\n+ * namespace, the kernel grants CAP_FULL_SET in the new namespace via\n+ * cap_capable_helper()'s ownership bypass.  Landlock's hook_capable() must\n+ * still deny capabilities not in the allowed set, ensuring that user namespace\n+ * creation cannot be used to escape capability restrictions.\n+ */\n+TEST(cap_userns_ownership_bypass)\n+{\n+\tpid_t child;\n+\tint status;\n+\n+\tchild = fork();\n+\tASSERT_LE(0, child);\n+\tif (child == 0) {\n+\t\tint ruleset_fd;\n+\n+\t\tdisable_caps(_metadata);\n+\n+\t\truleset_fd = create_cap_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\n+\t\t/* Allow CAP_SYS_ADMIN only. */\n+\t\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t\t/*\n+\t\t * Create a user namespace.  This is unprivileged and does not\n+\t\t * require capabilities.  LANDLOCK_PERM_NAMESPACE_USE is not\n+\t\t * handled so namespace creation is unrestricted.\n+\t\t */\n+\t\tASSERT_EQ(0, unshare(CLONE_NEWUSER));\n+\n+\t\t/*\n+\t\t * After unshare(CLONE_NEWUSER), the kernel set cap_effective =\n+\t\t * CAP_FULL_SET in the new namespace.  Create a UTS namespace\n+\t\t * (requires CAP_SYS_ADMIN in the new user NS).  Landlock allows\n+\t\t * CAP_SYS_ADMIN.\n+\t\t */\n+\t\tASSERT_EQ(0, unshare(CLONE_NEWUTS))\n+\t\t{\n+\t\t\tTH_LOG(\"unshare(CLONE_NEWUTS): %s\", strerror(errno));\n+\t\t}\n+\n+\t\t/*\n+\t\t * sethostname checks against uts_ns-\u003euser_ns, which is now the\n+\t\t * new user NS.  CAP_SYS_ADMIN is allowed.\n+\t\t */\n+\t\tEXPECT_EQ(0, sethostname(\"test\", 4));\n+\n+\t\t/*\n+\t\t * chroot checks against current_user_ns(), which is the new\n+\t\t * user NS.  The process has CAP_SYS_CHROOT in cap_effective\n+\t\t * (from user NS creation), so cap_capable() returns 0.  But\n+\t\t * Landlock denies because no rule allows CAP_SYS_CHROOT.\n+\t\t */\n+\t\tEXPECT_EQ(-1, chroot(\"/\"));\n+\t\tEXPECT_EQ(EPERM, errno);\n+\n+\t\t_exit(_metadata-\u003eexit_code);\n+\t\treturn;\n+\t}\n+\n+\tASSERT_EQ(child, waitpid(child, \u0026status, 0));\n+\tif (WIFSIGNALED(status) || !WIFEXITED(status) ||\n+\t    WEXITSTATUS(status) != EXIT_SUCCESS)\n+\t\t_metadata-\u003eexit_code = KSFT_FAIL;\n+}\n+\n+/* Audit tests */\n+\n+static int matches_log_cap(int audit_fd, int cap_number)\n+{\n+\tstatic const char log_template[] = REGEX_LANDLOCK_PREFIX\n+\t\t\" blockers=perm\\\\.capability_use capability=%d $\";\n+\tchar log_match[sizeof(log_template) + 10];\n+\tint log_match_len;\n+\n+\tlog_match_len = snprintf(log_match, sizeof(log_match), log_template,\n+\t\t\t\t cap_number);\n+\tif (log_match_len \u003e= sizeof(log_match))\n+\t\treturn -E2BIG;\n+\n+\treturn audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,\n+\t\t\t\t  NULL);\n+}\n+\n+FIXTURE(cap_audit)\n+{\n+\tstruct audit_filter audit_filter;\n+\tint audit_fd;\n+};\n+\n+FIXTURE_SETUP(cap_audit)\n+{\n+\tASSERT_TRUE(is_in_init_user_ns());\n+\n+\tdisable_caps(_metadata);\n+\n+\tset_cap(_metadata, CAP_AUDIT_CONTROL);\n+\tself-\u003eaudit_fd = audit_init_with_exe_filter(\u0026self-\u003eaudit_filter);\n+\tEXPECT_LE(0, self-\u003eaudit_fd);\n+\tclear_cap(_metadata, CAP_AUDIT_CONTROL);\n+}\n+\n+FIXTURE_TEARDOWN(cap_audit)\n+{\n+\tset_cap(_metadata, CAP_AUDIT_CONTROL);\n+\tEXPECT_EQ(0, audit_cleanup(self-\u003eaudit_fd, \u0026self-\u003eaudit_filter));\n+}\n+\n+/*\n+ * Verifies that a denied capability produces the expected audit record with the\n+ * correct capability number and blocker string.\n+ */\n+TEST_F(cap_audit, denied)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\t__u64 domain_id;\n+\n+\t/* Baseline: chroot works before Landlock. */\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tASSERT_EQ(0, chroot(\"/\"));\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\t/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */\n+\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* Deny CAP_SYS_CHROOT (no allow rule). */\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(-1, chroot(\"/\"));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\n+\tEXPECT_EQ(0, matches_log_cap(self-\u003eaudit_fd, CAP_SYS_CHROOT));\n+\n+\t/*\n+\t * The domain allocation record is emitted in the same event as the\n+\t * first denial; anchor its status=allocated and enforcing pid.  Both\n+\t * access and domain records are now consumed, so none remain.\n+\t */\n+\tEXPECT_EQ(0, matches_log_domain_allocated(self-\u003eaudit_fd, getpid(),\n+\t\t\t\t\t\t  \u0026domain_id));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+TEST_F(cap_audit, allowed)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN));\n+\t/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */\n+\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, sethostname(\"test\", 4));\n+\n+\t/* No records: allowed operations never trigger audit logging. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+/*\n+ * A quieted capability denial is still denied (EPERM); only its audit record is\n+ * suppressed.  Quiet-only rule (empty allowed set).\n+ */\n+TEST_F(cap_audit, quieted)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\t/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */\n+\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));\n+\tASSERT_EQ(0,\n+\t\t  add_cap_rule_full(ruleset_fd, 0, (1ULL \u003c\u003c CAP_SYS_CHROOT)));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(-1, chroot(\"/\"));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\n+\t/*\n+\t * Positive control: CAP_SYS_ADMIN is denied but not quieted, so its\n+\t * denial IS logged.  This proves quieting is per-member, not global.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, sethostname(\"test\", 4));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/*\n+\t * The quieted CAP_SYS_CHROOT denial is suppressed; the CAP_SYS_ADMIN\n+\t * denial is logged.\n+\t */\n+\tEXPECT_EQ(0, matches_log_cap(self-\u003eaudit_fd, CAP_SYS_ADMIN));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+/*\n+ * Quieting an unknown capability bit has no effect: a rule that quiets only a\n+ * bit above CAP_LAST_CAP still logs the denial of a known capability.\n+ */\n+TEST_F(cap_audit, quiet_unknown_bit_no_effect)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\t__u64 domain_id;\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));\n+\t/* Quiet only an unknown bit (bit 63 is above CAP_LAST_CAP). */\n+\tASSERT_EQ(0, add_cap_rule_full(ruleset_fd, 0, (1ULL \u003c\u003c 63)));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(-1, chroot(\"/\"));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\n+\t/* The unknown quiet bit does not suppress the known denial. */\n+\tEXPECT_EQ(0, matches_log_cap(self-\u003eaudit_fd, CAP_SYS_CHROOT));\n+\tEXPECT_EQ(0, matches_log_domain_allocated(self-\u003eaudit_fd, getpid(),\n+\t\t\t\t\t\t  \u0026domain_id));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+/*\n+ * A capability check made with CAP_OPT_NOAUDIT is denied by Landlock but not\n+ * logged: hook_capable() emits no Landlock audit record when the caller passes\n+ * CAP_OPT_NOAUDIT, so the denial is silent here even though CAP_SYS_ADMIN is\n+ * neither allowed nor quieted.  The probe is the\n+ * ns_capable_noaudit(CAP_SYS_ADMIN) call in tmpfs simple_xattr_list(), which\n+ * decides whether trusted.* xattrs are visible to listxattr(2).\n+ */\n+TEST_F(cap_audit, noaudit_probe_not_logged)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd, fd;\n+\tchar list[256];\n+\tssize_t len;\n+\n+\t/*\n+\t * Private tmpfs holding a trusted.* xattr so its listxattr(2)\n+\t * visibility depends only on the CAP_SYS_ADMIN noaudit probe.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tASSERT_EQ(0, unshare(CLONE_NEWNS));\n+\tASSERT_EQ(0, mount(NULL, \"/\", NULL, MS_REC | MS_PRIVATE, NULL));\n+\tASSERT_EQ(0, mount(\"test\", \"/tmp\", \"tmpfs\", 0, NULL));\n+\tfd = open(\"/tmp/f\", O_CREAT | O_RDWR | O_CLOEXEC, 0600);\n+\tASSERT_LE(0, fd);\n+\tASSERT_EQ(0, fsetxattr(fd, \"trusted.landlock_test\", \"x\", 1, 0));\n+\n+\t/* Baseline: with CAP_SYS_ADMIN, the trusted xattr is listed. */\n+\tlen = flistxattr(fd, list, sizeof(list));\n+\tASSERT_LE(0, len);\n+\tASSERT_TRUE(list_has_xattr(list, len, \"trusted.landlock_test\"));\n+\n+\truleset_fd = create_cap_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\t/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */\n+\tASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));\n+\t/* CAP_SYS_ADMIN is neither allowed nor quieted. */\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * CAP_SYS_ADMIN is still effective, but Landlock denies the noaudit\n+\t * probe, so the trusted.* xattr is filtered out of the listing.\n+\t */\n+\tlen = flistxattr(fd, list, sizeof(list));\n+\tASSERT_LE(0, len);\n+\tEXPECT_FALSE(list_has_xattr(list, len, \"trusted.landlock_test\"));\n+\tEXPECT_EQ(0, close(fd));\n+\n+\t/*\n+\t * Positive control: a normal (audited) CAP_SYS_ADMIN denial in the same\n+\t * domain IS logged.  This proves the empty record set above is caused\n+\t * by CAP_OPT_NOAUDIT, not by an always-allow or always-silent bug.\n+\t */\n+\tEXPECT_EQ(-1, sethostname(\"test\", 4));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\tEXPECT_EQ(0, matches_log_cap(self-\u003eaudit_fd, CAP_SYS_ADMIN));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+TEST_HARNESS_MAIN\ndiff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h\nindex 7206d5105d664b..88c23c1089f811 100644\n--- a/tools/testing/selftests/landlock/common.h\n+++ b/tools/testing/selftests/landlock/common.h\n@@ -130,6 +130,29 @@ static void __maybe_unused clear_ambient_cap(\n \tEXPECT_EQ(0, cap_get_ambient(cap));\n }\n \n+/*\n+ * Returns true if the current process is in the initial user namespace.\n+ * Compares the readlink targets of /proc/self/ns/user and /proc/1/ns/user.\n+ */\n+static bool __maybe_unused is_in_init_user_ns(void)\n+{\n+\tchar self_buf[64], init_buf[64];\n+\tssize_t self_len, init_len;\n+\n+\tself_len = readlink(\"/proc/self/ns/user\", self_buf, sizeof(self_buf));\n+\tif (self_len \u003c= 0 || self_len \u003e= (ssize_t)sizeof(self_buf))\n+\t\treturn false;\n+\n+\tinit_len = readlink(\"/proc/1/ns/user\", init_buf, sizeof(init_buf));\n+\tif (init_len \u003c= 0 || init_len \u003e= (ssize_t)sizeof(init_buf))\n+\t\treturn false;\n+\n+\tif (self_len != init_len)\n+\t\treturn false;\n+\n+\treturn memcmp(self_buf, init_buf, self_len) == 0;\n+}\n+\n /* Receives an FD from a UNIX socket. Returns the received FD, or -errno. */\n static int __maybe_unused recv_fd(int usock)\n {\ndiff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config\nindex 8fe9b461b1fde8..d09b637bf6ca1e 100644\n--- a/tools/testing/selftests/landlock/config\n+++ b/tools/testing/selftests/landlock/config\n@@ -3,6 +3,7 @@ CONFIG_AUDIT=y\n CONFIG_CGROUPS=y\n CONFIG_CGROUP_SCHED=y\n CONFIG_INET=y\n+CONFIG_IPC_NS=y\n CONFIG_IPV6=y\n CONFIG_KEYS=y\n CONFIG_MPTCP=y\n@@ -10,10 +11,14 @@ CONFIG_MPTCP_IPV6=y\n CONFIG_NET=y\n CONFIG_NET_NS=y\n CONFIG_OVERLAY_FS=y\n+CONFIG_PID_NS=y\n CONFIG_PROC_FS=y\n CONFIG_SECURITY=y\n CONFIG_SECURITY_LANDLOCK=y\n CONFIG_SHMEM=y\n CONFIG_SYSFS=y\n+CONFIG_TIME_NS=y\n CONFIG_TMPFS=y\n CONFIG_TMPFS_XATTR=y\n+CONFIG_USER_NS=y\n+CONFIG_UTS_NS=y\ndiff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c\nindex 86e08aa6e0a7eb..5f7023658a7937 100644\n--- a/tools/testing/selftests/landlock/fs_test.c\n+++ b/tools/testing/selftests/landlock/fs_test.c\n@@ -54,13 +54,6 @@ int renameat2(int olddirfd, const char *oldpath, int newdirfd,\n }\n #endif\n \n-#ifndef open_tree\n-int open_tree(int dfd, const char *filename, unsigned int flags)\n-{\n-\treturn syscall(__NR_open_tree, dfd, filename, flags);\n-}\n-#endif\n-\n static int sys_execveat(int dirfd, const char *pathname, char *const argv[],\n \t\t\tchar *const envp[], int flags)\n {\n@@ -2454,9 +2447,9 @@ TEST_F_FORK(layout1, refer_mount_root_deny)\n \n \t/* Creates a mount object from a non-mount point. */\n \tset_cap(_metadata, CAP_SYS_ADMIN);\n-\troot_fd =\n-\t\topen_tree(AT_FDCWD, dir_s1d1,\n-\t\t\t  AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);\n+\troot_fd = sys_open_tree(AT_FDCWD, dir_s1d1,\n+\t\t\t\tAT_EMPTY_PATH | OPEN_TREE_CLONE |\n+\t\t\t\t\tOPEN_TREE_CLOEXEC);\n \tclear_cap(_metadata, CAP_SYS_ADMIN);\n \tASSERT_LE(0, root_fd);\n \ndiff --git a/tools/testing/selftests/landlock/ns_test.c b/tools/testing/selftests/landlock/ns_test.c\nnew file mode 100644\nindex 00000000000000..4377b929de937b\n--- /dev/null\n+++ b/tools/testing/selftests/landlock/ns_test.c\n@@ -0,0 +1,2380 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Landlock tests - Namespace restriction\n+ *\n+ * Copyright © 2026 Cloudflare, Inc.\n+ */\n+\n+#define _GNU_SOURCE\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003clinux/capability.h\u003e\n+#include \u003clinux/landlock.h\u003e\n+#include \u003clinux/mount.h\u003e\n+#include \u003clinux/nsfs.h\u003e\n+#include \u003csched.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003csys/ioctl.h\u003e\n+#include \u003csys/prctl.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003csyscall.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"audit.h\"\n+#include \"common.h\"\n+\n+/*\n+ * Max length for /proc/self/ns/\u003cname\u003e paths (longest: \"/proc/self/ns/cgroup\").\n+ */\n+#define NS_PROC_PATH_MAX 32\n+\n+static int create_ns_ruleset(void)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t};\n+\n+\treturn landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+}\n+\n+static int add_ns_rule(int ruleset_fd, __u64 ns_type)\n+{\n+\tconst struct landlock_namespace_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = ns_type,\n+\t};\n+\n+\treturn landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, \u0026attr, 0);\n+}\n+\n+static int add_ns_rule_full(int ruleset_fd, __u64 allowed, __u64 quiet)\n+{\n+\tconst struct landlock_namespace_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = allowed,\n+\t\t.quiet_namespace_types = quiet,\n+\t};\n+\n+\treturn landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, \u0026attr, 0);\n+}\n+\n+/*\n+ * Returns the /proc/self/NS entry name for a given CLONE_NEW* type, or NULL if\n+ * unknown.  Used to check kernel support without side effects.\n+ */\n+static const char *ns_proc_name(__u64 ns_type)\n+{\n+\tswitch (ns_type) {\n+\tcase CLONE_NEWNS:\n+\t\treturn \"mnt\";\n+\tcase CLONE_NEWCGROUP:\n+\t\treturn \"cgroup\";\n+\tcase CLONE_NEWUTS:\n+\t\treturn \"uts\";\n+\tcase CLONE_NEWIPC:\n+\t\treturn \"ipc\";\n+\tcase CLONE_NEWUSER:\n+\t\treturn \"user\";\n+\tcase CLONE_NEWPID:\n+\t\treturn \"pid\";\n+\tcase CLONE_NEWNET:\n+\t\treturn \"net\";\n+\tcase CLONE_NEWTIME:\n+\t\treturn \"time\";\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static bool ns_is_supported(__u64 ns_type, char *proc_path, size_t size)\n+{\n+\tconst char *ns_name;\n+\n+\tns_name = ns_proc_name(ns_type);\n+\tif (!ns_name)\n+\t\treturn false;\n+\n+\tsnprintf(proc_path, size, \"/proc/self/ns/%s\", ns_name);\n+\treturn access(proc_path, F_OK) == 0;\n+}\n+\n+/* Rule validation tests */\n+\n+TEST(add_rule_bad_attr)\n+{\n+\tconst struct landlock_ruleset_attr cap_only_attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\tint ruleset_fd;\n+\tstruct landlock_namespace_attr attr = {};\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\t/* Empty perm selector returns ENOMSG. */\n+\tattr.perm = 0;\n+\tattr.allowed_namespace_types = CLONE_NEWUTS;\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(ENOMSG, errno);\n+\n+\t/* perm with unhandled bit. */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE | LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_namespace_types = CLONE_NEWUTS;\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(EINVAL, errno);\n+\n+\t/* perm with wrong type. */\n+\tattr.perm = LANDLOCK_PERM_CAPABILITY_USE;\n+\tattr.allowed_namespace_types = CLONE_NEWUTS;\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(EINVAL, errno);\n+\n+\t/*\n+\t * Unknown namespace bits (e.g. bit 63) are silently accepted for\n+\t * forward compatibility.  Only known CLONE_NEW* bits are stored.\n+\t */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = 1ULL \u003c\u003c 63;\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/* Useless rule: neither allowed nor quiet types set returns ENOMSG. */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = 0;\n+\tattr.quiet_namespace_types = 0;\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(ENOMSG, errno);\n+\n+\t/* Quiet-only rule (empty allowed set) is legal. */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = 0;\n+\tattr.quiet_namespace_types = CLONE_NEWUTS;\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/* Allow and quiet in the same rule. */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = CLONE_NEWNET;\n+\tattr.quiet_namespace_types = CLONE_NEWUTS;\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\tattr.quiet_namespace_types = 0;\n+\n+\t/*\n+\t * Bit 1 is not a CLONE_NEW* value but is silently accepted for forward\n+\t * compatibility (no hole rejection).\n+\t */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = (1ULL \u003c\u003c 1);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/* Multi-bit values are valid (bitmask allows multiple types). */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = CLONE_NEWUTS | CLONE_NEWNET;\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/*\n+\t * LANDLOCK_ADD_RULE_QUIET (and any other flag) is filesystem/network\n+\t * only and must be rejected for namespace rules, even with an\n+\t * otherwise-valid attr (cases.md Q-E5).\n+\t */\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = CLONE_NEWUTS;\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t\t\u0026attr, LANDLOCK_ADD_RULE_QUIET));\n+\tASSERT_EQ(EINVAL, errno);\n+\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * Ruleset handles PERM_CAPABILITY_USE but not PERM_NAMESPACE_USE:\n+\t * adding a namespace rule must be rejected.\n+\t */\n+\truleset_fd = landlock_create_ruleset(\u0026cap_only_attr,\n+\t\t\t\t\t     sizeof(cap_only_attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tattr.perm = LANDLOCK_PERM_NAMESPACE_USE;\n+\tattr.allowed_namespace_types = CLONE_NEWUTS;\n+\tASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t\t\u0026attr, 0));\n+\tASSERT_EQ(EINVAL, errno);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+}\n+\n+/*\n+ * Unknown namespace types in the upper range are silently accepted (allow-list:\n+ * they have no effect since the kernel never checks them).\n+ */\n+TEST(add_rule_unknown)\n+{\n+\tint ruleset_fd;\n+\tstruct landlock_namespace_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t};\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\t/*\n+\t * Bit 31 is in the lower 32 bits but not a CLONE_NEW* value.  Silently\n+\t * accepted for forward compatibility (no hole rejection).\n+\t */\n+\tattr.allowed_namespace_types = 1ULL \u003c\u003c 31;\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/* Bit 32 is in the unknown upper range: silently accepted. */\n+\tattr.allowed_namespace_types = 1ULL \u003c\u003c 32;\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+}\n+\n+/*\n+ * A rule that lists only namespace bits unknown to the running kernel is\n+ * accepted by landlock_add_rule() but has no runtime effect: once the domain is\n+ * enforced, any actual CLONE_NEW* operation is still denied by the per-category\n+ * deny-by-default behaviour.  This documents the forward-compatibility\n+ * contract: unknown bits are silently accepted so the same policy can be loaded\n+ * across kernels, but they never grant a permission that the running kernel\n+ * knows nothing about.\n+ */\n+TEST(add_rule_unknown_no_runtime_effect)\n+{\n+\tconst struct landlock_ruleset_attr ruleset_attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t};\n+\tstruct landlock_namespace_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t/* Only unknown bits: bit 31 (in lower 32) and bit 32. */\n+\t\t.allowed_namespace_types = (1ULL \u003c\u003c 31) | (1ULL \u003c\u003c 32),\n+\t};\n+\tint ruleset_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd =\n+\t\tlandlock_create_ruleset(\u0026ruleset_attr, sizeof(ruleset_attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/*\n+\t * Baseline: absent the domain, creating CLONE_NEWUTS succeeds.  This\n+\t * ensures the EPERM below is attributable to Landlock rather than to a\n+\t * missing capability, and fails if the hook always allows.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tASSERT_EQ(0, unshare(CLONE_NEWUTS));\n+\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * CLONE_NEWUTS is a real, known CLONE_NEW* type but was not authorised\n+\t * by the rule above; deny-by-default applies.\n+\t */\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * The install-hook mirror of add_rule_unknown_no_runtime_effect: a rule listing\n+ * only unknown namespace bits has no runtime effect on setns(2) either.  Once\n+ * enforced, entering a known namespace type is still denied by the per-category\n+ * deny-by-default behaviour.\n+ */\n+TEST(add_rule_unknown_no_runtime_effect_setns)\n+{\n+\tconst struct landlock_ruleset_attr ruleset_attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t};\n+\tstruct landlock_namespace_attr attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t/* Only unknown bits: bit 31 (in lower 32) and bit 32. */\n+\t\t.allowed_namespace_types = (1ULL \u003c\u003c 31) | (1ULL \u003c\u003c 32),\n+\t};\n+\tint ruleset_fd, ns_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\t/* Open the NS FD before enforcing the domain. */\n+\tns_fd = open(\"/proc/self/ns/uts\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\truleset_fd =\n+\t\tlandlock_create_ruleset(\u0026ruleset_attr, sizeof(ruleset_attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026attr, 0));\n+\n+\t/*\n+\t * Baseline: absent the domain, entering the process's own UTS namespace\n+\t * succeeds, so the later EPERM is attributable to Landlock rather than\n+\t * to a missing capability, and fails if the hook always allows.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tASSERT_EQ(0, setns(ns_fd, CLONE_NEWUTS));\n+\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * CLONE_NEWUTS is a real, known CLONE_NEW* type but was not authorised\n+\t * by the rule above; deny-by-default applies at the install hook.\n+\t */\n+\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, close(ns_fd));\n+}\n+\n+/* Namespace creation tests (variant-based positive/negative) */\n+\n+/* clang-format off */\n+FIXTURE(ns_create) {\n+\tchar proc_path[NS_PROC_PATH_MAX];\n+};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(ns_create)\n+{\n+\tconst __u64 namespace_types;\n+\tconst bool is_sandboxed;\n+\tconst bool has_rule;\n+\tconst bool drop_all_caps;\n+\tconst int expected_result;\n+};\n+\n+/*\n+ * Unsandboxed baseline: no Landlock domain is enforced. User namespace creation\n+ * should succeed without any restriction.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, user_unsandboxed) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWUSER,\n+\t.is_sandboxed = false,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+\n+/*\n+ * User namespace creation denied: handled by Landlock but no rule allows\n+ * CLONE_NEWUSER.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, user_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWUSER,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/* User namespace creation allowed: Landlock rule permits CLONE_NEWUSER. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, user_allowed) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWUSER,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+\n+/*\n+ * User namespace creation while unprivileged: the process has no capabilities\n+ * but unshare(CLONE_NEWUSER) is an unprivileged operation so it still succeeds.\n+ * The Landlock rule allows it.  For setns, the capability check (CAP_SYS_ADMIN)\n+ * fails first since the process has no capabilities, yielding EPERM.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, user_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWUSER,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = 0,\n+};\n+\n+/*\n+ * Unsandboxed baseline for non-user namespace: no Landlock domain, process has\n+ * CAP_SYS_ADMIN.  UTS creation should succeed.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, uts_unsandboxed) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWUTS,\n+\t.is_sandboxed = false,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+\n+/*\n+ * Non-user namespace denied: process has CAP_SYS_ADMIN (passes ns_capable), but\n+ * Landlock denies (no rule).\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, uts_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWUTS,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/*\n+ * Non-user namespace allowed: process has CAP_SYS_ADMIN and Landlock rule\n+ * permits CLONE_NEWUTS.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, uts_allowed) {\n+\t.namespace_types = CLONE_NEWUTS,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+/* clang-format on */\n+\n+/*\n+ * Unprivileged namespace creation: process lacks CAP_SYS_ADMIN, so the kernel\n+ * denies creation regardless of Landlock rules.  Landlock cannot authorize what\n+ * the kernel denied (LSM hooks are restriction-only).  The rule is present to\n+ * verify Landlock does not change the error code.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, uts_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWUTS,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, ipc_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWIPC,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, ipc_allowed) {\n+\t.namespace_types = CLONE_NEWIPC,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, ipc_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWIPC,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, mnt_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWNS,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, mnt_allowed) {\n+\t.namespace_types = CLONE_NEWNS,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, mnt_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWNS,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, cgroup_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWCGROUP,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, cgroup_allowed) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWCGROUP,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, cgroup_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWCGROUP,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, pid_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWPID,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, pid_allowed) {\n+\t.namespace_types = CLONE_NEWPID,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, pid_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWPID,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, net_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWNET,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, net_allowed) {\n+\t.namespace_types = CLONE_NEWNET,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, net_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWNET,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, time_denied) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWTIME,\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.drop_all_caps = false,\n+\t.expected_result = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, time_allowed) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWTIME,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = false,\n+\t.expected_result = 0,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create, time_unprivileged) {\n+\t/* clang-format on */\n+\t.namespace_types = CLONE_NEWTIME,\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.drop_all_caps = true,\n+\t.expected_result = EPERM,\n+};\n+\n+FIXTURE_SETUP(ns_create)\n+{\n+\tASSERT_TRUE(ns_is_supported(variant-\u003enamespace_types, self-\u003eproc_path,\n+\t\t\t\t    sizeof(self-\u003eproc_path)))\n+\t{\n+\t\tTH_LOG(\"Namespace type 0x%llx not supported\",\n+\t\t       (unsigned long long)variant-\u003enamespace_types);\n+\t}\n+\n+\tif (variant-\u003edrop_all_caps)\n+\t\tdrop_caps(_metadata);\n+\telse\n+\t\tdisable_caps(_metadata);\n+}\n+\n+FIXTURE_TEARDOWN(ns_create)\n+{\n+}\n+\n+TEST_F(ns_create, unshare)\n+{\n+\tint ruleset_fd, err;\n+\n+\tif (variant-\u003eis_sandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd,\n+\t\t\t\t\t\t variant-\u003enamespace_types));\n+\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\t/*\n+\t * Non-user namespaces need CAP_SYS_ADMIN for the privileged path.  User\n+\t * namespaces and unprivileged tests skip this.\n+\t */\n+\tif (!variant-\u003edrop_all_caps \u0026\u0026\n+\t    variant-\u003enamespace_types != CLONE_NEWUSER)\n+\t\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\terr = unshare(variant-\u003enamespace_types);\n+\tif (variant-\u003eexpected_result) {\n+\t\tEXPECT_EQ(-1, err);\n+\t\tEXPECT_EQ(variant-\u003eexpected_result, errno);\n+\t} else {\n+\t\tEXPECT_EQ(0, err);\n+\t}\n+\n+\tif (!variant-\u003edrop_all_caps \u0026\u0026\n+\t    variant-\u003enamespace_types != CLONE_NEWUSER)\n+\t\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * clone3 exercises a different kernel entry point than unshare: it goes through\n+ * kernel_clone() -\u003e copy_process() -\u003e copy_namespaces() -\u003e\n+ * create_new_namespaces().  Both paths converge at __ns_common_init() -\u003e\n+ * security_namespace_init(), but the entry point and argument handling differ.\n+ */\n+TEST_F(ns_create, clone3)\n+{\n+\tint ruleset_fd, status;\n+\tpid_t pid;\n+\tstruct clone_args args = {};\n+\n+\tif (variant-\u003eis_sandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd,\n+\t\t\t\t\t\t variant-\u003enamespace_types));\n+\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\tif (!variant-\u003edrop_all_caps \u0026\u0026\n+\t    variant-\u003enamespace_types != CLONE_NEWUSER)\n+\t\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\targs.flags = variant-\u003enamespace_types;\n+\targs.exit_signal = SIGCHLD;\n+\tpid = sys_clone3(\u0026args, sizeof(args));\n+\tif (pid == 0)\n+\t\t_exit(EXIT_SUCCESS);\n+\n+\tif (variant-\u003eexpected_result) {\n+\t\tEXPECT_EQ(-1, pid);\n+\t\tEXPECT_EQ(variant-\u003eexpected_result, errno);\n+\t} else {\n+\t\tEXPECT_LE(0, pid);\n+\t\tASSERT_EQ(pid, waitpid(pid, \u0026status, 0));\n+\t\tASSERT_EQ(1, WIFEXITED(status));\n+\t\tASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));\n+\t}\n+\n+\tif (!variant-\u003edrop_all_caps \u0026\u0026\n+\t    variant-\u003enamespace_types != CLONE_NEWUSER)\n+\t\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * setns exercises the namespace install path: validate_ns() -\u003e\n+ * security_namespace_install() -\u003e hook_namespace_install().  This is a\n+ * different LSM hook than creation, so it must be tested separately for each\n+ * type.\n+ *\n+ * Mount namespace setns requires both CAP_SYS_ADMIN and CAP_SYS_CHROOT (checked\n+ * by mntns_install), so the allowed variant sets both.\n+ */\n+TEST_F(ns_create, setns)\n+{\n+\tint ruleset_fd, ns_fd, err, expected;\n+\n+\t/*\n+\t * setns into the process's own user NS returns EINVAL from\n+\t * userns_install() (rejects re-entry), but when Landlock denies the\n+\t * operation, security_namespace_install() returns EPERM before\n+\t * userns_install() runs.\n+\t */\n+\tif (variant-\u003enamespace_types == CLONE_NEWUSER \u0026\u0026\n+\t    !variant-\u003eexpected_result) {\n+\t\texpected = EINVAL;\n+\t} else {\n+\t\texpected = variant-\u003eexpected_result;\n+\t}\n+\n+\t/* Open the NS FD before enforcing the domain. */\n+\tns_fd = open(self-\u003eproc_path, O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\tif (variant-\u003eis_sandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd,\n+\t\t\t\t\t\t variant-\u003enamespace_types));\n+\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\tif (!variant-\u003edrop_all_caps) {\n+\t\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\t\t/*\n+\t\t * mntns_install() requires CAP_SYS_CHROOT in addition to\n+\t\t * CAP_SYS_ADMIN.\n+\t\t */\n+\t\tif (variant-\u003enamespace_types == CLONE_NEWNS)\n+\t\t\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\t}\n+\n+\terr = setns(ns_fd, variant-\u003enamespace_types);\n+\tif (expected) {\n+\t\tEXPECT_EQ(-1, err);\n+\t\tEXPECT_EQ(expected, errno);\n+\t} else {\n+\t\tEXPECT_EQ(0, err);\n+\t}\n+\n+\tif (!variant-\u003edrop_all_caps) {\n+\t\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\t\tif (variant-\u003enamespace_types == CLONE_NEWNS)\n+\t\t\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\t}\n+\n+\tEXPECT_EQ(0, close(ns_fd));\n+}\n+\n+/* Additional namespace creation tests */\n+\n+/*\n+ * When LANDLOCK_PERM_NAMESPACE_USE is not handled by any domain, namespace\n+ * creation must produce the same result as without Landlock.  Unlike the\n+ * unsandboxed variants of ns_create (which have no domain at all), this test\n+ * verifies that a domain handling only FS access does not interfere with\n+ * namespace operations.\n+ */\n+TEST(ns_create_unhandled)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,\n+\t};\n+\tint ruleset_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* User namespace creation should still work (unhandled). */\n+\tEXPECT_EQ(0, unshare(CLONE_NEWUSER));\n+}\n+\n+/*\n+ * Layer stacking: both layers must allow CLONE_NEWUSER for the operation to\n+ * succeed.  Variants exercise the three combinations of per-layer allow/deny\n+ * that exercise distinct semantics; the (deny, deny) combination is omitted\n+ * because it is covered by every other \"deny\" test in this file.\n+ */\n+/* clang-format off */\n+FIXTURE(ns_stacking) {};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(ns_stacking)\n+{\n+\tbool first_layer_allows;\n+\tbool second_layer_allows;\n+};\n+\n+/* Layer 1 allows, layer 2 denies -\u003e child denies. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_stacking, deny) {\n+\t/* clang-format on */\n+\t.first_layer_allows = true,\n+\t.second_layer_allows = false,\n+};\n+\n+/* Both layers allow CLONE_NEWUSER -\u003e operation succeeds. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_stacking, allow) {\n+\t/* clang-format on */\n+\t.first_layer_allows = true,\n+\t.second_layer_allows = true,\n+};\n+\n+/*\n+ * Layer 1 denies, layer 2 allows -\u003e still denied: a child layer cannot grant\n+ * what an ancestor layer withheld.  This complements the\n+ * parent-allows/child-denies variant above; together they verify the walker\n+ * checks both layers and accepts only the (allow, allow) cell.\n+ */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_stacking, parent_denies) {\n+\t/* clang-format on */\n+\t.first_layer_allows = false,\n+\t.second_layer_allows = true,\n+};\n+\n+FIXTURE_SETUP(ns_stacking)\n+{\n+\tdisable_caps(_metadata);\n+}\n+\n+FIXTURE_TEARDOWN(ns_stacking)\n+{\n+}\n+\n+/*\n+ * Verify that any layer can deny an operation: enforcement requires all layers\n+ * to allow.  Variants exercise the three combinations that exercise distinct\n+ * walker paths (allow/deny, allow/allow, deny/allow); only allow/allow lets the\n+ * operation through.\n+ */\n+TEST_F(ns_stacking, two_layers)\n+{\n+\tint ruleset_fd;\n+\tconst bool expect_success = variant-\u003efirst_layer_allows \u0026\u0026\n+\t\t\t\t    variant-\u003esecond_layer_allows;\n+\n+\t/* First layer: allow or deny depending on variant. */\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tif (variant-\u003efirst_layer_allows)\n+\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUSER));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* Second layer: allow or deny depending on variant. */\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tif (variant-\u003esecond_layer_allows)\n+\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUSER));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tif (expect_success) {\n+\t\tEXPECT_EQ(0, unshare(CLONE_NEWUSER));\n+\t} else {\n+\t\tEXPECT_EQ(-1, unshare(CLONE_NEWUSER));\n+\t\tEXPECT_EQ(EPERM, errno);\n+\t}\n+}\n+\n+/*\n+ * Combined capability and namespace permissions in a single domain.  Verifies\n+ * that both permission types can coexist and are enforced independently.\n+ */\n+TEST(combined_cap_ns)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_CAPABILITY_USE |\n+\t\t\t\tLANDLOCK_PERM_NAMESPACE_USE,\n+\t};\n+\tconst struct landlock_capability_attr cap_attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN),\n+\t};\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWUSER,\n+\t};\n+\tint ruleset_fd;\n+\n+\t/* Isolate hostname changes from other tests. */\n+\tASSERT_EQ(0, unshare(CLONE_NEWUTS));\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026cap_attr, 0));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* CAP_SYS_ADMIN use allowed by capability rule. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, sethostname(\"test\", 4));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* CAP_SYS_CHROOT denied (not in allowed capability rules). */\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(-1, chroot(\"/\"));\n+\tEXPECT_EQ(EPERM, errno);\n+\n+\t/*\n+\t * UTS namespace creation denied by Landlock (not in allowed namespace\n+\t * rules).  CAP_SYS_ADMIN is needed for the kernel's ns_capable() check\n+\t * to pass, so that Landlock's hook is actually reached.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* User namespace creation allowed by namespace rule. */\n+\tEXPECT_EQ(0, unshare(CLONE_NEWUSER));\n+}\n+\n+/*\n+ * Partial allow: one namespace type is allowed, another is denied.  Verifies\n+ * that rules are per-type.\n+ */\n+TEST(ns_create_partial)\n+{\n+\tint ruleset_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\n+\t/* Only allow UTS namespace creation. */\n+\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));\n+\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* UTS namespace should be allowed. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, unshare(CLONE_NEWUTS));\n+\n+\t/* User namespace should be denied (no rule). */\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUSER));\n+\tEXPECT_EQ(EPERM, errno);\n+}\n+\n+/*\n+ * open_tree(2) and fsmount(2) acquire a file descriptor referring to a\n+ * newly-created mount namespace.  Both call paths funnel into\n+ * security_namespace_init() with CLONE_NEWNS, gated by\n+ * LANDLOCK_PERM_NAMESPACE_USE.  Without coverage here, regressions in those\n+ * paths would slip past the suite.\n+ */\n+/* clang-format off */\n+FIXTURE(ns_mount_fd) {};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(ns_mount_fd)\n+{\n+\tbool sandboxed;\n+\tbool has_rule;\n+\tint expected_errno;\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_mount_fd, denied) {\n+\t/* clang-format on */\n+\t.sandboxed = true,\n+\t.has_rule = false,\n+\t.expected_errno = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_mount_fd, allowed) {\n+\t/* clang-format on */\n+\t.sandboxed = true,\n+\t.has_rule = true,\n+\t.expected_errno = 0,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_mount_fd, unsandboxed) {\n+\t/* clang-format on */\n+\t.sandboxed = false,\n+\t.has_rule = false,\n+\t.expected_errno = 0,\n+};\n+\n+FIXTURE_SETUP(ns_mount_fd)\n+{\n+}\n+\n+FIXTURE_TEARDOWN(ns_mount_fd)\n+{\n+}\n+\n+/*\n+ * open_tree(OPEN_TREE_CLONE) creates an anonymous mount namespace to hold the\n+ * cloned mount tree.  hook_namespace_init() fires with CLONE_NEWNS.\n+ */\n+TEST_F(ns_mount_fd, open_tree_clone)\n+{\n+\tint ruleset_fd, fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\tif (variant-\u003esandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tfd = sys_open_tree(AT_FDCWD, \"/\",\n+\t\t\t   OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE);\n+\tif (variant-\u003eexpected_errno) {\n+\t\tEXPECT_EQ(-1, fd);\n+\t\tEXPECT_EQ(variant-\u003eexpected_errno, errno);\n+\t} else {\n+\t\tASSERT_LE(0, fd);\n+\t\tEXPECT_EQ(0, close(fd));\n+\t}\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * open_tree(OPEN_TREE_NAMESPACE) clones the mount tree into a new\n+ * (non-anonymous) mount namespace.  Same hook (CLONE_NEWNS) but a different\n+ * code path inside fs/namespace.c (open_new_namespace -\u003e alloc_mnt_ns).\n+ * OPEN_TREE_NAMESPACE and OPEN_TREE_CLONE are mutually exclusive.\n+ */\n+TEST_F(ns_mount_fd, open_tree_namespace)\n+{\n+\tint ruleset_fd, fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\tif (variant-\u003esandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tfd = sys_open_tree(AT_FDCWD, \"/\",\n+\t\t\t   OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC |\n+\t\t\t\t   AT_RECURSIVE);\n+\tif (variant-\u003eexpected_errno) {\n+\t\tEXPECT_EQ(-1, fd);\n+\t\tEXPECT_EQ(variant-\u003eexpected_errno, errno);\n+\t} else {\n+\t\tASSERT_LE(0, fd);\n+\t\tEXPECT_EQ(0, close(fd));\n+\t}\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * fsmount(2) without FSMOUNT_NAMESPACE creates an anonymous mount namespace to\n+ * attach the new superblock.  hook_namespace_init() fires with CLONE_NEWNS.\n+ * The fs context (fsopen + fsconfig) is set up before sandboxing because\n+ * Landlock here only handles the namespace permission.\n+ */\n+TEST_F(ns_mount_fd, fsmount_default)\n+{\n+\tint ruleset_fd, fs_fd, mnt_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tfs_fd = sys_fsopen(\"tmpfs\", 0);\n+\tASSERT_LE(0, fs_fd);\n+\tASSERT_EQ(0, sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));\n+\n+\tif (variant-\u003esandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\tmnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC, 0);\n+\tif (variant-\u003eexpected_errno) {\n+\t\tEXPECT_EQ(-1, mnt_fd);\n+\t\tEXPECT_EQ(variant-\u003eexpected_errno, errno);\n+\t} else {\n+\t\tASSERT_LE(0, mnt_fd);\n+\t\tEXPECT_EQ(0, close(mnt_fd));\n+\t}\n+\tEXPECT_EQ(0, close(fs_fd));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * fsmount(2) with FSMOUNT_NAMESPACE creates a (non-anonymous) mount namespace\n+ * for the new mount.  Same hook as the default path, different code path.\n+ */\n+TEST_F(ns_mount_fd, fsmount_namespace)\n+{\n+\tint ruleset_fd, fs_fd, mnt_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tfs_fd = sys_fsopen(\"tmpfs\", 0);\n+\tASSERT_LE(0, fs_fd);\n+\tASSERT_EQ(0, sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));\n+\n+\tif (variant-\u003esandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\tmnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC | FSMOUNT_NAMESPACE, 0);\n+\tif (variant-\u003eexpected_errno) {\n+\t\tEXPECT_EQ(-1, mnt_fd);\n+\t\tEXPECT_EQ(variant-\u003eexpected_errno, errno);\n+\t} else {\n+\t\tASSERT_LE(0, mnt_fd);\n+\t\tEXPECT_EQ(0, close(mnt_fd));\n+\t}\n+\tEXPECT_EQ(0, close(fs_fd));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * unshare(2) with multiple CLONE_NEW* flags: when LANDLOCK_PERM_NAMESPACE_USE\n+ * denies any of the requested types, the entire syscall fails with EPERM.  This\n+ * documents the kernel's atomic behavior: namespaces are created sequentially\n+ * in __ns_common_init() via copy_namespaces(), and the first Landlock denial\n+ * rolls back the whole operation.  Mixing CLONE_NEWUSER (no capability check)\n+ * with another CLONE_NEW* type is the typical container-runtime bootstrap\n+ * pattern.\n+ */\n+/* clang-format off */\n+FIXTURE(ns_create_multi_flag) {};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(ns_create_multi_flag)\n+{\n+\t__u64 allowed_types;\n+\tint expected_errno;\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create_multi_flag, partial_denied) {\n+\t/* clang-format on */\n+\t/* User namespace allowed; UTS namespace denied. */\n+\t.allowed_types = CLONE_NEWUSER,\n+\t.expected_errno = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_create_multi_flag, both_allowed) {\n+\t/* clang-format on */\n+\t.allowed_types = CLONE_NEWUSER | CLONE_NEWUTS,\n+\t.expected_errno = 0,\n+};\n+\n+FIXTURE_SETUP(ns_create_multi_flag)\n+{\n+}\n+\n+FIXTURE_TEARDOWN(ns_create_multi_flag)\n+{\n+}\n+\n+TEST_F(ns_create_multi_flag, unshare)\n+{\n+\tint ruleset_fd, status, err;\n+\tpid_t child;\n+\n+\tdisable_caps(_metadata);\n+\n+\t/* Run unshare(2) in a child to avoid polluting the test process. */\n+\tchild = fork();\n+\tASSERT_LE(0, child);\n+\n+\tif (child == 0) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, variant-\u003eallowed_types));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t\terr = unshare(CLONE_NEWUSER | CLONE_NEWUTS);\n+\t\tif (variant-\u003eexpected_errno) {\n+\t\t\tEXPECT_EQ(-1, err);\n+\t\t\tEXPECT_EQ(variant-\u003eexpected_errno, errno);\n+\t\t} else {\n+\t\t\tEXPECT_EQ(0, err);\n+\t\t}\n+\t\t_exit(_metadata-\u003eexit_code);\n+\t}\n+\n+\tASSERT_EQ(child, waitpid(child, \u0026status, 0));\n+\tASSERT_EQ(1, WIFEXITED(status));\n+\tASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));\n+}\n+\n+/* clang-format off */\n+FIXTURE(setns_cross_process) {};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(setns_cross_process)\n+{\n+\tbool is_sandboxed;\n+\tbool has_rule;\n+\tint expected_setns;\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(setns_cross_process, denied) {\n+\t/* clang-format on */\n+\t.is_sandboxed = true,\n+\t.has_rule = false,\n+\t.expected_setns = EPERM,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(setns_cross_process, sandboxed_allowed) {\n+\t/* clang-format on */\n+\t.is_sandboxed = true,\n+\t.has_rule = true,\n+\t.expected_setns = 0,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(setns_cross_process, unsandboxed) {\n+\t/* clang-format on */\n+\t.is_sandboxed = false,\n+\t.has_rule = false,\n+\t.expected_setns = 0,\n+};\n+\n+FIXTURE_SETUP(setns_cross_process)\n+{\n+}\n+\n+FIXTURE_TEARDOWN(setns_cross_process)\n+{\n+}\n+\n+/*\n+ * setns into a child's UTS namespace: when sandboxed with\n+ * LANDLOCK_PERM_NAMESPACE_USE denying UTS, the rule-based check applies\n+ * regardless of which process created the namespace.  This fixture exercises\n+ * only CLONE_NEWUTS; the same enforcement applies to every namespace type (see\n+ * hook_namespace_install() in security/landlock/ns.c), so per-type variants\n+ * would not exercise different code paths.\n+ */\n+TEST_F(setns_cross_process, setns)\n+{\n+\tint ruleset_fd, ns_fd, status;\n+\tpid_t child;\n+\tint pipe_parent[2], pipe_child[2];\n+\tchar buf, path[64];\n+\n+\tdisable_caps(_metadata);\n+\n+\t/*\n+\t * Enable dumpable so the parent can read /proc/\u003cchild\u003e/ns/uts.  Without\n+\t * this, ptrace access checks (PTRACE_MODE_READ) prevent opening another\n+\t * process's namespace entries.\n+\t */\n+\tASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0));\n+\n+\tASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));\n+\tASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));\n+\n+\tchild = fork();\n+\tASSERT_LE(0, child);\n+\n+\tif (child == 0) {\n+\t\tEXPECT_EQ(0, close(pipe_parent[1]));\n+\t\tEXPECT_EQ(0, close(pipe_child[0]));\n+\n+\t\t/* Child: create a UTS namespace. */\n+\t\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\t\tASSERT_EQ(0, unshare(CLONE_NEWUTS));\n+\n+\t\tdrop_caps(_metadata);\n+\t\tASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0));\n+\n+\t\t/* Signal parent that the namespace is ready. */\n+\t\tASSERT_EQ(1, write(pipe_child[1], \".\", 1));\n+\n+\t\t/* Wait for parent to finish testing. */\n+\t\tASSERT_EQ(1, read(pipe_parent[0], \u0026buf, 1));\n+\t\t_exit(_metadata-\u003eexit_code);\n+\t}\n+\n+\tEXPECT_EQ(0, close(pipe_parent[0]));\n+\tEXPECT_EQ(0, close(pipe_child[1]));\n+\n+\t/* Wait for child namespace. */\n+\tASSERT_EQ(1, read(pipe_child[0], \u0026buf, 1));\n+\tEXPECT_EQ(0, close(pipe_child[0]));\n+\n+\t/* Open the child's NS FD BEFORE creating the domain. */\n+\tsnprintf(path, sizeof(path), \"/proc/%d/ns/uts\", child);\n+\tns_fd = open(path, O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\tif (variant-\u003eis_sandboxed) {\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tif (variant-\u003ehas_rule)\n+\t\t\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\t}\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tif (variant-\u003eexpected_setns) {\n+\t\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));\n+\t\tEXPECT_EQ(variant-\u003eexpected_setns, errno);\n+\t} else {\n+\t\tEXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS));\n+\t}\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, close(ns_fd));\n+\n+\t/* Release child. */\n+\tASSERT_EQ(1, write(pipe_parent[1], \".\", 1));\n+\tEXPECT_EQ(0, close(pipe_parent[1]));\n+\tASSERT_EQ(child, waitpid(child, \u0026status, 0));\n+\tASSERT_EQ(1, WIFEXITED(status));\n+\tASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));\n+}\n+\n+/*\n+ * Verify that LANDLOCK_PERM_NAMESPACE_USE and LANDLOCK_PERM_CAPABILITY_USE\n+ * apply simultaneously: creating/entering a non-user namespace requires both\n+ * the namespace type to be allowed AND CAP_SYS_ADMIN to be allowed.  User\n+ * namespace creation is the exception (no capable() call from the kernel).\n+ */\n+TEST(setns_and_create)\n+{\n+\tint ruleset_fd, ns_fd;\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\tLANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWUTS,\n+\t};\n+\tconst struct landlock_capability_attr cap_attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN),\n+\t};\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026cap_attr, 0));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* UTS unshare: allowed by NS rule + CAP_SYS_ADMIN allowed. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tASSERT_EQ(0, unshare(CLONE_NEWUTS));\n+\n+\t/* IPC unshare: denied by NS rule (type not allowed). */\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWIPC));\n+\tEXPECT_EQ(EPERM, errno);\n+\n+\t/* setns into current UTS: allowed by NS rule. */\n+\tns_fd = open(\"/proc/self/ns/uts\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\tEXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, close(ns_fd));\n+\n+\t/*\n+\t * User namespace creation: only LANDLOCK_PERM_NAMESPACE_USE needed (no\n+\t * capable() call from the kernel for user NS).  Denied because\n+\t * CLONE_NEWUSER is not in the allowed namespace types.\n+\t */\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUSER));\n+\tEXPECT_EQ(EPERM, errno);\n+}\n+\n+/*\n+ * Verify that LANDLOCK_PERM_CAPABILITY_USE can deny the CAP_SYS_ADMIN check\n+ * that the kernel performs before the Landlock namespace hook is reached.  The\n+ * NS type is allowed but the required capability is not, so the operation fails\n+ * on the capability check.\n+ *\n+ * User namespace creation is the exception: no capable() call, so the operation\n+ * succeeds with just LANDLOCK_PERM_NAMESPACE_USE.\n+ */\n+TEST(two_perm_cap_denied)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\tLANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWUTS | CLONE_NEWUSER,\n+\t};\n+\t/* CAP_SYS_ADMIN is NOT allowed. */\n+\tconst struct landlock_capability_attr cap_attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_CHROOT),\n+\t};\n+\tint ruleset_fd, ns_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026cap_attr, 0));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * UTS creation: the process holds CAP_SYS_ADMIN but Landlock denies it\n+\t * (not in the cap rule), so the kernel's ns_capable(CAP_SYS_ADMIN) gate\n+\t * fails before the namespace hook is reached.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\n+\t/*\n+\t * setns into the current UTS namespace: the type is allowed but\n+\t * CAP_SYS_ADMIN is denied, so the kernel's ns_capable(CAP_SYS_ADMIN)\n+\t * gate fails before the namespace hook is reached (the setns capability\n+\t * leg, complementing the unshare/create leg above).\n+\t */\n+\tns_fd = open(\"/proc/self/ns/uts\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tEXPECT_EQ(0, close(ns_fd));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/*\n+\t * User NS creation: no capable() call from the kernel, so only\n+\t * LANDLOCK_PERM_NAMESPACE_USE applies.  CLONE_NEWUSER is in the allowed\n+\t * set, so this succeeds.\n+\t */\n+\tEXPECT_EQ(0, unshare(CLONE_NEWUSER));\n+}\n+\n+/*\n+ * Combining CLONE_NEWUSER with another namespace type in a single unshare(2)\n+ * call does not exempt the non-user namespace from the capability check: the\n+ * kernel checks ns_capable(new_user_ns, CAP_SYS_ADMIN) for it, and the\n+ * namespace-agnostic capability hook denies it when the domain handles\n+ * LANDLOCK_PERM_CAPABILITY_USE without allowing CAP_SYS_ADMIN.  Both namespace\n+ * types are allowed here, yet the combined call fails atomically, exactly like\n+ * the equivalent two-call sequence.\n+ */\n+TEST(two_perm_combined_unshare_denied)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\tLANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\t/* Both namespace types are allowed; no capability is allowed. */\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWUSER | CLONE_NEWUTS,\n+\t};\n+\tint ruleset_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * CAP_SYS_ADMIN is held so the kernel's commoncap check passes (via\n+\t * ownership of the new user namespace) and the denial is attributable\n+\t * to Landlock.  The UTS leg needs CAP_SYS_ADMIN, which the capability\n+\t * hook denies, so the atomic unshare(2) fails and no namespace is\n+\t * created.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUSER | CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* The user namespace alone has no capability check, so it succeeds. */\n+\tEXPECT_EQ(0, unshare(CLONE_NEWUSER));\n+}\n+\n+/*\n+ * Positive complement to two_perm_combined_unshare_denied: allowing\n+ * CAP_SYS_ADMIN lets the same combined unshare(CLONE_NEWUSER | CLONE_NEWUTS)\n+ * succeed, proving the denial above comes from the missing capability allowance\n+ * and not the namespace rule.  Run in a child because a successful unshare(2)\n+ * mutates the caller.\n+ */\n+TEST(two_perm_combined_unshare_allowed)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\tLANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWUSER | CLONE_NEWUTS,\n+\t};\n+\tconst struct landlock_capability_attr cap_attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN),\n+\t};\n+\tint ruleset_fd, status;\n+\tpid_t child;\n+\n+\tdisable_caps(_metadata);\n+\n+\tchild = fork();\n+\tASSERT_LE(0, child);\n+\tif (child == 0) {\n+\t\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\tASSERT_EQ(0,\n+\t\t\t  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t\t    \u0026ns_attr, 0));\n+\t\tASSERT_EQ(0, landlock_add_rule(ruleset_fd,\n+\t\t\t\t\t       LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t\t       \u0026cap_attr, 0));\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\t\tEXPECT_EQ(0, unshare(CLONE_NEWUSER | CLONE_NEWUTS));\n+\t\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\t\t_exit(_metadata-\u003eexit_code);\n+\t}\n+\tASSERT_EQ(child, waitpid(child, \u0026status, 0));\n+\tASSERT_EQ(1, WIFEXITED(status));\n+\tASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));\n+}\n+\n+/*\n+ * Inverse of two_perm_cap_denied: the required capability is allowed but the\n+ * namespace type is not, so the Landlock namespace hook denies the operation\n+ * (the capability leg passes).  A positive control (an allowed type creates\n+ * successfully with the same allowed capability) proves CAP_SYS_ADMIN is\n+ * genuinely exercisable, so the UTS denial is attributable to the namespace\n+ * hook rather than the capability hook.\n+ */\n+TEST(two_perm_ns_denied)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\tLANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\t/* CLONE_NEWUTS is NOT allowed; CLONE_NEWNET is. */\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWNET,\n+\t};\n+\tconst struct landlock_capability_attr cap_attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN),\n+\t};\n+\tint ruleset_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026cap_attr, 0));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * Positive control: CLONE_NEWNET is allowed and CAP_SYS_ADMIN is\n+\t * allowed, so the kernel's ns_capable() gate passes and the namespace\n+\t * hook allows it.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, unshare(CLONE_NEWNET));\n+\n+\t/*\n+\t * CAP_SYS_ADMIN is allowed (ns_capable passes), but CLONE_NEWUTS is not\n+\t * in the allowed namespace types, so the Landlock namespace hook denies\n+\t * it.\n+\t */\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+}\n+\n+/*\n+ * Mount namespace setns is unique: the kernel checks both CAP_SYS_ADMIN and\n+ * CAP_SYS_CHROOT in mntns_install().  Verify that allowing CAP_SYS_ADMIN alone\n+ * is not sufficient.\n+ */\n+TEST(two_perm_mnt_setns)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\tLANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWNS,\n+\t};\n+\tconst struct landlock_capability_attr cap_admin = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN),\n+\t};\n+\tconst struct landlock_capability_attr cap_admin_chroot = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN) |\n+\t\t\t\t\t(1ULL \u003c\u003c CAP_SYS_CHROOT),\n+\t};\n+\tint ruleset_fd, ns_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\t/* Layer 1: allow mount NS + CAP_SYS_ADMIN only (no CAP_SYS_CHROOT). */\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026cap_admin, 0));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tns_fd = open(\"/proc/self/ns/mnt\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\t/*\n+\t * Fails: mntns_install() checks CAP_SYS_ADMIN (allowed) then\n+\t * CAP_SYS_CHROOT (denied by LANDLOCK_PERM_CAPABILITY_USE).\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWNS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\n+\t/* Layer 2: also allows CAP_SYS_CHROOT. */\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026cap_admin_chroot, 0));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * Still fails: layer 1 still denies CAP_SYS_CHROOT.  Landlock layer\n+\t * stacking means the most restrictive layer wins.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWNS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(0, close(ns_fd));\n+}\n+\n+/*\n+ * Positive complement to two_perm_mnt_setns: a single layer that allows the\n+ * mount namespace type AND both CAP_SYS_ADMIN and CAP_SYS_CHROOT lets\n+ * setns(CLONE_NEWNS) into the process's own mount namespace succeed.  Proves\n+ * the denial legs above are caused by the withheld CAP_SYS_CHROOT, not an\n+ * unconditional block of mount setns.\n+ */\n+TEST(two_perm_mnt_setns_allowed)\n+{\n+\tconst struct landlock_ruleset_attr attr = {\n+\t\t.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |\n+\t\t\t\tLANDLOCK_PERM_CAPABILITY_USE,\n+\t};\n+\tconst struct landlock_namespace_attr ns_attr = {\n+\t\t.perm = LANDLOCK_PERM_NAMESPACE_USE,\n+\t\t.allowed_namespace_types = CLONE_NEWNS,\n+\t};\n+\tconst struct landlock_capability_attr cap_attr = {\n+\t\t.perm = LANDLOCK_PERM_CAPABILITY_USE,\n+\t\t.allowed_capabilities = (1ULL \u003c\u003c CAP_SYS_ADMIN) |\n+\t\t\t\t\t(1ULL \u003c\u003c CAP_SYS_CHROOT),\n+\t};\n+\tint ruleset_fd, ns_fd;\n+\n+\tdisable_caps(_metadata);\n+\n+\t/* Open the NS FD before enforcing the domain. */\n+\tns_fd = open(\"/proc/self/ns/mnt\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\truleset_fd = landlock_create_ruleset(\u0026attr, sizeof(attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,\n+\t\t\t\t       \u0026ns_attr, 0));\n+\tASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,\n+\t\t\t\t       \u0026cap_attr, 0));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/*\n+\t * mntns_install() checks CAP_SYS_ADMIN then CAP_SYS_CHROOT (both\n+\t * allowed by the rule) and the mount NS type is allowed, so setns into\n+\t * the process's own mount namespace succeeds.\n+\t */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tset_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(0, setns(ns_fd, CLONE_NEWNS));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tclear_cap(_metadata, CAP_SYS_CHROOT);\n+\tEXPECT_EQ(0, close(ns_fd));\n+}\n+\n+/*\n+ * setns() through a namespace fd inherited across fork() is still governed by\n+ * the child's own Landlock domain: the child enforces a domain denying\n+ * CLONE_NEWUTS, then joining the inherited /proc/self/ns/uts fd is denied.\n+ */\n+TEST(setns_inherited_fd)\n+{\n+\tint ns_fd, status;\n+\tpid_t child;\n+\n+\tdisable_caps(_metadata);\n+\n+\t/* Open the UTS ns fd before fork() so the child inherits it. */\n+\tns_fd = open(\"/proc/self/ns/uts\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\tchild = fork();\n+\tASSERT_LE(0, child);\n+\tif (child == 0) {\n+\t\tint ruleset_fd;\n+\n+\t\t/*\n+\t\t * Baseline: joining the inherited fd works before the domain,\n+\t\t * so the later EPERM is attributable to Landlock.\n+\t\t */\n+\t\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\t\tASSERT_EQ(0, setns(ns_fd, CLONE_NEWUTS));\n+\t\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t\truleset_fd = create_ns_ruleset();\n+\t\tASSERT_LE(0, ruleset_fd);\n+\t\t/* No rule allows UTS. */\n+\t\tenforce_ruleset(_metadata, ruleset_fd);\n+\t\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\t\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));\n+\t\tEXPECT_EQ(EPERM, errno);\n+\t\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t\t_exit(_metadata-\u003eexit_code);\n+\t\treturn;\n+\t}\n+\n+\tEXPECT_EQ(0, close(ns_fd));\n+\tASSERT_EQ(child, waitpid(child, \u0026status, 0));\n+\tif (WIFSIGNALED(status) || !WIFEXITED(status) ||\n+\t    WEXITSTATUS(status) != EXIT_SUCCESS)\n+\t\t_metadata-\u003eexit_code = KSFT_FAIL;\n+}\n+\n+/* Audit tests */\n+\n+static int matches_log_ns_create(int audit_fd, __u64 ns_type)\n+{\n+\tstatic const char log_template[] = REGEX_LANDLOCK_PREFIX\n+\t\t\" blockers=perm\\\\.namespace_use\"\n+\t\t\" namespace_type=0x%x\"\n+\t\t\" namespace_id=0$\";\n+\tchar log_match[sizeof(log_template) + 10];\n+\tint log_match_len;\n+\n+\tlog_match_len = snprintf(log_match, sizeof(log_match), log_template,\n+\t\t\t\t (unsigned int)ns_type);\n+\tif (log_match_len \u003e= sizeof(log_match))\n+\t\treturn -E2BIG;\n+\n+\treturn audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,\n+\t\t\t\t  NULL);\n+}\n+\n+static int matches_log_ns_setns(int audit_fd, __u64 ns_type, __u64 ns_id)\n+{\n+\tstatic const char log_template[] = REGEX_LANDLOCK_PREFIX\n+\t\t\" blockers=perm\\\\.namespace_use\"\n+\t\t\" namespace_type=0x%x\"\n+\t\t\" namespace_id=%llu$\";\n+\tchar log_match[sizeof(log_template) + 32];\n+\tint log_match_len;\n+\n+\tlog_match_len = snprintf(log_match, sizeof(log_match), log_template,\n+\t\t\t\t (unsigned int)ns_type,\n+\t\t\t\t (unsigned long long)ns_id);\n+\tif (log_match_len \u003e= sizeof(log_match))\n+\t\treturn -E2BIG;\n+\n+\treturn audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,\n+\t\t\t\t  NULL);\n+}\n+\n+FIXTURE(ns_audit)\n+{\n+\tstruct audit_filter audit_filter;\n+\tint audit_fd;\n+};\n+\n+FIXTURE_SETUP(ns_audit)\n+{\n+\tASSERT_TRUE(is_in_init_user_ns());\n+\n+\tdisable_caps(_metadata);\n+\n+\tset_cap(_metadata, CAP_AUDIT_CONTROL);\n+\tself-\u003eaudit_fd = audit_init_with_exe_filter(\u0026self-\u003eaudit_filter);\n+\tEXPECT_LE(0, self-\u003eaudit_fd);\n+\tclear_cap(_metadata, CAP_AUDIT_CONTROL);\n+}\n+\n+FIXTURE_TEARDOWN(ns_audit)\n+{\n+\tset_cap(_metadata, CAP_AUDIT_CONTROL);\n+\tEXPECT_EQ(0, audit_cleanup(self-\u003eaudit_fd, \u0026self-\u003eaudit_filter));\n+}\n+\n+/*\n+ * Verifies that a denied namespace creation produces the expected audit record\n+ * with the perm.namespace_use blocker string and namespace_type.\n+ */\n+TEST_F(ns_audit, create_denied)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\t__u64 domain_id;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\tEXPECT_EQ(0, matches_log_ns_create(self-\u003eaudit_fd, CLONE_NEWUTS));\n+\n+\t/*\n+\t * The domain allocation record is emitted in the same event as the\n+\t * first denial; anchor its status=allocated and enforcing pid.  Both\n+\t * access and domain records are now consumed, so none remain.\n+\t */\n+\tEXPECT_EQ(0, matches_log_domain_allocated(self-\u003eaudit_fd, getpid(),\n+\t\t\t\t\t\t  \u0026domain_id));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+TEST_F(ns_audit, create_allowed)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, unshare(CLONE_NEWUTS));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* No records: allowed operations never trigger audit logging. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+TEST_F(ns_audit, setns_allowed)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd, ns_fd;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tns_fd = open(\"/proc/self/ns/uts\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\t/* Allowed: should succeed with no audit record. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, close(ns_fd));\n+\n+\t/* No records: allowed setns never triggers audit logging. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+TEST_F(ns_audit, setns_denied)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd, ns_fd;\n+\t__u64 ns_id, domain_id;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\t/* No rule allows UTS -\u003e denied. */\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tns_fd = open(\"/proc/self/ns/uts\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\t/* The audit record logs the target namespace's exact ns_id. */\n+\tASSERT_EQ(0, ioctl(ns_fd, NS_GET_ID, \u0026ns_id));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, close(ns_fd));\n+\n+\t/* Verify the audit record for setns denial, anchored to the ns_id. */\n+\tEXPECT_EQ(0, matches_log_ns_setns(self-\u003eaudit_fd, CLONE_NEWUTS, ns_id));\n+\n+\t/*\n+\t * The domain allocation record is emitted in the same event as the\n+\t * first denial; anchor its status=allocated and enforcing pid.  Both\n+\t * access and domain records are now consumed, so none remain.\n+\t */\n+\tEXPECT_EQ(0, matches_log_domain_allocated(self-\u003eaudit_fd, getpid(),\n+\t\t\t\t\t\t  \u0026domain_id));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+/*\n+ * A quieted denial is still denied (EPERM); only its audit record is\n+ * suppressed.  Quiet-only rule (empty allowed set).\n+ */\n+TEST_F(ns_audit, create_quieted)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* The denial is suppressed: no access record. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+/*\n+ * A quieted setns denial is still denied (EPERM); only its audit record is\n+ * suppressed.  The install-hook mirror of create_quieted.\n+ */\n+TEST_F(ns_audit, setns_quieted)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd, ns_fd;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tns_fd = open(\"/proc/self/ns/uts\", O_RDONLY);\n+\tASSERT_LE(0, ns_fd);\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, close(ns_fd));\n+\n+\t/* The denial is suppressed: no access record. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+/*\n+ * Quiet is per-member: quieting one namespace type does not silence the denial\n+ * of another type denied by the same layer.\n+ */\n+TEST_F(ns_audit, quiet_is_per_member)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\t__u64 domain_id;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\t/*\n+\t * Quiet CLONE_NEWNET only; CLONE_NEWUTS is neither allowed nor quiet.\n+\t */\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWNET));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* The unquieted CLONE_NEWUTS denial is still logged. */\n+\tEXPECT_EQ(0, matches_log_ns_create(self-\u003eaudit_fd, CLONE_NEWUTS));\n+\tEXPECT_EQ(0, matches_log_domain_allocated(self-\u003eaudit_fd, getpid(),\n+\t\t\t\t\t\t  \u0026domain_id));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+/*\n+ * Quieting an unknown namespace bit has no effect: a rule that quiets only a\n+ * bit the running kernel does not know still logs the denial of a known type.\n+ * Mirrors the forward-compatibility contract for the quiet mask.\n+ */\n+TEST_F(ns_audit, quiet_unknown_bit_no_effect)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\t__u64 domain_id;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\t/* Quiet only an unknown bit (bit 31 is not a CLONE_NEW* value). */\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, (1ULL \u003c\u003c 31)));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* The unknown quiet bit does not suppress the known denial. */\n+\tEXPECT_EQ(0, matches_log_ns_create(self-\u003eaudit_fd, CLONE_NEWUTS));\n+\tEXPECT_EQ(0, matches_log_domain_allocated(self-\u003eaudit_fd, getpid(),\n+\t\t\t\t\t\t  \u0026domain_id));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\tEXPECT_EQ(0, records.domain);\n+}\n+\n+/* A single rule can both allow one member and quiet the denial of another. */\n+TEST_F(ns_audit, allow_and_quiet)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWNET, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* Allowed member: succeeds. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, unshare(CLONE_NEWNET));\n+\t/* Quieted member: denied but not logged. */\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+/*\n+ * Quieting a member that is also allowed is inert: an allowed member is never\n+ * denied, so its quiet bit has no effect.\n+ */\n+TEST_F(ns_audit, allow_and_quiet_same_member)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\t/* Same member both allowed and quieted. */\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWNET, CLONE_NEWNET));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* The member is allowed (quiet is inert), so it succeeds. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(0, unshare(CLONE_NEWNET));\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* Nothing denied, nothing logged. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+/*\n+ * Only the youngest denying layer decides quieting: a parent that quiets a\n+ * member cannot silence a denial by a deeper layer that handles but does not\n+ * quiet it.\n+ */\n+TEST_F(ns_audit, quiet_youngest_layer_wins)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\t/* Parent layer: handles namespaces and quiets CLONE_NEWUTS. */\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* Child layer: handles namespaces but does not quiet CLONE_NEWUTS. */\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* The child layer denies and does not quiet: logged. */\n+\tEXPECT_EQ(0, matches_log_ns_create(self-\u003eaudit_fd, CLONE_NEWUTS));\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+/*\n+ * A parent layer that quiets and denies a member suppresses its own denial even\n+ * when it is not the youngest layer: a child layer allows the member, so the\n+ * parent is the youngest (and only) denying layer and its stored quiet mask\n+ * fires.  Complements quiet_youngest_layer_wins.\n+ */\n+TEST_F(ns_audit, quiet_parent_only_denying_layer)\n+{\n+\tstruct audit_records records;\n+\tint ruleset_fd;\n+\n+\t/* Parent layer: quiet-only, so it denies CLONE_NEWUTS. */\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\t/* Child layer: allows CLONE_NEWUTS, so only the parent denies it. */\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tEXPECT_EQ(-1, unshare(CLONE_NEWUTS));\n+\tEXPECT_EQ(EPERM, errno);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\t/* The parent quiets and is the youngest denying layer: suppressed. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+}\n+\n+/* clang-format off */\n+FIXTURE(ns_proc_open) {\n+\tstruct audit_filter audit_filter;\n+\tint audit_fd;\n+};\n+/* clang-format on */\n+\n+FIXTURE_VARIANT(ns_proc_open)\n+{\n+\t__u64 ns_type;\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_proc_open, mnt) {\n+\t/* clang-format on */\n+\t.ns_type = CLONE_NEWNS,\n+};\n+\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(ns_proc_open, user) {\n+\t/* clang-format on */\n+\t.ns_type = CLONE_NEWUSER,\n+};\n+\n+FIXTURE_SETUP(ns_proc_open)\n+{\n+\tASSERT_TRUE(is_in_init_user_ns());\n+\n+\tdisable_caps(_metadata);\n+\n+\tset_cap(_metadata, CAP_AUDIT_CONTROL);\n+\tself-\u003eaudit_fd = audit_init_with_exe_filter(\u0026self-\u003eaudit_filter);\n+\tEXPECT_LE(0, self-\u003eaudit_fd);\n+\tclear_cap(_metadata, CAP_AUDIT_CONTROL);\n+}\n+\n+FIXTURE_TEARDOWN(ns_proc_open)\n+{\n+\tset_cap(_metadata, CAP_AUDIT_CONTROL);\n+\tEXPECT_EQ(0, audit_cleanup(self-\u003eaudit_fd, \u0026self-\u003eaudit_filter));\n+}\n+\n+/*\n+ * Opening /proc/self/ns/\u003ctype\u003e only acquires a procfs reference, not membership\n+ * or an acquired fd of the kind LANDLOCK_PERM_NAMESPACE_USE gates.  Verify the\n+ * open is unrestricted even when the permission is handled with no rules.\n+ */\n+TEST_F(ns_proc_open, open_unrestricted)\n+{\n+\tchar proc_path[NS_PROC_PATH_MAX];\n+\tstruct audit_records records;\n+\tint ruleset_fd, fd;\n+\n+\tASSERT_TRUE(\n+\t\tns_is_supported(variant-\u003ens_type, proc_path, sizeof(proc_path)))\n+\t{\n+\t\tTH_LOG(\"Namespace type 0x%llx not supported\",\n+\t\t       (unsigned long long)variant-\u003ens_type);\n+\t}\n+\n+\truleset_fd = create_ns_ruleset();\n+\tASSERT_LE(0, ruleset_fd);\n+\tenforce_ruleset(_metadata, ruleset_fd);\n+\tEXPECT_EQ(0, close(ruleset_fd));\n+\n+\tfd = open(proc_path, O_RDONLY);\n+\tASSERT_LE(0, fd)\n+\t{\n+\t\tTH_LOG(\"open(%s) failed: %s\", proc_path, strerror(errno));\n+\t}\n+\n+\t/* No Landlock denial. */\n+\tEXPECT_EQ(0, audit_count_records(self-\u003eaudit_fd, \u0026records));\n+\tEXPECT_EQ(0, records.access);\n+\n+\tEXPECT_EQ(0, close(fd));\n+}\n+\n+TEST_HARNESS_MAIN\ndiff --git a/tools/testing/selftests/landlock/wrappers.h b/tools/testing/selftests/landlock/wrappers.h\nindex 65548323e45d9f..e6fe46b7c2cc4e 100644\n--- a/tools/testing/selftests/landlock/wrappers.h\n+++ b/tools/testing/selftests/landlock/wrappers.h\n@@ -9,6 +9,7 @@\n \n #define _GNU_SOURCE\n #include \u003clinux/landlock.h\u003e\n+#include \u003clinux/sched.h\u003e\n #include \u003csys/syscall.h\u003e\n #include \u003csys/types.h\u003e\n #include \u003cunistd.h\u003e\n@@ -45,3 +46,31 @@ static inline pid_t sys_gettid(void)\n {\n \treturn syscall(__NR_gettid);\n }\n+\n+static inline pid_t sys_clone3(struct clone_args *args, size_t size)\n+{\n+\treturn syscall(__NR_clone3, args, size);\n+}\n+\n+static inline int sys_open_tree(int dfd, const char *filename,\n+\t\t\t\tunsigned int flags)\n+{\n+\treturn syscall(__NR_open_tree, dfd, filename, flags);\n+}\n+\n+static inline int sys_fsopen(const char *fsname, unsigned int flags)\n+{\n+\treturn syscall(__NR_fsopen, fsname, flags);\n+}\n+\n+static inline int sys_fsconfig(int fs_fd, unsigned int cmd, const char *key,\n+\t\t\t       const void *value, int aux)\n+{\n+\treturn syscall(__NR_fsconfig, fs_fd, cmd, key, value, aux);\n+}\n+\n+static inline int sys_fsmount(int fs_fd, unsigned int flags,\n+\t\t\t      unsigned int attr_flags)\n+{\n+\treturn syscall(__NR_fsmount, fs_fd, flags, attr_flags);\n+}\n"
}

2/1 2026/07/27 06:25 agent patch-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "SECURITY_LANDLOCK",
    "NAMESPACES",
    "USER_NS",
    "UTS_NS",
    "IPC_NS",
    "PID_NS",
    "NET_NS",
    "TIME_NS",
    "CGROUPS",
    "AUDIT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "sys_landlock_create_ruleset",
    "sys_landlock_add_rule",
    "sys_landlock_restrict_self",
    "security_namespace_init",
    "security_namespace_install"
  ],
  "Reasoning": "The patch adds new Landlock LSM features to restrict capabilities and namespaces. It introduces new rule types (LANDLOCK_RULE_CAPABILITY, LANDLOCK_RULE_NAMESPACE) and new permission flags (LANDLOCK_PERM_CAPABILITY_USE, LANDLOCK_PERM_NAMESPACE_USE). It modifies the landlock_create_ruleset and landlock_add_rule syscalls to handle these new rules and permissions. It also adds new LSM hooks for capable, namespace_init, and namespace_install to enforce these restrictions. This is a functional change to the core kernel and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 646d02ca32d3ecd5b5730bdadc91cb05f6323826
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Jul 27 06:25:25 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/admin-guide/LSM/landlock.rst b/Documentation/admin-guide/LSM/landlock.rst
index 314052bbeb0a96..8d3b20a72f2085 100644
--- a/Documentation/admin-guide/LSM/landlock.rst
+++ b/Documentation/admin-guide/LSM/landlock.rst
@@ -6,7 +6,7 @@ Landlock: system-wide management
 ================================
 
 :Author: Mickaël Salaün
-:Date: June 2026
+:Date: July 2026
 
 Landlock can leverage the audit framework to log events.
 
@@ -63,14 +63,25 @@ AUDIT_LANDLOCK_ACCESS
         - scope.abstract_unix_socket - Abstract UNIX socket connection denied
         - scope.signal - Signal sending denied
 
+    **perm.*** - Permission restrictions (ABI 11+):
+        - perm.namespace_use - Namespace entry was denied (creation via
+          :manpage:`unshare(2)` / :manpage:`clone(2)` or joining via
+          :manpage:`setns(2)`);
+          ``namespace_type`` indicates the type (hex CLONE_NEW* bitmask),
+          ``namespace_id`` identifies the target namespace for
+          :manpage:`setns(2)` operations
+        - perm.capability_use - Capability use was denied;
+          ``capability`` indicates the capability number
+
     Multiple blockers can appear in a single event (comma-separated) when
     multiple access rights are missing. For example, creating a regular file
     in a directory that lacks both ``make_reg`` and ``refer`` rights would show
     ``blockers=fs.make_reg,fs.refer``.
 
-    The object identification fields (path, dev, ino for filesystem; opid,
-    ocomm for signals) depend on the type of access being blocked and provide
-    context about what resource was involved in the denial.
+    The object identification fields depend on the type of access being blocked:
+    ``path``, ``dev``, ``ino`` for filesystem; ``opid``, ``ocomm`` for signals;
+    ``namespace_type`` and ``namespace_id`` for namespace operations;
+    ``capability`` for capability use.
 
 
 AUDIT_LANDLOCK_DOMAIN
diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
index c5186526e76f6c..18f14652c20cac 100644
--- a/Documentation/security/landlock.rst
+++ b/Documentation/security/landlock.rst
@@ -7,7 +7,7 @@ Landlock LSM: kernel documentation
 ==================================
 
 :Author: Mickaël Salaün
-:Date: March 2026
+:Date: July 2026
 
 Landlock's goal is to create scoped access-control (i.e. sandboxing).  To
 harden a whole system, this feature should be available to any process,
@@ -129,6 +129,165 @@ The reasoning is:
   restrictions, because access within the same scope is already
   allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.
 
+Composability with user namespaces
+----------------------------------
+
+Landlock domain-based scoping and the kernel's user-namespace-based capability
+scoping enforce isolation over independent hierarchies.  Landlock checks domain
+ancestry; the kernel's ``ns_capable()`` checks user namespace ancestry.  These
+hierarchies are orthogonal: Landlock enforcement is deterministic with respect
+to its own configuration, regardless of namespace or capability state, and vice
+versa.  This orthogonality is a design invariant that must hold for all Landlock
+access controls.
+
+Design philosophy
+-----------------
+
+Landlock's goal is to restrict a sandboxed process's access to three kinds of
+resources: data (files, sockets, pipes), other processes (signals, ptrace), and
+kernel-internal resources whose use widens the kernel attack surface
+(capabilities, namespace types).  Each access right or permission gates one or
+more operations that grant such access; restricting the operations is how
+Landlock restricts the underlying access.
+
+When designing a new access control, identify the protected resource kind
+first (data, processes, or kernel-internal resources).  The operations to
+restrict follow from the protected resource, by identifying which kernel code
+paths grant access to the resource and at which place in the code the access to
+the resource can be gated.  Do not design a permission around
+"restrict the unshare(2) syscall" or similar mechanism-centric framings; design
+it around "restrict the process from acquiring access to namespace types" (the
+protected resource), letting the operation set follow.
+
+Ruleset restriction models
+--------------------------
+
+Landlock provides three restriction models that differ in how rules identify the
+resource being restricted.
+
+In general, the ``struct landlock_ruleset_attr`` specifies the operations to be
+denied by default under the enforced policy.  The *rules* added to the ruleset
+define the exceptions to these restrictions, allow-listing specific conditions
+under which these operations are still permitted.
+
+Per-object access rights (``handled_access_*``)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Per-object access rights control operations on a specific resource instance,
+identified in the rule key by a value drawn from an open-ended space: a file
+hierarchy referenced by ``parent_fd``, or a network port identified by its
+16-bit number.
+
+Each ``handled_access_*`` field declares a set of access rights, operations
+which are to be denied by default once the ruleset is enforced.
+
+The rule body declares which of the multiple distinct operations on that object
+instance are allowed (open, read, write, truncate; bind, connect).
+
+Operations are grouped by object type in the respective ``handled_access_*``
+field: a new operation on an existing type extends that field, and a new object
+type gets its own ``handled_access_*`` field.
+
+Per-category permissions (``handled_perm``)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Per-category permissions control the process's exercise of category members,
+where the category is a small kernel-defined enumeration (a Linux capability
+number ``CAP_*``, a namespace type ``CLONE_NEW*``).  Unlike per-object access
+rights, which restrict specific operations on a single resource instance,
+per-category permissions gate the prerequisite operation itself (exercising a
+capability, acquiring access to a namespace), so gating it transitively covers a
+broad set of downstream operations (e.g. denying ``CAP_SYS_ADMIN`` blocks all
+admin operations such as mounts).
+
+These category members are the LSM-level access-control objects (the entities
+the process is authorized against) even though they are enum values rather than
+externally-instantiated kernel data structures.  Per-category permissions apply
+where the controlled operation collapses to "may the process use this category
+member at all" (use a capability; acquire access to a namespace), so the rule
+body lists which category members the process may exercise.
+
+Each ``LANDLOCK_PERM_*`` flag maps to its own rule type and covers every kernel
+path that exercises a member.  When a ruleset handles a permission, all uses of
+category members are denied unless explicitly allowed by a rule.
+
+Logging of a denied member can be suppressed at the same granularity as the
+restriction itself: per rule and per member.  A rule names the members whose
+denial should not be audited; suppression only affects the audit record, never
+the denial, and only applies when the layer owning the rule is the one that
+denied the member.
+
+See Documentation/userspace-api/landlock.rst for the concrete syscall paths
+covered by each permission.
+
+The category enum is owned by the corresponding kernel subsystem (capabilities,
+namespaces, etc.).  Userspace policy authors query category member availability
+via the relevant non-Landlock interfaces:
+
+* For capabilities: ``<linux/capability.h>``,
+  ``/proc/sys/kernel/cap_last_cap``, ``prctl(PR_CAPBSET_READ)``.
+* For namespaces: ``<linux/sched.h>``, ``/proc/$$/ns/*``,
+  :manpage:`unshare(2)` runtime probe.
+
+The Landlock ABI version does not encode this availability; ABI versioning
+describes which Landlock features (rule types, access rights, scopes,
+permissions) the kernel implements, not which category members the kernel knows
+about.
+
+Forward compatibility for new category members follows a simple rule set:
+
+* New members in future kernels are automatically denied: rules whitelist
+  specific values, and a member not in any rule is denied.
+* Kernel-side compatibility for split categories is handled by the owning
+  subsystem (e.g., when ``CAP_BPF`` was split from ``CAP_SYS_ADMIN``, either
+  capability became sufficient for the affected operations, so a rule allowing
+  ``CAP_SYS_ADMIN`` continues to allow operations now gated by
+  ``CAP_SYS_ADMIN || CAP_BPF``).
+* Unknown values in the rule body are silently accepted rather than rejected.
+  Rejecting them would tie Landlock policy semantics to the running kernel's
+  category-member set: a rule built against future headers would fail to load
+  on older kernels, forcing policy authors to know each kernel's enumeration.
+  Acceptance is fail-safe in both directions: a rule referring to a value the
+  running kernel does not yet know has no effect (deny-by-default still applies
+  to that operation), and a rule written against future headers loads
+  identically across kernels so the same policy keeps the same restrictions.
+  When a value becomes real on a future kernel, the policy activates as written
+  by the author.
+* In contrast, unknown ``LANDLOCK_PERM_*`` flags in ``handled_perm`` are
+  rejected (``-EINVAL``), since Landlock owns that bit space.
+
+Cross-domain scopes (``scoped``)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Scopes restrict **cross-domain interactions** categorically, without rules.
+Setting a scope flag (e.g.  ``LANDLOCK_SCOPE_SIGNAL``) denies the operation to
+targets outside the Landlock domain or its children.  Like per-category
+permissions, scopes provide complete coverage of the controlled operation.
+
+Choosing a model for a new feature
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* If the new feature controls operations on resource objects supplied by the
+  sandbox author, extend or add a per-object access right
+  (``handled_access_*``).
+* If the new feature controls a per-category operation gated by an enum (a
+  Linux capability, a namespace type, a socket family, etc.), use a
+  per-category permission (``handled_perm``).  When several such enums could
+  classify the operation, prefer the enum the originating subsystem already
+  uses for capability/access checks (e.g. ``CAP_*`` for ``capable()`` hooks,
+  ``CLONE_NEW*`` for namespace hooks).
+* When an operation is gated by multiple kernel-defined enums (a classic
+  example being ``CAP_SYS_ADMIN`` plus a ``CLONE_NEW*`` flag for non-user
+  namespace creation), define one per-category permission per enum dimension.
+  Sandbox authors handle each dimension's permission in ``handled_perm`` and
+  add rules for each; the kernel enforces each dimension at its own LSM hook.
+  ``LANDLOCK_PERM_NAMESPACE_USE`` and ``LANDLOCK_PERM_CAPABILITY_USE`` follow
+  this pattern.
+* If the new feature restricts a categorical cross-domain interaction with no
+  per-target granularity, use a cross-domain scope (``scoped``).
+* For all three models, confirm a single LSM hook (or small set of related
+  hooks) covers every kernel path that exercises the operation.
+
 Tests
 =====
 
@@ -150,6 +309,18 @@ Filesystem
 .. kernel-doc:: security/landlock/fs.h
     :identifiers:
 
+Namespace
+---------
+
+.. kernel-doc:: security/landlock/ns.h
+    :identifiers:
+
+Capability
+----------
+
+.. kernel-doc:: security/landlock/cap.h
+    :identifiers:
+
 Process credential
 ------------------
 
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 5a63d4476c1c75..652f55e8cd8cc5 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -8,7 +8,7 @@ Landlock: unprivileged access control
 =====================================
 
 :Author: Mickaël Salaün
-:Date: June 2026
+:Date: July 2026
 
 The goal of Landlock is to enable restriction of ambient rights (e.g. global
 filesystem or network access) for a set of processes.  Because Landlock
@@ -29,21 +29,30 @@ If Landlock is not currently supported, we need to
 Landlock rules
 ==============
 
-A Landlock rule describes an action on an object which the process intends to
-perform.  A set of rules is aggregated in a ruleset, which can then restrict
-the thread enforcing it, and its future children.
+A Landlock rule describes the actions a process is allowed to perform on a
+specific resource.  A set of rules is aggregated in a ruleset, which can then
+restrict the thread enforcing it, and its future children.
 
-The two existing types of rules are:
+The existing types of rules are:
 
 Filesystem rules
-    For these rules, the object is a file hierarchy,
-    and the related filesystem actions are defined with
-    `filesystem access rights`.
+    The rule key is a file hierarchy, and the actions it allows are
+    defined with `filesystem access rights`.
 
 Network rules (since ABI v4 for TCP and v10 for UDP)
     For these rules, the object is a TCP or UDP port,
     and the related actions are defined with `network access rights`.
 
+Capability rules (since ABI v11)
+    The rule body lists which Linux capabilities (see
+    :manpage:`capabilities(7)`) the process may exercise; the action is
+    defined with `permission flags`.
+
+Namespace rules (since ABI v11)
+    The rule body lists which namespace types (see
+    :manpage:`namespaces(7)`) the process may use; the action is defined
+    with `permission flags`.
+
 Defining and enforcing a security policy
 ----------------------------------------
 
@@ -87,6 +96,9 @@ to be explicit about the denied-by-default access rights.
         .scoped =
             LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
             LANDLOCK_SCOPE_SIGNAL,
+        .handled_perm =
+            LANDLOCK_PERM_CAPABILITY_USE |
+            LANDLOCK_PERM_NAMESPACE_USE,
     };
 
 Because we may not know which kernel version an application will be executed
@@ -140,6 +152,11 @@ version, and only use the available subset of access rights:
         ruleset_attr.handled_access_net &=
             ~(LANDLOCK_ACCESS_NET_BIND_UDP |
               LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+        __attribute__((fallthrough));
+    case 10:
+        /* Removes LANDLOCK_PERM_* for ABI < 11 */
+        ruleset_attr.handled_perm &= ~(LANDLOCK_PERM_NAMESPACE_USE |
+                                       LANDLOCK_PERM_CAPABILITY_USE);
     }
 
 This enables the creation of an inclusive ruleset that will contain our rules.
@@ -242,6 +259,55 @@ the program explicitly called :manpage:`bind(2)` on port 0.
         err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
                                 &udp_bind, 0);
 
+Capability and namespace rules use a different attribute layout:
+``perm`` identifies the permission category (a single
+``LANDLOCK_PERM_*`` flag) and a type-specific value field carries the bitmask to
+allow within it.  See `Capability and namespace restrictions`_ for the model.
+
+For capability access-control, we can add rules that allow specific
+capabilities (see :manpage:`capabilities(7)` for the list of ``CAP_*``
+values).  For instance, to allow ``CAP_SYS_CHROOT`` (so the sandboxed
+process can call :manpage:`chroot(2)` inside a user namespace):
+
+.. code-block:: c
+
+    struct landlock_capability_attr cap_attr = {
+        .perm = LANDLOCK_PERM_CAPABILITY_USE,
+        .allowed_capabilities = (1ULL << CAP_SYS_CHROOT),
+    };
+
+    cap_attr.perm &= ruleset_attr.handled_perm;
+    if (cap_attr.perm)
+        err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+                                &cap_attr, 0);
+
+For namespace access-control, we can add rules that allow using specific
+namespace types (see :manpage:`namespaces(7)` for the list of ``CLONE_NEW*``
+values): creating them via :manpage:`unshare(2)` / :manpage:`clone(2)` /
+:manpage:`clone3(2)`, joining them via :manpage:`setns(2)`, or acquiring an fd
+reference via :manpage:`open_tree(2)` / :manpage:`fsmount(2)`.  For instance,
+to allow creating user namespaces (which grants all capabilities inside the new
+namespace):
+
+.. code-block:: c
+
+    struct landlock_namespace_attr ns_attr = {
+        .perm = LANDLOCK_PERM_NAMESPACE_USE,
+        .allowed_namespace_types = CLONE_NEWUSER,
+    };
+
+    ns_attr.perm &= ruleset_attr.handled_perm;
+    if (ns_attr.perm)
+        err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+                                &ns_attr, 0);
+
+Together, these two rules allow an unprivileged process to create a user
+namespace and call :manpage:`chroot(2)` inside it, while denying all other
+capabilities and namespace types.  User namespace creation is the one operation
+that does not require ``CAP_SYS_ADMIN``, so no capability rule is needed for it.
+See `Capability and namespace restrictions`_ for details on capability
+requirements.
+
 When passing a non-zero ``flags`` argument to ``landlock_restrict_self()``, a
 similar backwards compatibility check is needed for the restrict flags
 (see sys_landlock_restrict_self() documentation for available flags):
@@ -420,9 +486,135 @@ 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.
 
-IPC 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.
+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.
+
+Capability and namespace restrictions
+-------------------------------------
+
+``handled_perm`` declares per-category permissions: each permission selects
+which members of a kernel-defined category (CAP_* capabilities, CLONE_NEW*
+namespace types) the process may use.  Unlike per-object access rights
+(``handled_access_*``) or cross-domain scopes (``scoped``), per-category
+permissions constrain the sandboxed process's own use of these enums; members
+not allowed by a rule are denied by default.
+
+``LANDLOCK_PERM_NAMESPACE_USE`` gates *acquisition of access* to
+namespaces: creation via :manpage:`unshare(2)` / :manpage:`clone(2)`
+/ :manpage:`clone3(2)`, entry via :manpage:`setns(2)`, and fd-reference
+acquisition via :manpage:`open_tree(2)` / :manpage:`fsmount(2)`.  Namespaces
+the process is already a member of when the domain is enforced are implicitly
+allowed (the process could not continue running otherwise); rules describe which
+new namespace types the process may acquire.  ``LANDLOCK_PERM_CAPABILITY_USE``
+gates every exercise of a capability after the domain is enforced, regardless
+of how the capability was obtained (inherited credentials, ``CLONE_NEWUSER``
+grant, ``setuid``/file-cap-bearing :manpage:`execve(2)`, etc.).  Configuring
+both together restricts what privileges are available *and* the namespaces in
+which they take effect, which matters because user namespace creation has no
+capability check and grants all capabilities within the new namespace: gating
+only one of the two leaves a kernel attack-surface widening path open.  When a
+domain handles both permissions, an operation is allowed only if each
+independently allows it: :manpage:`unshare(2)` with ``CLONE_NEWNET`` then
+requires both a rule allowing ``CLONE_NEWNET`` and a rule allowing
+``CAP_SYS_ADMIN``.
+
+``LANDLOCK_PERM_CAPABILITY_USE`` complements :manpage:`prctl(2)`
+``PR_SET_NO_NEW_PRIVS`` but does not replace it.  ``PR_SET_NO_NEW_PRIVS``
+prevents privilege *acquisition* via :manpage:`execve(2)` (setuid, file
+capability xattrs, privilege-elevating LSM transitions) and is a prerequisite
+for unprivileged Landlock self-sandboxing.  ``LANDLOCK_PERM_CAPABILITY_USE``
+restricts *exercise* of capabilities the process already holds, including those
+gained via ``CLONE_NEWUSER`` which ``PR_SET_NO_NEW_PRIVS`` does not block.
+Sandboxes typically set both.
+
+Rules are added with ``LANDLOCK_RULE_CAPABILITY`` and &struct
+landlock_capability_attr (each rule lists ``CAP_*`` values to allow), and with
+``LANDLOCK_RULE_NAMESPACE`` and &struct landlock_namespace_attr (each rule
+lists ``CLONE_NEW*`` flags to allow).  Landlock is purely restrictive: it can
+only deny what the traditional check would have allowed, never grant additional
+privileges.
+
+Denials of specific capabilities or namespace types can be excluded from audit
+logging per rule, by listing them in the ``quiet_capabilities`` or
+``quiet_namespace_types`` field of the rule (independently of the allowed
+members).  Quieting requires the permission to be handled, is member-granular,
+and only suppresses the audit record of a denial attributed to the rule's own
+layer; it never changes the denial itself.  Sandboxes should quiet only
+specific members known to be requested but expected to be denied (for example
+for compatibility with older callers), never a blanket set: a blanket set
+follows the running kernel's known members and would hide surprising or future
+denials.
+
+Rule bodies silently accept values unknown to the current kernel (capabilities
+above ``CAP_LAST_CAP``, unrecognised ``CLONE_NEW*`` bits): they have no runtime
+effect, so a rule compiled against future kernel headers loads without error on
+older kernels.  Future kernels gain new members denied by default until a rule
+explicitly allows them.
+
+The single ``LANDLOCK_PERM_NAMESPACE_USE`` bit gates every kernel path that
+grants the calling process access to a namespace of the controlled types,
+whether by becoming a member of the namespace or by holding a file descriptor
+that references it.  The covered syscall paths are:
+
+* :manpage:`unshare(2)` with ``CLONE_NEW*``: the caller becomes a member of a
+  newly-created namespace.
+* :manpage:`clone(2)` (or :manpage:`clone3(2)`) with ``CLONE_NEW*``: the
+  child becomes a member of a newly-created namespace.
+* :manpage:`setns(2)`: the caller becomes a member of an existing namespace
+  referenced by file descriptor.
+* :manpage:`open_tree(2)` with ``OPEN_TREE_NAMESPACE``: the caller obtains a
+  file descriptor referring to a newly-created mount namespace.
+* :manpage:`open_tree(2)` with ``OPEN_TREE_CLONE``: the caller obtains a file
+  descriptor referring to a newly-created anonymous mount namespace.
+* :manpage:`fsmount(2)` with ``FSMOUNT_NAMESPACE``: the caller obtains a file
+  descriptor referring to a newly-created mount namespace.
+* :manpage:`fsmount(2)` (default): the caller obtains a file descriptor
+  referring to a newly-created anonymous mount namespace.
+
+Anonymous mount namespaces (created by ``open_tree(OPEN_TREE_CLONE)`` and the
+default :manpage:`fsmount(2)`) are intentionally covered by the bit even though
+the calling process does not become a member of them.  Without this coverage, a
+sandboxed process could combine ``open_tree(OPEN_TREE_CLONE)`` with
+:manpage:`move_mount(2)` to graft mounts from a freshly-allocated mount
+namespace into its current namespace, bypassing a ``CLONE_NEWNS`` restriction
+(these mount operations require ``CAP_SYS_ADMIN``, which a sandboxed process
+typically obtains by first creating a user namespace).
+
+In practice, unprivileged processes first create a user namespace (which
+requires no capability and grants all capabilities within it), then use those
+capabilities to create other namespace types.  All non-user namespace types
+require ``CAP_SYS_ADMIN`` for both creation and :manpage:`setns(2)` entry; mount
+namespace entry additionally requires ``CAP_SYS_CHROOT``.  For
+:manpage:`setns(2)`, capabilities are checked relative to the target namespace,
+so a process in an ancestor user namespace naturally satisfies them; this
+includes joining user namespaces, which requires ``CAP_SYS_ADMIN``.  When
+``LANDLOCK_PERM_CAPABILITY_USE`` is also handled, each of these capabilities
+must be explicitly allowed by a rule.
+
+Creating a non-user namespace always requires ``CAP_SYS_ADMIN``, and
+``LANDLOCK_PERM_CAPABILITY_USE`` (when handled) gates that check regardless of
+which user namespace it targets.  This is true whether the namespace is created
+on its own, combined with ``CLONE_NEWUSER`` in a single :manpage:`unshare(2)`
+call, or in a separate :manpage:`unshare(2)` call after a user namespace: in
+all cases the kernel checks ``CAP_SYS_ADMIN`` against the owning user
+namespace, and Landlock's capability hook denies it unless a rule allows
+``CAP_SYS_ADMIN``.  Combining ``CLONE_NEWUSER`` with another ``CLONE_NEW*``
+flag therefore does not avoid the capability check; only user namespace
+creation itself needs no capability rule.
+
+When creating child user namespaces, it is recommended to also create a
+dedicated Landlock domain with restrictions relevant to each namespace context.
+
+Note that ``LANDLOCK_PERM_CAPABILITY_USE`` restricts the *use* of capabilities,
+not their presence in the process's credential.  Capability sets can change
+after a domain is enforced through user namespace entry or :manpage:`capset(2)`;
+privileged sandboxes that did not set ``PR_SET_NO_NEW_PRIVS`` may also gain
+capabilities through :manpage:`execve(2)` of binaries with file capabilities.
+In all cases, :manpage:`capget(2)` will report the credential's capability sets,
+but any denied capability will fail with ``EPERM`` when exercised.  Do not rely
+on :manpage:`capget(2)` to determine whether the policy permits a given
+capability; only the actual operation will return ``EPERM`` upon denial.
 
 Truncating files
 ----------------
@@ -585,7 +777,7 @@ Access rights
 -------------
 
 .. kernel-doc:: include/uapi/linux/landlock.h
-    :identifiers: fs_access net_access scope
+    :identifiers: fs_access net_access scope perm
 
 Creating a new ruleset
 ----------------------
@@ -604,7 +796,8 @@ Extending a ruleset
 
 .. kernel-doc:: include/uapi/linux/landlock.h
     :identifiers: landlock_rule_type landlock_path_beneath_attr
-                  landlock_net_port_attr
+                  landlock_net_port_attr landlock_capability_attr
+                  landlock_namespace_attr
 
 Enforcing a ruleset
 -------------------
@@ -789,6 +982,23 @@ when at least one sys_landlock_add_rule() call is made for it with the
 ``LANDLOCK_ADD_RULE_QUIET`` flag, additional add-rule calls for the same
 object without this flag do not clear it.
 
+Capability (ABI < 11)
+---------------------
+
+Starting with the Landlock ABI version 11, it is possible to restrict
+:manpage:`capabilities(7)` with the new ``LANDLOCK_PERM_CAPABILITY_USE``
+permission flag and ``LANDLOCK_RULE_CAPABILITY`` rule type.
+
+Namespace (ABI < 11)
+--------------------
+
+Starting with the Landlock ABI version 11, it is possible to restrict namespace
+use (see :manpage:`namespaces(7)`) across creation (:manpage:`unshare(2)`,
+:manpage:`clone(2)`, :manpage:`clone3(2)`), entry (:manpage:`setns(2)`), and
+fd-reference acquisition (:manpage:`open_tree(2)`, :manpage:`fsmount(2)`) with
+the new ``LANDLOCK_PERM_NAMESPACE_USE`` permission flag and
+``LANDLOCK_RULE_NAMESPACE`` rule type.
+
 .. _kernel_support:
 
 Kernel support
diff --git a/fs/namespace.c b/fs/namespace.c
index 3d5cd5bf3b0537..0602f133f3a04c 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -4186,8 +4186,7 @@ static void dec_mnt_namespaces(struct ucounts *ucounts)
 
 static void free_mnt_ns(struct mnt_namespace *ns)
 {
-	if (!is_anon_ns(ns))
-		ns_common_free(ns);
+	ns_common_free(ns);
 	dec_mnt_namespaces(ns->ucounts);
 	mnt_ns_tree_remove(ns);
 }
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 584db296e43b20..526a8e7471c80c 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -78,6 +78,7 @@ struct common_audit_data {
 #define LSM_AUDIT_DATA_NOTIFICATION 16
 #define LSM_AUDIT_DATA_ANONINODE	17
 #define LSM_AUDIT_DATA_NLMSGTYPE	18
+#define LSM_AUDIT_DATA_NS		19
 	union 	{
 		struct path path;
 		struct dentry *dentry;
@@ -100,6 +101,10 @@ struct common_audit_data {
 		int reason;
 		const char *anonclass;
 		u16 nlmsg_type;
+		struct {
+			u32 ns_type;
+			u64 ns_id;
+		} ns;
 	} u;
 	/* this union contains LSM specific data */
 	union {
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec20770..0da6c7e8f65957 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -265,6 +265,9 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2,
 LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p,
 	 struct inode *inode)
 LSM_HOOK(int, 0, userns_create, const struct cred *cred)
+LSM_HOOK(int, 0, namespace_init, struct ns_common *ns)
+LSM_HOOK(void, LSM_RET_VOID, namespace_free, struct ns_common *ns)
+LSM_HOOK(int, 0, namespace_install, const struct nsset *nsset, struct ns_common *ns)
 LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag)
 LSM_HOOK(void, LSM_RET_VOID, ipc_getlsmprop, struct kern_ipc_perm *ipcp,
 	 struct lsm_prop *prop)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index b4f8cad53ddb03..5cff1306952952 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -112,6 +112,7 @@ struct lsm_blob_sizes {
 	unsigned int lbs_ipc;
 	unsigned int lbs_key;
 	unsigned int lbs_msg_msg;
+	unsigned int lbs_ns;
 	unsigned int lbs_perf_event;
 	unsigned int lbs_task;
 	unsigned int lbs_xattr_count; /* num xattr slots in new_xattrs array */
diff --git a/include/linux/ns/ns_common_types.h b/include/linux/ns/ns_common_types.h
index ea45c54e4435ed..5cfe0ce3c8818e 100644
--- a/include/linux/ns/ns_common_types.h
+++ b/include/linux/ns/ns_common_types.h
@@ -116,6 +116,9 @@ struct ns_common {
 	struct dentry *stashed;
 	const struct proc_ns_operations *ops;
 	unsigned int inum;
+#ifdef CONFIG_SECURITY
+	void *ns_security;
+#endif
 	union {
 		struct ns_tree;
 		struct rcu_head ns_rcu;
diff --git a/include/linux/security.h b/include/linux/security.h
index 153e9043058f88..bf002ed14ac83c 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -67,6 +67,7 @@ enum fs_value_type;
 struct watch;
 struct watch_notification;
 struct lsm_ctx;
+struct nsset;
 
 /* Default (no) options for the capable function */
 #define CAP_OPT_NONE 0x0
@@ -80,6 +81,7 @@ struct lsm_ctx;
 
 struct ctl_table;
 struct audit_krule;
+struct ns_common;
 struct user_namespace;
 struct timezone;
 
@@ -540,6 +542,9 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
 			unsigned long arg4, unsigned long arg5);
 void security_task_to_inode(struct task_struct *p, struct inode *inode);
 int security_create_user_ns(const struct cred *cred);
+int security_namespace_init(struct ns_common *ns);
+void security_namespace_free(struct ns_common *ns);
+int security_namespace_install(const struct nsset *nsset, struct ns_common *ns);
 int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag);
 void security_ipc_getlsmprop(struct kern_ipc_perm *ipcp, struct lsm_prop *prop);
 int security_msg_msg_alloc(struct msg_msg *msg);
@@ -1431,6 +1436,21 @@ static inline int security_create_user_ns(const struct cred *cred)
 	return 0;
 }
 
+static inline int security_namespace_init(struct ns_common *ns)
+{
+	return 0;
+}
+
+static inline void security_namespace_free(struct ns_common *ns)
+{
+}
+
+static inline int security_namespace_install(const struct nsset *nsset,
+					     struct ns_common *ns)
+{
+	return 0;
+}
+
 static inline int security_ipc_permission(struct kern_ipc_perm *ipcp,
 					  short flag)
 {
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 7ffe2ef127ee74..393dd62fcde14f 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -78,6 +78,15 @@ struct landlock_ruleset_attr {
 	 * @quiet_scoped: Bitmask of scoped actions which should not be logged.
 	 */
 	__u64 quiet_scoped;
+	/**
+	 * @handled_perm: Bitmask of permissions (cf. `Permission flags`_) that
+	 * this ruleset handles.  Each permission controls a per-category
+	 * operation gated by an enum (CLONE_NEW* for namespace types, CAP_* for
+	 * capabilities); all uses of category members are denied unless
+	 * explicitly allowed by a rule.  See
+	 * Documentation/security/landlock.rst for the rationale.
+	 */
+	__u64 handled_perm;
 };
 
 /**
@@ -215,6 +224,15 @@ enum landlock_rule_type {
 	 * landlock_net_port_attr .
 	 */
 	LANDLOCK_RULE_NET_PORT,
+	/**
+	 * @LANDLOCK_RULE_NAMESPACE: Type of a &struct landlock_namespace_attr .
+	 */
+	LANDLOCK_RULE_NAMESPACE,
+	/**
+	 * @LANDLOCK_RULE_CAPABILITY: Type of a &struct
+	 * landlock_capability_attr .
+	 */
+	LANDLOCK_RULE_CAPABILITY,
 };
 
 /**
@@ -268,6 +286,74 @@ struct landlock_net_port_attr {
 	__u64 port;
 };
 
+/**
+ * struct landlock_namespace_attr - Namespace type definition
+ *
+ * Argument of sys_landlock_add_rule() with %LANDLOCK_RULE_NAMESPACE.
+ */
+struct landlock_namespace_attr {
+	/**
+	 * @perm: Must be set to %LANDLOCK_PERM_NAMESPACE_USE.
+	 */
+	__u64 perm;
+	/**
+	 * @allowed_namespace_types: Bitmask of namespace types (``CLONE_NEW*``
+	 * flags) to allow under this rule.  Unknown bits are silently ignored
+	 * for forward compatibility.
+	 */
+	__u64 allowed_namespace_types;
+	/**
+	 * @quiet_namespace_types: Bitmask of namespace types (``CLONE_NEW*``
+	 * flags) whose denial by this layer should not be logged, even if
+	 * logging would normally take place per landlock_restrict_self() flags.
+	 * Only denials attributed to this layer are suppressed (see `permission
+	 * flags`_).  Bits also set in @allowed_namespace_types have no effect,
+	 * since an allowed type is never denied.  Unknown bits are silently
+	 * ignored.
+	 *
+	 * At least one of @allowed_namespace_types or @quiet_namespace_types
+	 * must be non-zero, otherwise the call returns ``-ENOMSG``.  The
+	 * non-zero check runs on the raw input before unknown-bit masking, so a
+	 * rule that sets only bits unknown to the running kernel succeeds but
+	 * has no runtime effect.
+	 */
+	__u64 quiet_namespace_types;
+};
+
+/**
+ * struct landlock_capability_attr - Capability definition
+ *
+ * Argument of sys_landlock_add_rule() with %LANDLOCK_RULE_CAPABILITY.
+ */
+struct landlock_capability_attr {
+	/**
+	 * @perm: Must be set to %LANDLOCK_PERM_CAPABILITY_USE.
+	 */
+	__u64 perm;
+	/**
+	 * @allowed_capabilities: Bitmask of capabilities (``1ULL << CAP_*``) to
+	 * allow under this rule.  Bits above ``CAP_LAST_CAP`` are silently
+	 * ignored for forward compatibility.
+	 */
+	__u64 allowed_capabilities;
+	/**
+	 * @quiet_capabilities: Bitmask of capabilities (``1ULL << CAP_*``)
+	 * whose denial by this layer should not be logged, even if logging
+	 * would normally take place per landlock_restrict_self() flags.  Only
+	 * denials attributed to this layer are suppressed (see `permission
+	 * flags`_).  Bits also set in @allowed_capabilities have no effect,
+	 * since an allowed capability is never denied.  Bits above
+	 * ``CAP_LAST_CAP`` are silently ignored.
+	 *
+	 * At least one of @allowed_capabilities or @quiet_capabilities must be
+	 * non-zero, otherwise the call returns ``-ENOMSG``.  The non-zero check
+	 * runs on the raw input before unknown-bit masking, so a rule that sets
+	 * only bits unknown to the running kernel (above ``CAP_LAST_CAP``)
+	 * succeeds but has no runtime effect.
+	 */
+	__u64 quiet_capabilities;
+};
+
 /**
  * DOC: fs_access
  *
@@ -491,6 +577,45 @@ struct landlock_net_port_attr {
 /* clang-format off */
 #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET		(1ULL << 0)
 #define LANDLOCK_SCOPE_SIGNAL		                (1ULL << 1)
-/* clang-format on*/
+/* clang-format on */
+
+/**
+ * DOC: perm
+ *
+ * Permission flags
+ * ~~~~~~~~~~~~~~~~
+ *
+ * These flags restrict per-category operations gated by enums (CLONE_NEW* for
+ * namespace types, CAP_* for capabilities).  Each flag covers every kernel path
+ * that exercises a member of the category.  Handled permissions that are not
+ * explicitly allowed by a rule are denied by default.  Rule values reference
+ * constants from other kernel subsystems; unknown values are silently accepted
+ * for forward compatibility since the allow-list denies them by default.  See
+ * Documentation/security/landlock.rst for design details.
+ *
+ * When a ruleset handles multiple permissions whose operations overlap (e.g. a
+ * non-user namespace needs both its namespace type and CAP_SYS_ADMIN), the
+ * operation is allowed only if each handled permission independently allows it.
+ * See Documentation/userspace-api/landlock.rst.
+ *
+ * - %LANDLOCK_PERM_NAMESPACE_USE: Restrict the use of specific namespace
+ *   types: creation (:manpage:`unshare(2)`, :manpage:`clone(2)`,
+ *   :manpage:`clone3(2)`), joining (:manpage:`setns(2)`), and acquiring an
+ *   fd reference (:manpage:`open_tree(2)`, :manpage:`fsmount(2)`).  A
+ *   process in a Landlock domain that handles this permission is denied
+ *   from using namespace types that are not explicitly allowed by a
+ *   %LANDLOCK_RULE_NAMESPACE rule.
+ * - %LANDLOCK_PERM_CAPABILITY_USE: Restrict the use of specific Linux
+ *   capabilities.  A process in a Landlock domain that handles this
+ *   permission is denied from exercising capabilities that are not
+ *   explicitly allowed by a %LANDLOCK_RULE_CAPABILITY rule.  This hook
+ *   is purely restrictive: it can deny capabilities that the kernel
+ *   would otherwise grant, but it can never grant capabilities that the
+ *   kernel already denied.
+ */
+/* clang-format off */
+#define LANDLOCK_PERM_NAMESPACE_USE			(1ULL << 0)
+#define LANDLOCK_PERM_CAPABILITY_USE			(1ULL << 1)
+/* clang-format on */
 
 #endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h
index a25e38d1c8747d..ea0f0267d90f7b 100644
--- a/include/uapi/linux/nsfs.h
+++ b/include/uapi/linux/nsfs.h
@@ -55,6 +55,7 @@ enum init_ns_ino {
 	MNT_NS_INIT_INO		= 0xEFFFFFF8U,
 #ifdef __KERNEL__
 	MNT_NS_ANON_INO		= 0xEFFFFFF7U,
+	MNT_NS_INO_SPECIAL_MAX	= MNT_NS_ANON_INO,
 #endif
 };
 
diff --git a/kernel/nscommon.c b/kernel/nscommon.c
index 3166c1fd844afd..e72426bba29ab1 100644
--- a/kernel/nscommon.c
+++ b/kernel/nscommon.c
@@ -4,6 +4,7 @@
 #include <linux/ns_common.h>
 #include <linux/nstree.h>
 #include <linux/proc_ns.h>
+#include <linux/security.h>
 #include <linux/user_namespace.h>
 #include <linux/vfsdebug.h>
 
@@ -59,6 +60,9 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
 
 	refcount_set(&ns->__ns_ref, 1);
 	ns->stashed = NULL;
+#ifdef CONFIG_SECURITY
+	ns->ns_security = NULL;
+#endif
 	ns->ops = ops;
 	ns->ns_id = 0;
 	ns->ns_type = ns_type;
@@ -77,6 +81,14 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
 		ret = proc_alloc_inum(&ns->inum);
 	if (ret)
 		return ret;
+
+	ret = security_namespace_init(ns);
+	if (ret) {
+		if (!inum)
+			proc_free_inum(ns->inum);
+		return ret;
+	}
+
 	/*
 	 * Tree ref starts at 0. It's incremented when namespace enters
 	 * active use (installed in nsproxy) and decremented when all
@@ -91,7 +103,10 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
 
 void __ns_common_free(struct ns_common *ns)
 {
-	proc_free_inum(ns->inum);
+	security_namespace_free(ns);
+
+	if (ns->inum > MNT_NS_INO_SPECIAL_MAX)
+		proc_free_inum(ns->inum);
 }
 
 struct ns_common *__must_check ns_owner(struct ns_common *ns)
diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
index d9d3d5973bf523..0f1b208d8eeff1 100644
--- a/kernel/nsproxy.c
+++ b/kernel/nsproxy.c
@@ -385,6 +385,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
 
 static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
 {
+	int ret;
+
+	ret = security_namespace_install(nsset, ns);
+	if (ret)
+		return ret;
+
 	return ns->ops->install(nsset, ns);
 }
 
diff --git a/samples/landlock/Makefile b/samples/landlock/Makefile
index 5d601e51c2ebf2..b30239c8a281ce 100644
--- a/samples/landlock/Makefile
+++ b/samples/landlock/Makefile
@@ -3,6 +3,7 @@
 userprogs-always-y := sandboxer
 
 userccflags += -I usr/include
+userldlibs += -lcap
 
 .PHONY: all clean
 
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index ac71019e62122a..b528ef41e2f561 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -14,15 +14,17 @@
 #include <fcntl.h>
 #include <linux/landlock.h>
 #include <linux/socket.h>
+#include <sched.h>
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/capability.h>
 #include <sys/prctl.h>
 #include <sys/stat.h>
 #include <sys/syscall.h>
 #include <unistd.h>
-#include <stdbool.h>
 
 #if defined(__GLIBC__)
 #include <linux/prctl.h>
@@ -62,6 +64,10 @@ static inline int landlock_restrict_self(const int ruleset_fd,
 #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
 #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
 #define ENV_NET_QUIET_NAME "LL_NET_QUIET"
+#define ENV_CAP_NAME "LL_CAP"
+#define ENV_CAP_QUIET_NAME "LL_CAP_QUIET"
+#define ENV_NS_NAME "LL_NS"
+#define ENV_NS_QUIET_NAME "LL_NS_QUIET"
 #define ENV_SCOPED_NAME "LL_SCOPED"
 #define ENV_QUIET_ACCESS_NAME "LL_QUIET_ACCESS"
 #define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
@@ -232,6 +238,161 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
 	return ret;
 }
 
+static __u64 str2ns(const char *const name)
+{
+	static const struct {
+		const char *name;
+		__u64 value;
+	} ns_map[] = {
+		/* clang-format off */
+		{ "cgroup",	CLONE_NEWCGROUP },
+		{ "ipc",	CLONE_NEWIPC },
+		{ "mnt",	CLONE_NEWNS },
+		{ "net",	CLONE_NEWNET },
+		{ "pid",	CLONE_NEWPID },
+		{ "time",	CLONE_NEWTIME },
+		{ "user",	CLONE_NEWUSER },
+		{ "uts",	CLONE_NEWUTS },
+		/* clang-format on */
+	};
+	size_t i;
+
+	for (i = 0; i < sizeof(ns_map) / sizeof(ns_map[0]); i++) {
+		if (strcmp(name, ns_map[i].name) == 0)
+			return ns_map[i].value;
+	}
+	return 0;
+}
+
+/*
+ * Parses a colon-delimited list of capability names into a bitmask.  Returns 0
+ * on success (mask 0 when the variable is unset or empty), or 1 on a parse
+ * error.
+ */
+static int parse_cap_list(const char *const env_var, __u64 *const mask)
+{
+	int ret = 1;
+	char *env_cap_name, *env_cap_name_next, *strcap;
+
+	*mask = 0;
+	env_cap_name = getenv(env_var);
+	if (!env_cap_name)
+		return 0;
+	env_cap_name = strdup(env_cap_name);
+	unsetenv(env_var);
+
+	env_cap_name_next = env_cap_name;
+	while ((strcap = strsep(&env_cap_name_next, ENV_DELIMITER))) {
+		cap_value_t cap;
+
+		if (strcmp(strcap, "") == 0)
+			continue;
+
+		if (cap_from_name(strcap, &cap)) {
+			fprintf(stderr, "Failed to parse capability \"%s\"\n",
+				strcap);
+			goto out_free_name;
+		}
+		*mask |= 1ULL << cap;
+	}
+	ret = 0;
+
+out_free_name:
+	free(env_cap_name);
+	return ret;
+}
+
+static int populate_ruleset_cap(const char *const allowed_env,
+				const char *const quiet_env,
+				const int ruleset_fd)
+{
+	struct landlock_capability_attr cap_attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+	};
+
+	if (parse_cap_list(allowed_env, &cap_attr.allowed_capabilities))
+		return 1;
+	if (parse_cap_list(quiet_env, &cap_attr.quiet_capabilities))
+		return 1;
+
+	if (!cap_attr.allowed_capabilities && !cap_attr.quiet_capabilities)
+		return 0;
+
+	if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_attr,
+			      0)) {
+		fprintf(stderr,
+			"Failed to update the ruleset with capabilities: %s\n",
+			strerror(errno));
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * Parses a colon-delimited list of namespace type names into a bitmask.
+ * Returns 0 on success (mask 0 when the variable is unset or empty), or 1 on a
+ * parse error.
+ */
+static int parse_ns_list(const char *const env_var, __u64 *const mask)
+{
+	int ret = 1;
+	char *env_ns_name, *env_ns_name_next, *strns;
+
+	*mask = 0;
+	env_ns_name = getenv(env_var);
+	if (!env_ns_name)
+		return 0;
+	env_ns_name = strdup(env_ns_name);
+	unsetenv(env_var);
+
+	env_ns_name_next = env_ns_name;
+	while ((strns = strsep(&env_ns_name_next, ENV_DELIMITER))) {
+		__u64 ns_type;
+
+		if (strcmp(strns, "") == 0)
+			continue;
+
+		ns_type = str2ns(strns);
+		if (!ns_type) {
+			fprintf(stderr, "Unknown namespace type \"%s\"\n",
+				strns);
+			goto out_free_name;
+		}
+		*mask |= ns_type;
+	}
+	ret = 0;
+
+out_free_name:
+	free(env_ns_name);
+	return ret;
+}
+
+static int populate_ruleset_ns(const char *const allowed_env,
+			       const char *const quiet_env,
+			       const int ruleset_fd)
+{
+	struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+	};
+
+	if (parse_ns_list(allowed_env, &ns_attr.allowed_namespace_types))
+		return 1;
+	if (parse_ns_list(quiet_env, &ns_attr.quiet_namespace_types))
+		return 1;
+
+	if (!ns_attr.allowed_namespace_types && !ns_attr.quiet_namespace_types)
+		return 0;
+
+	if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr,
+			      0)) {
+		fprintf(stderr,
+			"Failed to update the ruleset with namespace types: %s\n",
+			strerror(errno));
+		return 1;
+	}
+	return 0;
+}
+
 /* Returns true on error, false otherwise. */
 static bool check_ruleset_scope(const char *const env_var,
 				struct landlock_ruleset_attr *ruleset_attr)
@@ -369,7 +530,7 @@ static int add_quiet_access(const char *const env_var,
 	return 0;
 }
 
-#define LANDLOCK_ABI_LAST 10
+#define LANDLOCK_ABI_LAST 11
 
 #define XSTR(s) #s
 #define STR(s) XSTR(s)
@@ -397,6 +558,17 @@ static const char help[] =
 	"* " ENV_UDP_CONNECT_SEND_NAME ": remote UDP ports allowed to connect "
 	"or send to (client: use as destination port / server: receive only from it)\n"
 	"(caution: sending requires being able to bind to a local source port)\n"
+	"* " ENV_CAP_NAME ": capabilities allowed to use, as names "
+	"or numbers (e.g. cap_net_bind_service, cap_sys_admin, 18)\n"
+	"* " ENV_CAP_QUIET_NAME ": capabilities whose denial should not be logged, "
+	"same value format as " ENV_CAP_NAME " (quieting a member that is also "
+	"allowed is inert)\n"
+	"* " ENV_NS_NAME ": namespace types allowed to use "
+	"(cgroup, ipc, mnt, net, pid, time, user, uts)\n"
+	"* " ENV_NS_QUIET_NAME ": namespace types whose denial should not be "
+	"logged, same value format as " ENV_NS_NAME "\n"
+	"  (quieting a member that is also allowed is inert, as an allowed "
+	"member is never denied)\n"
 	"* " 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"
@@ -423,6 +595,8 @@ static const char help[] =
 	ENV_TCP_BIND_NAME "=\"9418\" "
 	ENV_TCP_CONNECT_NAME "=\"80:443\" "
 	ENV_UDP_CONNECT_SEND_NAME "=\"53\" "
+	ENV_CAP_NAME "=\"cap_sys_admin\" "
+	ENV_NS_NAME "=\"user:uts:net\" "
 	ENV_SCOPED_NAME "=\"a:s\" "
 	"%1$s bash -i\n"
 	"\n"
@@ -451,6 +625,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		.quiet_access_fs = 0,
 		.quiet_access_net = 0,
 		.quiet_scoped = 0,
+		.handled_perm = LANDLOCK_PERM_CAPABILITY_USE |
+				LANDLOCK_PERM_NAMESPACE_USE,
 	};
 	bool quiet_supported = true;
 	int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
@@ -545,7 +721,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
 			  LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
 		/* Removes quiet flags for ABI < 10 later on. */
 		quiet_supported = false;
-
+		__attribute__((fallthrough));
+	case 10:
+		/* Removes LANDLOCK_PERM_* for ABI < 11 */
+		ruleset_attr.handled_perm &= ~(LANDLOCK_PERM_NAMESPACE_USE |
+					       LANDLOCK_PERM_CAPABILITY_USE);
 		/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
@@ -590,6 +770,14 @@ int main(const int argc, char *const argv[], char *const *const envp)
 			~LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
 	}
 
+	/* Removes capability handling if not set by a user. */
+	if (!getenv(ENV_CAP_NAME) && !getenv(ENV_CAP_QUIET_NAME))
+		ruleset_attr.handled_perm &= ~LANDLOCK_PERM_CAPABILITY_USE;
+
+	/* Removes namespace handling if not set by a user. */
+	if (!getenv(ENV_NS_NAME) && !getenv(ENV_NS_QUIET_NAME))
+		ruleset_attr.handled_perm &= ~LANDLOCK_PERM_NAMESPACE_USE;
+
 	if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
 		return 1;
 
@@ -673,6 +861,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		}
 	}
 
+	if (populate_ruleset_cap(ENV_CAP_NAME, ENV_CAP_QUIET_NAME, ruleset_fd))
+		goto err_close_ruleset;
+
+	if (populate_ruleset_ns(ENV_NS_NAME, ENV_NS_QUIET_NAME, ruleset_fd))
+		goto err_close_ruleset;
+
 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
 		perror("Failed to restrict privileges");
 		goto err_close_ruleset;
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index ffa7646d99f3ef..1927b81fea93d7 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -8,7 +8,9 @@ landlock-y := \
 	cred.o \
 	task.o \
 	fs.o \
-	tsync.o
+	tsync.o \
+	ns.o \
+	cap.o
 
 landlock-$(CONFIG_INET) += net.o
 
diff --git a/security/landlock/access.h b/security/landlock/access.h
index d926078bf0a5f5..cb456eee8c32ff 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -19,9 +19,9 @@
 
 /*
  * All access rights that are denied by default whether they are handled or not
- * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
- * entries when we need to get the absolute handled access masks, see
- * landlock_upgrade_handled_access_masks().
+ * by a ruleset/layer.  This must be ORed with the .handled field of all
+ * ruleset->layers[] entries when we need to get the absolute handled access
+ * masks, see landlock_upgrade_handled_layer_config().
  */
 /* clang-format off */
 #define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
@@ -42,14 +42,17 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
 static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
 /* Makes sure all scoped rights can be stored. */
 static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
+/* Makes sure all permission types can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_PERM);
 /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
 static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
 
-/* Ruleset access masks. */
+/* Handled access masks (bitfields only). */
 struct access_masks {
 	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
 	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
 	access_mask_t scope : LANDLOCK_NUM_SCOPE;
+	access_mask_t perm : LANDLOCK_NUM_PERM;
 } __packed __aligned(sizeof(u32));
 
 union access_masks_all {
@@ -61,6 +64,62 @@ union access_masks_all {
 static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
 	      sizeof(typeof_member(union access_masks_all, all)));
 
+/**
+ * struct perm_masks - Per-layer allowed bitmasks for permission types
+ *
+ * Compact bitfield struct holding the allowed bitmasks for permission types
+ * that use flat (non-tree) per-layer storage.  All fields share a single 64-bit
+ * storage unit.
+ */
+struct perm_masks {
+	/**
+	 * @caps: Allowed capabilities.  Each bit corresponds to a ``CAP_*``
+	 * value (e.g. ``CAP_NET_RAW`` = bit 13).  Bits are stored directly
+	 * (sequential mapping) and masked with ``CAP_VALID_MASK`` at rule-add
+	 * time.
+	 */
+	u64 caps : LANDLOCK_NUM_PERM_CAP;
+	/**
+	 * @ns: Allowed namespace types.  Each bit corresponds to a sequential
+	 * index assigned by the ``_LANDLOCK_NS_*`` enum (derived from
+	 * ``FOR_EACH_NS_TYPE``).  Bits are converted from ``CLONE_NEW*`` flags
+	 * at rule-add time via ``landlock_ns_types_to_bits()`` and at
+	 * enforcement time via ``landlock_ns_type_to_bit()``.
+	 */
+	u64 ns : LANDLOCK_NUM_PERM_NS;
+} __packed __aligned(sizeof(u64));
+
+static_assert(sizeof(struct perm_masks) == sizeof(u64));
+/* All perm_masks bitfields must fit in a single u64. */
+static_assert(LANDLOCK_NUM_PERM_CAP + LANDLOCK_NUM_PERM_NS <=
+	      BITS_PER_TYPE(u64));
+
+/**
+ * struct layer_config - Per-layer access configuration
+ *
+ * Wraps the handled-access bitfields together with per-layer allowed bitmasks.
+ * This is the element type of the &struct landlock_ruleset.layers FAM.
+ *
+ * Unlike filesystem and network access rights, which are tracked per-object in
+ * red-black trees, namespace types and capabilities use flat bitmasks because
+ * their keyspaces are small and bounded (~8 namespace types, 41 capabilities).
+ * A single rule adds to the allowed set via bitwise OR; at enforcement time
+ * each layer is checked directly (no tree lookup needed).
+ */
+struct layer_config {
+	/**
+	 * @allowed: Per-layer allowed bitmasks for permission types.  Placed
+	 * before @handled so the wider, more-aligned member comes first,
+	 * avoiding internal padding.
+	 */
+	struct perm_masks allowed;
+	/**
+	 * @handled: Bitmask of access rights handled (i.e. restricted) by this
+	 * layer.
+	 */
+	struct access_masks handled;
+};
+
 /**
  * struct layer_mask - The access rights and rule flags for a layer.
  *
@@ -123,17 +182,17 @@ static_assert(BITS_PER_TYPE(deny_masks_t) >=
 static_assert(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS) == 1);
 
 /* Upgrades with all initially denied by default access rights. */
-static inline struct access_masks
-landlock_upgrade_handled_access_masks(struct access_masks access_masks)
+static inline struct layer_config
+landlock_upgrade_handled_layer_config(struct layer_config layer_config)
 {
 	/*
 	 * All access rights that are denied by default whether they are
 	 * explicitly handled or not.
 	 */
-	if (access_masks.fs)
-		access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+	if (layer_config.handled.fs)
+		layer_config.handled.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
 
-	return access_masks;
+	return layer_config;
 }
 
 /* Checks the subset relation between access masks. */
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 50536c5685262e..19a8f462678fe4 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -14,10 +14,12 @@
 
 #include "access.h"
 #include "audit.h"
+#include "cap.h"
 #include "common.h"
 #include "cred.h"
 #include "domain.h"
 #include "limits.h"
+#include "ns.h"
 #include "ruleset.h"
 
 static const char *const fs_access_strings[] = {
@@ -82,6 +84,14 @@ get_blocker(const enum landlock_request_type type,
 	case LANDLOCK_REQUEST_SCOPE_SIGNAL:
 		WARN_ON_ONCE(access_bit != -1);
 		return "scope.signal";
+
+	case LANDLOCK_REQUEST_NAMESPACE:
+		WARN_ON_ONCE(access_bit != -1);
+		return "perm.namespace_use";
+
+	case LANDLOCK_REQUEST_CAPABILITY:
+		WARN_ON_ONCE(access_bit != -1);
+		return "perm.capability_use";
 	}
 
 	WARN_ON_ONCE(1);
@@ -625,7 +635,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
 		 */
 		const access_mask_t quiet_mask =
 			pick_access_mask_for_request_type(
-				request->type, youngest_denied->quiet_masks);
+				request->type, youngest_denied->quiet_access);
 
 		quiet_applicable_to_access = (quiet_mask & missing) == missing;
 	} else {
@@ -634,7 +644,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
 		 * We check request->type to distinguish between the two cases.
 		 */
 		const access_mask_t quiet_mask =
-			youngest_denied->quiet_masks.scope;
+			youngest_denied->quiet_access.scope;
 
 		switch (request->type) {
 		case LANDLOCK_REQUEST_SCOPE_SIGNAL:
@@ -646,6 +656,23 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
 				!!(quiet_mask &
 				   LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
 			break;
+		/*
+		 * Capability and namespace denials are per-member (single
+		 * axis): the denied member is quiet when its bit is set in the
+		 * denying layer's quiet_perm mask.  The member is recovered
+		 * from the audit data recorded by the hook.
+		 */
+		case LANDLOCK_REQUEST_CAPABILITY:
+			quiet_applicable_to_access =
+				!!(youngest_denied->quiet_perm.caps &
+				   landlock_cap_to_bit(request->audit.u.cap));
+			break;
+		case LANDLOCK_REQUEST_NAMESPACE:
+			quiet_applicable_to_access =
+				!!(youngest_denied->quiet_perm.ns &
+				   landlock_ns_type_to_bit(
+					   request->audit.u.ns.ns_type));
+			break;
 		/*
 		 * Leave LANDLOCK_REQUEST_PTRACE and
 		 * LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 620f8a24291d5d..1e00b12d08a7df 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -21,6 +21,8 @@ enum landlock_request_type {
 	LANDLOCK_REQUEST_NET_ACCESS,
 	LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
 	LANDLOCK_REQUEST_SCOPE_SIGNAL,
+	LANDLOCK_REQUEST_NAMESPACE,
+	LANDLOCK_REQUEST_CAPABILITY,
 };
 
 /*
diff --git a/security/landlock/cap.c b/security/landlock/cap.c
new file mode 100644
index 00000000000000..d8bdbaea444056
--- /dev/null
+++ b/security/landlock/cap.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Capability hooks
+ *
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#include <linux/capability.h>
+#include <linux/cred.h>
+#include <linux/lsm_audit.h>
+#include <linux/lsm_hooks.h>
+#include <uapi/linux/landlock.h>
+
+#include "audit.h"
+#include "cap.h"
+#include "cred.h"
+#include "limits.h"
+#include "ruleset.h"
+#include "setup.h"
+
+static const struct access_masks cap_perm = {
+	.perm = LANDLOCK_PERM_CAPABILITY_USE,
+};
+
+/**
+ * hook_capable - Deny capability use for Landlock-sandboxed processes
+ *
+ * @cred: Credentials being checked.
+ * @ns: User namespace for the capability check.
+ * @cap: Capability number (CAP_*).
+ * @opts: Capability check options.  CAP_OPT_NOAUDIT suppresses audit logging.
+ *
+ * Pure bitmask check: denies the capability if it is not in the layer's allowed
+ * set.  This hook is purely restrictive: commoncap is registered with
+ * LSM_ORDER_FIRST so cap_capable() always runs first, which means Landlock can
+ * deny capabilities that commoncap would allow, but never grant capabilities
+ * that commoncap denied.
+ *
+ * Return: 0 if allowed, -EPERM if capability use is denied.
+ */
+static int hook_capable(const struct cred *cred, struct user_namespace *ns,
+			int cap, unsigned int opts)
+{
+	const struct landlock_cred_security *subject;
+	size_t denied_layer;
+
+	subject = landlock_get_applicable_subject(cred, cap_perm, NULL);
+	if (!subject)
+		return 0;
+
+	denied_layer = landlock_perm_is_denied(subject->domain,
+					       LANDLOCK_PERM_CAPABILITY_USE,
+					       landlock_cap_to_bit(cap));
+	if (!denied_layer)
+		return 0;
+
+	if (!(opts & CAP_OPT_NOAUDIT))
+		landlock_log_denial(subject,
+				    &(struct landlock_request){
+					    .type = LANDLOCK_REQUEST_CAPABILITY,
+					    .audit.type = LSM_AUDIT_DATA_CAP,
+					    .audit.u.cap = cap,
+					    .layer_plus_one = denied_layer,
+				    });
+
+	return -EPERM;
+}
+
+static struct security_hook_list landlock_hooks[] __ro_after_init = {
+	LSM_HOOK_INIT(capable, hook_capable),
+};
+
+__init void landlock_add_cap_hooks(void)
+{
+	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+			   &landlock_lsmid);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+#include <kunit/test.h>
+
+static void test_cap_to_bit(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, BIT_ULL(0), landlock_cap_to_bit(0));
+	KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW),
+			landlock_cap_to_bit(CAP_NET_RAW));
+	KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_SYS_ADMIN),
+			landlock_cap_to_bit(CAP_SYS_ADMIN));
+	KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_LAST_CAP),
+			landlock_cap_to_bit(CAP_LAST_CAP));
+}
+
+static void test_cap_to_bit_invalid(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, 0ULL, landlock_cap_to_bit(-1));
+	KUNIT_EXPECT_EQ(test, 0ULL, landlock_cap_to_bit(CAP_LAST_CAP + 1));
+}
+
+static void test_caps_to_bits_valid(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, (u64)CAP_VALID_MASK,
+			landlock_caps_to_bits(CAP_VALID_MASK));
+	KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW),
+			landlock_caps_to_bits(BIT_ULL(CAP_NET_RAW)));
+}
+
+static void test_caps_to_bits_unknown(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, 0ULL,
+			landlock_caps_to_bits(BIT_ULL(CAP_LAST_CAP + 1)));
+}
+
+static void test_caps_to_bits_zero(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, 0ULL, landlock_caps_to_bits(0));
+}
+
+static struct kunit_case test_cases[] = {
+	/* clang-format off */
+	KUNIT_CASE(test_cap_to_bit),
+	KUNIT_CASE(test_cap_to_bit_invalid),
+	KUNIT_CASE(test_caps_to_bits_valid),
+	KUNIT_CASE(test_caps_to_bits_unknown),
+	KUNIT_CASE(test_caps_to_bits_zero),
+	{}
+	/* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+	.name = "landlock_cap",
+	.test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/cap.h b/security/landlock/cap.h
new file mode 100644
index 00000000000000..3f12fd7d978822
--- /dev/null
+++ b/security/landlock/cap.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Capability hooks
+ *
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#ifndef _SECURITY_LANDLOCK_CAP_H
+#define _SECURITY_LANDLOCK_CAP_H
+
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/capability.h>
+#include <linux/compiler_attributes.h>
+#include <linux/types.h>
+
+/**
+ * landlock_cap_to_bit - Convert a capability number to a compact bitmask
+ *
+ * @cap: Capability number (CAP_*).
+ *
+ * Return: BIT_ULL(@cap), or 0 if @cap is invalid (with a WARN).
+ */
+static inline __attribute_const__ u64 landlock_cap_to_bit(const int cap)
+{
+	if (WARN_ON_ONCE(!cap_valid(cap)))
+		return 0;
+
+	return BIT_ULL(cap);
+}
+
+/**
+ * landlock_caps_to_bits - Validate and mask a capability bitmask
+ *
+ * @capabilities: Bitmask of capabilities (e.g. from user space).
+ *
+ * Return: @capabilities masked to known capabilities.  Warns if unknown bits
+ * are present (callers must pre-mask for user input).
+ */
+static inline __attribute_const__ u64
+landlock_caps_to_bits(const u64 capabilities)
+{
+	/* Callers pre-mask (CAP_VALID_MASK); the WARN guards future callers. */
+	WARN_ON_ONCE(capabilities & ~CAP_VALID_MASK);
+	return capabilities & CAP_VALID_MASK;
+}
+
+__init void landlock_add_cap_hooks(void);
+
+#endif /* _SECURITY_LANDLOCK_CAP_H */
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index f287c56b5fd4b2..35d6bfac88997e 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -139,7 +139,7 @@ landlock_get_applicable_subject(const struct cred *const cred,
 	for (layer_level = domain->num_layers - 1; layer_level >= 0;
 	     layer_level--) {
 		union access_masks_all layer = {
-			.masks = domain->access_masks[layer_level],
+			.masks = domain->layers[layer_level].handled,
 		};
 
 		if (layer.all & masks_all.all) {
@@ -153,6 +153,59 @@ landlock_get_applicable_subject(const struct cred *const cred,
 	return NULL;
 }
 
+/**
+ * landlock_perm_is_denied - Check if a permission bitmask request is denied
+ *
+ * @domain: The enforced domain.
+ * @perm_bit: The LANDLOCK_PERM_* flag to check.  Must have exactly one
+ *            bit set.
+ * @request_value: Compact bitmask to look for (e.g. result of
+ *                 `landlock_ns_type_to_bit(CLONE_NEWNET)`).  Must have
+ *                 exactly one bit set.
+ *
+ * Iterate from the youngest layer to the oldest.  For each layer that handles
+ * @perm_bit, check whether @request_value is present in the layer's allowed
+ * bitmask.  Return on the first (youngest) denying layer.
+ *
+ * Return: The youngest denying layer + 1, or 0 if allowed.
+ */
+static inline size_t
+landlock_perm_is_denied(const struct landlock_ruleset *const domain,
+			const access_mask_t perm_bit, const u64 request_value)
+{
+	ssize_t layer;
+
+	BUILD_BUG_ON(sizeof(perm_bit) > sizeof(u32));
+
+	if (WARN_ON_ONCE(hweight32(perm_bit) != 1) ||
+	    WARN_ON_ONCE(hweight64(request_value) != 1))
+		return domain->num_layers;
+
+	for (layer = domain->num_layers - 1; layer >= 0; layer--) {
+		u64 allowed;
+
+		if (!(domain->layers[layer].handled.perm & perm_bit))
+			continue;
+
+		switch (perm_bit) {
+		case LANDLOCK_PERM_NAMESPACE_USE:
+			allowed = domain->layers[layer].allowed.ns;
+			break;
+		case LANDLOCK_PERM_CAPABILITY_USE:
+			allowed = domain->layers[layer].allowed.caps;
+			break;
+		default:
+			WARN_ONCE(1, "Unknown permission %u\n",
+				  (unsigned int)perm_bit);
+			return layer + 1;
+		}
+
+		if (!(allowed & request_value))
+			return layer + 1;
+	}
+	return 0;
+}
+
 __init void landlock_add_cred_hooks(void);
 
 #endif /* _SECURITY_LANDLOCK_CRED_H */
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 2a1660e3dea7e0..c2b72795b0d624 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -112,10 +112,16 @@ struct landlock_hierarchy {
 		 */
 		log_new_exec : 1;
 	/**
-	 * @quiet_masks: Bitmasks of access that should be quieted (i.e. not
+	 * @quiet_access: Bitmasks of access that should be quieted (i.e. not
 	 * logged) if the related object is marked as quiet.
 	 */
-	struct access_masks quiet_masks;
+	struct access_masks quiet_access;
+	/**
+	 * @quiet_perm: Per-member quiet bitmasks for permission types
+	 * (capabilities and namespace types).  A member denied by this layer is
+	 * not logged when its bit is set here.
+	 */
+	struct perm_masks quiet_perm;
 #endif /* CONFIG_AUDIT */
 };
 
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 08d5f2f6d321a7..6cdd649005f48b 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -11,7 +11,9 @@
 #define _SECURITY_LANDLOCK_LIMITS_H
 
 #include <linux/bitops.h>
+#include <linux/capability.h>
 #include <linux/limits.h>
+#include <linux/ns/ns_common_types.h>
 #include <uapi/linux/landlock.h>
 
 /* clang-format off */
@@ -31,6 +33,13 @@
 #define LANDLOCK_MASK_SCOPE		((LANDLOCK_LAST_SCOPE << 1) - 1)
 #define LANDLOCK_NUM_SCOPE		__const_hweight64(LANDLOCK_MASK_SCOPE)
 
+#define LANDLOCK_LAST_PERM		LANDLOCK_PERM_CAPABILITY_USE
+#define LANDLOCK_MASK_PERM		((LANDLOCK_LAST_PERM << 1) - 1)
+#define LANDLOCK_NUM_PERM		__const_hweight64(LANDLOCK_MASK_PERM)
+
+#define LANDLOCK_NUM_PERM_NS		__const_hweight64((u64)(CLONE_NS_ALL))
+#define LANDLOCK_NUM_PERM_CAP		(CAP_LAST_CAP + 1)
+
 #define LANDLOCK_NUM_ACCESS_MAX \
 	MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE)
 
diff --git a/security/landlock/ns.c b/security/landlock/ns.c
new file mode 100644
index 00000000000000..9d704b0ec7fffc
--- /dev/null
+++ b/security/landlock/ns.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Namespace hooks
+ *
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#include <linux/lsm_audit.h>
+#include <linux/lsm_hooks.h>
+#include <linux/ns/ns_common_types.h>
+#include <linux/ns_common.h>
+#include <linux/nsproxy.h>
+#include <uapi/linux/landlock.h>
+
+#include "audit.h"
+#include "cred.h"
+#include "limits.h"
+#include "ns.h"
+#include "ruleset.h"
+#include "setup.h"
+
+/* Ensures the audit ns_id field can hold ns_common.ns_id without truncation. */
+static_assert(sizeof(((struct common_audit_data *)NULL)->u.ns.ns_id) >=
+	      sizeof(((struct ns_common *)NULL)->ns_id));
+
+static const struct access_masks ns_perm = {
+	.perm = LANDLOCK_PERM_NAMESPACE_USE,
+};
+
+/**
+ * check_ns_type - Check namespace entry permission
+ *
+ * @ns: The namespace being allocated or installed.
+ *
+ * Shared check for namespace_init (creation via unshare(2) or clone(2)) and
+ * namespace_install (entry via setns(2)): denies when the namespace type is not
+ * in the domain's allowed set.  At allocation time @ns->ns_id is still zero and
+ * is logged as such.
+ *
+ * Return: 0 if allowed, -EPERM if denied.
+ */
+static int check_ns_type(struct ns_common *const ns)
+{
+	const struct landlock_cred_security *subject;
+	size_t denied_layer;
+
+	subject =
+		landlock_get_applicable_subject(current_cred(), ns_perm, NULL);
+	if (!subject)
+		return 0;
+
+	denied_layer = landlock_perm_is_denied(
+		subject->domain, LANDLOCK_PERM_NAMESPACE_USE,
+		landlock_ns_type_to_bit(ns->ns_type));
+	if (!denied_layer)
+		return 0;
+
+	landlock_log_denial(subject, &(struct landlock_request){
+					     .type = LANDLOCK_REQUEST_NAMESPACE,
+					     .audit.type = LSM_AUDIT_DATA_NS,
+					     .audit.u.ns.ns_type = ns->ns_type,
+					     .audit.u.ns.ns_id = ns->ns_id,
+					     .layer_plus_one = denied_layer,
+				     });
+	return -EPERM;
+}
+
+static int hook_namespace_init(struct ns_common *const ns)
+{
+	return check_ns_type(ns);
+}
+
+static int hook_namespace_install(const struct nsset *const nsset,
+				  struct ns_common *const ns)
+{
+	return check_ns_type(ns);
+}
+
+static struct security_hook_list landlock_hooks[] __ro_after_init = {
+	LSM_HOOK_INIT(namespace_init, hook_namespace_init),
+	LSM_HOOK_INIT(namespace_install, hook_namespace_install),
+};
+
+__init void landlock_add_ns_hooks(void)
+{
+	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+			   &landlock_lsmid);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+#include <kunit/test.h>
+
+/* clang-format off */
+#define _TEST_NS_BIT(struct_name, flag) \
+	do { \
+		const u64 bit = landlock_ns_type_to_bit(flag); \
+		KUNIT_EXPECT_NE(test, 0ULL, bit); \
+		KUNIT_EXPECT_EQ(test, 0ULL, seen & bit); \
+		seen |= bit; \
+	} while (0);
+/* clang-format on */
+
+static void test_ns_type_to_bit(struct kunit *const test)
+{
+	u64 seen = 0;
+
+	FOR_EACH_NS_TYPE(_TEST_NS_BIT)
+
+	KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_PERM_NS - 1, 0), seen);
+}
+
+static void test_ns_type_to_bit_unknown(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, 0ULL, landlock_ns_type_to_bit(CLONE_THREAD));
+}
+
+static void test_ns_types_to_bits_all(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_PERM_NS - 1, 0),
+			landlock_ns_types_to_bits(CLONE_NS_ALL));
+}
+
+/* clang-format off */
+#define _TEST_NS_SINGLE(struct_name, flag) \
+	KUNIT_EXPECT_EQ(test, landlock_ns_type_to_bit(flag), \
+			landlock_ns_types_to_bits(flag));
+/* clang-format on */
+
+static void test_ns_types_to_bits_single(struct kunit *const test)
+{
+	FOR_EACH_NS_TYPE(_TEST_NS_SINGLE)
+}
+
+static void test_ns_types_to_bits_zero(struct kunit *const test)
+{
+	KUNIT_EXPECT_EQ(test, 0ULL, landlock_ns_types_to_bits(0));
+}
+
+static struct kunit_case test_cases[] = {
+	KUNIT_CASE(test_ns_type_to_bit),
+	KUNIT_CASE(test_ns_type_to_bit_unknown),
+	KUNIT_CASE(test_ns_types_to_bits_all),
+	KUNIT_CASE(test_ns_types_to_bits_single),
+	KUNIT_CASE(test_ns_types_to_bits_zero),
+	{}
+};
+
+static struct kunit_suite test_suite = {
+	.name = "landlock_ns",
+	.test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/ns.h b/security/landlock/ns.h
new file mode 100644
index 00000000000000..d78b36d8ce8239
--- /dev/null
+++ b/security/landlock/ns.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Namespace hooks
+ *
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#ifndef _SECURITY_LANDLOCK_NS_H
+#define _SECURITY_LANDLOCK_NS_H
+
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/compiler_attributes.h>
+#include <linux/ns/ns_common_types.h>
+#include <linux/types.h>
+
+#include "limits.h"
+
+/* _LANDLOCK_NS_CLONE_NEWCGROUP, */
+#define _LANDLOCK_NS_ENUM(struct_name, flag) _LANDLOCK_NS_##flag,
+
+/* _LANDLOCK_NS_CLONE_NEWCGROUP = 0, */
+enum {
+	FOR_EACH_NS_TYPE(_LANDLOCK_NS_ENUM) _LANDLOCK_NUM_NS_TYPES,
+};
+
+static_assert(_LANDLOCK_NUM_NS_TYPES == LANDLOCK_NUM_PERM_NS);
+
+/*
+ * case CLONE_NEWCGROUP:
+ *         return BIT_ULL(_LANDLOCK_NS_CLONE_NEWCGROUP);
+ */
+/* clang-format off */
+#define _LANDLOCK_NS_CASE(struct_name, flag) \
+	case flag: \
+		return BIT_ULL(_LANDLOCK_NS_##flag);
+/* clang-format on */
+
+static inline __attribute_const__ u64
+landlock_ns_type_to_bit(const unsigned long ns_type)
+{
+	switch (ns_type) {
+		FOR_EACH_NS_TYPE(_LANDLOCK_NS_CASE)
+	}
+	WARN_ONCE(1, "Unknown namespace type 0x%lx\n", ns_type);
+	return 0;
+}
+
+/*
+ * if (ns_types & CLONE_NEWCGROUP)
+ *         bits |= BIT_ULL(_LANDLOCK_NS_CLONE_NEWCGROUP);
+ */
+/* clang-format off */
+#define _LANDLOCK_NS_CONVERT(struct_name, flag) \
+	do { \
+		if (ns_types & (flag)) \
+			bits |= BIT_ULL(_LANDLOCK_NS_##flag); \
+	} while (0);
+/* clang-format on */
+
+static inline __attribute_const__ u64
+landlock_ns_types_to_bits(const u64 ns_types)
+{
+	u64 bits = 0;
+
+	/* Callers pre-mask (CLONE_NS_ALL); the WARN guards future callers. */
+	WARN_ON_ONCE(ns_types & ~CLONE_NS_ALL);
+	FOR_EACH_NS_TYPE(_LANDLOCK_NS_CONVERT)
+	return bits;
+}
+
+__init void landlock_add_ns_hooks(void);
+
+#endif /* _SECURITY_LANDLOCK_NS_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4dd09ea22c8444..45687f348bc547 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -33,7 +33,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 {
 	struct landlock_ruleset *new_ruleset;
 
-	new_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers,
+	new_ruleset = kzalloc_flex(*new_ruleset, layers, num_layers,
 				   GFP_KERNEL_ACCOUNT);
 	if (!new_ruleset)
 		return ERR_PTR(-ENOMEM);
@@ -47,22 +47,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 
 	new_ruleset->num_layers = num_layers;
 	/*
-	 * hierarchy = NULL
-	 * num_rules = 0
-	 * access_masks[] = 0
+	 * - hierarchy = NULL
+	 * - num_rules = 0
+	 * - layers[] = 0
 	 */
 	return new_ruleset;
 }
 
-struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask,
-			const access_mask_t net_access_mask,
-			const access_mask_t scope_mask)
+struct landlock_ruleset *landlock_create_ruleset(
+	const access_mask_t fs_access_mask, const access_mask_t net_access_mask,
+	const access_mask_t scope_mask, const access_mask_t perm_mask)
 {
 	struct landlock_ruleset *new_ruleset;
 
 	/* Informs about useless ruleset. */
-	if (!fs_access_mask && !net_access_mask && !scope_mask)
+	if (!fs_access_mask && !net_access_mask && !scope_mask && !perm_mask)
 		return ERR_PTR(-ENOMSG);
 	new_ruleset = create_ruleset(1);
 	if (IS_ERR(new_ruleset))
@@ -73,6 +72,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,
 		landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
 	if (scope_mask)
 		landlock_add_scope_mask(new_ruleset, scope_mask, 0);
+	if (perm_mask)
+		landlock_add_perm_mask(new_ruleset, perm_mask, 0);
 	return new_ruleset;
 }
 
@@ -387,8 +388,8 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 		err = -EINVAL;
 		goto out_unlock;
 	}
-	dst->access_masks[dst->num_layers - 1] =
-		landlock_upgrade_handled_access_masks(src->access_masks[0]);
+	dst->layers[dst->num_layers - 1] =
+		landlock_upgrade_handled_layer_config(src->layers[0]);
 
 	/* Merges the @src inode tree. */
 	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
@@ -402,6 +403,18 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 		goto out_unlock;
 #endif /* IS_ENABLED(CONFIG_INET) */
 
+#ifdef CONFIG_AUDIT
+	dst->hierarchy->quiet_access = src->quiet_access;
+	/*
+	 * quiet_perm is accumulated per rule by landlock_add_rule() under
+	 * ruleset->lock, so it must be snapshotted here, in the same critical
+	 * section as the allowed mask; a later, separate copy would race
+	 * landlock_add_rule() and split the snapshot.  The immutable
+	 * quiet_access above shares this site for consistency.
+	 */
+	dst->hierarchy->quiet_perm = src->quiet_perm;
+#endif /* CONFIG_AUDIT */
+
 out_unlock:
 	mutex_unlock(&src->lock);
 	mutex_unlock(&dst->lock);
@@ -470,8 +483,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 		goto out_unlock;
 	}
 	/* Copies the parent layer stack and leaves a space for the new layer. */
-	memcpy(child->access_masks, parent->access_masks,
-	       flex_array_size(parent, access_masks, parent->num_layers));
+	memcpy(child->layers, parent->layers,
+	       flex_array_size(parent, layers, parent->num_layers));
 
 	if (WARN_ON_ONCE(!parent->hierarchy)) {
 		err = -EINVAL;
@@ -587,10 +600,6 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
 	if (err)
 		return ERR_PTR(err);
 
-#ifdef CONFIG_AUDIT
-	new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
-#endif /* CONFIG_AUDIT */
-
 	return no_free_ptr(new_dom);
 }
 
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0437adf1742876..005ac3550088ed 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -157,7 +157,7 @@ struct landlock_ruleset {
 		 * section.  This is only used by
 		 * landlock_put_ruleset_deferred() when @usage reaches zero.
 		 * The fields @lock, @usage, @num_rules, @num_layers,
-		 * @quiet_masks and @access_masks are then unused.
+		 * @quiet_access, @quiet_perm and @layers are then unused.
 		 */
 		struct work_struct work_free;
 		struct {
@@ -184,32 +184,40 @@ struct landlock_ruleset {
 			 */
 			u32 num_layers;
 			/**
-			 * @quiet_masks: Stores the quiet flags for an unmerged
+			 * @quiet_access: Stores the quiet flags for an unmerged
 			 * ruleset.  For a merged domain, this is stored in each
 			 * layer's struct landlock_hierarchy instead.
 			 */
-			struct access_masks quiet_masks;
+			struct access_masks quiet_access;
 			/**
-			 * @access_masks: Contains the subset of filesystem and
-			 * network actions that are restricted by a ruleset.
+			 * @quiet_perm: Per-member quiet bitmasks for permission
+			 * types (capabilities and namespace types) of an
+			 * unmerged ruleset.  A denied member whose bit is set
+			 * here is not logged when this layer is the one that
+			 * denies it.  For a merged domain, this is stored in
+			 * each layer's struct landlock_hierarchy instead.
+			 */
+			struct perm_masks quiet_perm;
+			/**
+			 * @layers: Per-layer access configuration, including
+			 * handled access masks and allowed permission bitmasks.
 			 * A domain saves all layers of merged rulesets in a
 			 * stack (FAM), starting from the first layer to the
 			 * last one.  These layers are used when merging
-			 * rulesets, for user space backward compatibility
-			 * (i.e. future-proof), and to properly handle merged
-			 * rulesets without overlapping access rights.  These
-			 * layers are set once and never changed for the
-			 * lifetime of the ruleset.
+			 * rulesets, for user space backward compatibility (i.e.
+			 * future-proof), and to properly handle merged rulesets
+			 * without overlapping access rights.  These layers are
+			 * set once and never changed for the lifetime of the
+			 * ruleset.
 			 */
-			struct access_masks access_masks[];
+			struct layer_config layers[] __counted_by(num_layers);
 		};
 	};
 };
 
-struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t access_mask_fs,
-			const access_mask_t access_mask_net,
-			const access_mask_t scope_mask);
+struct landlock_ruleset *landlock_create_ruleset(
+	const access_mask_t access_mask_fs, const access_mask_t access_mask_net,
+	const access_mask_t scope_mask, const access_mask_t perm_mask);
 
 void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@@ -241,7 +249,8 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
  *
  * @domain: Landlock ruleset (used as a domain)
  *
- * Return: An access_masks result of the OR of all the domain's access masks.
+ * Return: An access_masks result of the OR of all the domain's handled access
+ * masks.
  */
 static inline struct access_masks
 landlock_union_access_masks(const struct landlock_ruleset *const domain)
@@ -251,7 +260,7 @@ landlock_union_access_masks(const struct landlock_ruleset *const domain)
 
 	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
 		union access_masks_all layer = {
-			.masks = domain->access_masks[layer_level],
+			.masks = domain->layers[layer_level].handled,
 		};
 
 		matches.all |= layer.all;
@@ -269,7 +278,7 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
 
 	/* Should already be checked in sys_landlock_create_ruleset(). */
 	WARN_ON_ONCE(fs_access_mask != fs_mask);
-	ruleset->access_masks[layer_level].fs |= fs_mask;
+	ruleset->layers[layer_level].handled.fs |= fs_mask;
 }
 
 static inline void
@@ -281,7 +290,7 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
 
 	/* Should already be checked in sys_landlock_create_ruleset(). */
 	WARN_ON_ONCE(net_access_mask != net_mask);
-	ruleset->access_masks[layer_level].net |= net_mask;
+	ruleset->layers[layer_level].handled.net |= net_mask;
 }
 
 static inline void
@@ -292,7 +301,7 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
 
 	/* Should already be checked in sys_landlock_create_ruleset(). */
 	WARN_ON_ONCE(scope_mask != mask);
-	ruleset->access_masks[layer_level].scope |= mask;
+	ruleset->layers[layer_level].handled.scope |= mask;
 }
 
 static inline access_mask_t
@@ -300,7 +309,7 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 			    const u16 layer_level)
 {
 	/* Handles all initially denied by default access rights. */
-	return ruleset->access_masks[layer_level].fs |
+	return ruleset->layers[layer_level].handled.fs |
 	       _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
 }
 
@@ -308,14 +317,32 @@ static inline access_mask_t
 landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
 			     const u16 layer_level)
 {
-	return ruleset->access_masks[layer_level].net;
+	return ruleset->layers[layer_level].handled.net;
 }
 
 static inline access_mask_t
 landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
 			const u16 layer_level)
 {
-	return ruleset->access_masks[layer_level].scope;
+	return ruleset->layers[layer_level].handled.scope;
+}
+
+static inline void
+landlock_add_perm_mask(struct landlock_ruleset *const ruleset,
+		       const access_mask_t perm_mask, const u16 layer_level)
+{
+	access_mask_t mask = perm_mask & LANDLOCK_MASK_PERM;
+
+	/* Should already be checked in sys_landlock_create_ruleset(). */
+	WARN_ON_ONCE(perm_mask != mask);
+	ruleset->layers[layer_level].handled.perm |= mask;
+}
+
+static inline access_mask_t
+landlock_get_perm_mask(const struct landlock_ruleset *const ruleset,
+		       const u16 layer_level)
+{
+	return ruleset->layers[layer_level].handled.perm;
 }
 
 bool landlock_unmask_layers(const struct landlock_rule *const rule,
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 47dac1736f100a..971419d663bb86 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -11,12 +11,14 @@
 #include <linux/lsm_hooks.h>
 #include <uapi/linux/lsm.h>
 
+#include "cap.h"
 #include "common.h"
 #include "cred.h"
 #include "errata.h"
 #include "fs.h"
 #include "id.h"
 #include "net.h"
+#include "ns.h"
 #include "setup.h"
 #include "task.h"
 
@@ -68,6 +70,8 @@ static int __init landlock_init(void)
 	landlock_add_task_hooks();
 	landlock_add_fs_hooks();
 	landlock_add_net_hooks();
+	landlock_add_ns_hooks();
+	landlock_add_cap_hooks();
 	landlock_init_id();
 	landlock_initialized = true;
 	pr_info("Up and running.\n");
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 36b02892c62f52..6d0b02dae2ac59 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -20,6 +20,7 @@
 #include <linux/fs.h>
 #include <linux/limits.h>
 #include <linux/mount.h>
+#include <linux/ns/ns_common_types.h>
 #include <linux/path.h>
 #include <linux/sched.h>
 #include <linux/security.h>
@@ -29,11 +30,13 @@
 #include <linux/uaccess.h>
 #include <uapi/linux/landlock.h>
 
+#include "cap.h"
 #include "cred.h"
 #include "domain.h"
 #include "fs.h"
 #include "limits.h"
 #include "net.h"
+#include "ns.h"
 #include "ruleset.h"
 #include "setup.h"
 #include "tsync.h"
@@ -95,7 +98,10 @@ static void build_check_abi(void)
 	struct landlock_ruleset_attr ruleset_attr;
 	struct landlock_path_beneath_attr path_beneath_attr;
 	struct landlock_net_port_attr net_port_attr;
+	struct landlock_namespace_attr namespace_attr;
+	struct landlock_capability_attr capability_attr;
 	size_t ruleset_size, path_beneath_size, net_port_size;
+	size_t namespace_size, capability_size;
 
 	/*
 	 * For each user space ABI structures, first checks that there is no
@@ -108,8 +114,9 @@ static void build_check_abi(void)
 	ruleset_size += sizeof(ruleset_attr.quiet_access_fs);
 	ruleset_size += sizeof(ruleset_attr.quiet_access_net);
 	ruleset_size += sizeof(ruleset_attr.quiet_scoped);
+	ruleset_size += sizeof(ruleset_attr.handled_perm);
 	BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
-	BUILD_BUG_ON(sizeof(ruleset_attr) != 48);
+	BUILD_BUG_ON(sizeof(ruleset_attr) != 56);
 
 	path_beneath_size = sizeof(path_beneath_attr.allowed_access);
 	path_beneath_size += sizeof(path_beneath_attr.parent_fd);
@@ -120,6 +127,18 @@ static void build_check_abi(void)
 	net_port_size += sizeof(net_port_attr.port);
 	BUILD_BUG_ON(sizeof(net_port_attr) != net_port_size);
 	BUILD_BUG_ON(sizeof(net_port_attr) != 16);
+
+	namespace_size = sizeof(namespace_attr.perm);
+	namespace_size += sizeof(namespace_attr.allowed_namespace_types);
+	namespace_size += sizeof(namespace_attr.quiet_namespace_types);
+	BUILD_BUG_ON(sizeof(namespace_attr) != namespace_size);
+	BUILD_BUG_ON(sizeof(namespace_attr) != 24);
+
+	capability_size = sizeof(capability_attr.perm);
+	capability_size += sizeof(capability_attr.allowed_capabilities);
+	capability_size += sizeof(capability_attr.quiet_capabilities);
+	BUILD_BUG_ON(sizeof(capability_attr) != capability_size);
+	BUILD_BUG_ON(sizeof(capability_attr) != 24);
 }
 
 /* Ruleset handling */
@@ -169,7 +188,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 = 10;
+const int landlock_abi_version = 11;
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
@@ -270,16 +289,22 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
 	    ruleset_attr.scoped)
 		return -EINVAL;
 
+	/* Checks permission content (and 32-bits cast). */
+	if ((ruleset_attr.handled_perm | LANDLOCK_MASK_PERM) !=
+	    LANDLOCK_MASK_PERM)
+		return -EINVAL;
+
 	/* Checks arguments and transforms to kernel struct. */
 	ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
 					  ruleset_attr.handled_access_net,
-					  ruleset_attr.scoped);
+					  ruleset_attr.scoped,
+					  ruleset_attr.handled_perm);
 	if (IS_ERR(ruleset))
 		return PTR_ERR(ruleset);
 
-	ruleset->quiet_masks.fs = ruleset_attr.quiet_access_fs;
-	ruleset->quiet_masks.net = ruleset_attr.quiet_access_net;
-	ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped;
+	ruleset->quiet_access.fs = ruleset_attr.quiet_access_fs;
+	ruleset->quiet_access.net = ruleset_attr.quiet_access_net;
+	ruleset->quiet_access.scope = ruleset_attr.quiet_scoped;
 
 	/* Creates anonymous FD referring to the ruleset. */
 	ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops,
@@ -367,12 +392,12 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
 		return -ENOMSG;
 
 	/* Checks that allowed_access matches the @ruleset constraints. */
-	mask = ruleset->access_masks[0].fs;
+	mask = ruleset->layers[0].handled.fs;
 	if ((path_beneath_attr.allowed_access | mask) != mask)
 		return -EINVAL;
 
 	/* Checks for useless quiet flag. */
-	if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.fs)
+	if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_access.fs)
 		return -EINVAL;
 
 	/* Gets and checks the new rule. */
@@ -413,7 +438,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
 		return -EINVAL;
 
 	/* Checks for useless quiet flag. */
-	if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.net)
+	if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_access.net)
 		return -EINVAL;
 
 	/* Denies inserting a rule with port greater than 65535. */
@@ -425,13 +450,142 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
 					net_port_attr.allowed_access, flags);
 }
 
+static int add_rule_namespace(struct landlock_ruleset *const ruleset,
+			      const void __user *const rule_attr,
+			      const u32 flags)
+{
+	struct landlock_namespace_attr ns_attr;
+	int res;
+	access_mask_t mask;
+
+	/*
+	 * Namespace rules support no add-rule flags.  In particular,
+	 * LANDLOCK_ADD_RULE_QUIET is filesystem/network only.
+	 */
+	if (flags)
+		return -EINVAL;
+
+	/* Copies raw user space buffer. */
+	res = copy_from_user(&ns_attr, rule_attr, sizeof(ns_attr));
+	if (res)
+		return -EFAULT;
+
+	/* Informs about useless rule: empty perm. */
+	if (!ns_attr.perm)
+		return -ENOMSG;
+
+	/*
+	 * The perm selector must match LANDLOCK_PERM_NAMESPACE_USE.  The valid
+	 * set is a single bit today, so this is an exact match now; the check
+	 * broadens to a subset test once another supported permission is added.
+	 */
+	if (ns_attr.perm != LANDLOCK_PERM_NAMESPACE_USE)
+		return -EINVAL;
+
+	/*
+	 * Checks that perm matches the ruleset constraints.  This also makes
+	 * quieting require the category to be handled.
+	 */
+	mask = landlock_get_perm_mask(ruleset, 0);
+	if (!(mask & LANDLOCK_PERM_NAMESPACE_USE))
+		return -EINVAL;
+
+	/*
+	 * Informs about useless rule: neither allows nor quiets anything.  A
+	 * quiet-only rule (empty allowed set) is legal.
+	 */
+	if (!ns_attr.allowed_namespace_types && !ns_attr.quiet_namespace_types)
+		return -ENOMSG;
+
+	/*
+	 * Stores only the namespace types this kernel knows about.  Unknown
+	 * bits are silently accepted for forward compatibility: user space
+	 * compiled against newer headers can pass new CLONE_NEW* flags without
+	 * getting EINVAL on older kernels.  Unknown bits have no effect because
+	 * no hook checks them.  The quiet bitmask suppresses logging of denials
+	 * attributed to this layer; see landlock_log_denial().
+	 */
+	mutex_lock(&ruleset->lock);
+	ruleset->layers[0].allowed.ns |= landlock_ns_types_to_bits(
+		ns_attr.allowed_namespace_types & CLONE_NS_ALL);
+	ruleset->quiet_perm.ns |= landlock_ns_types_to_bits(
+		ns_attr.quiet_namespace_types & CLONE_NS_ALL);
+	mutex_unlock(&ruleset->lock);
+	return 0;
+}
+
+static int add_rule_capability(struct landlock_ruleset *const ruleset,
+			       const void __user *const rule_attr,
+			       const u32 flags)
+{
+	struct landlock_capability_attr cap_attr;
+	int res;
+	access_mask_t mask;
+
+	/*
+	 * Capability rules support no add-rule flags.  In particular,
+	 * LANDLOCK_ADD_RULE_QUIET is filesystem/network only.
+	 */
+	if (flags)
+		return -EINVAL;
+
+	/* Copies raw user space buffer. */
+	res = copy_from_user(&cap_attr, rule_attr, sizeof(cap_attr));
+	if (res)
+		return -EFAULT;
+
+	/* Informs about useless rule: empty perm. */
+	if (!cap_attr.perm)
+		return -ENOMSG;
+
+	/*
+	 * The perm selector must match LANDLOCK_PERM_CAPABILITY_USE.  The valid
+	 * set is a single bit today, so this is an exact match now; the check
+	 * broadens to a subset test once another supported permission is added.
+	 */
+	if (cap_attr.perm != LANDLOCK_PERM_CAPABILITY_USE)
+		return -EINVAL;
+
+	/*
+	 * Checks that perm matches the ruleset constraints.  This also makes
+	 * quieting require the category to be handled.
+	 */
+	mask = landlock_get_perm_mask(ruleset, 0);
+	if (!(mask & LANDLOCK_PERM_CAPABILITY_USE))
+		return -EINVAL;
+
+	/*
+	 * Informs about useless rule: neither allows nor quiets anything.  A
+	 * quiet-only rule (empty allowed set) is legal.
+	 */
+	if (!cap_attr.allowed_capabilities && !cap_attr.quiet_capabilities)
+		return -ENOMSG;
+
+	/*
+	 * Stores only the capabilities this kernel knows about.  Unknown bits
+	 * are silently accepted for forward compatibility: user space compiled
+	 * against newer headers can pass new CAP_* bits without getting EINVAL
+	 * on older kernels.  Unknown bits have no effect because no hook checks
+	 * them.  The quiet bitmask suppresses logging of denials attributed to
+	 * this layer; see landlock_log_denial().
+	 */
+	mutex_lock(&ruleset->lock);
+	ruleset->layers[0].allowed.caps |= landlock_caps_to_bits(
+		cap_attr.allowed_capabilities & CAP_VALID_MASK);
+	ruleset->quiet_perm.caps |= landlock_caps_to_bits(
+		cap_attr.quiet_capabilities & CAP_VALID_MASK);
+	mutex_unlock(&ruleset->lock);
+	return 0;
+}
+
 /**
  * sys_landlock_add_rule - Add a new rule to a ruleset
  *
  * @ruleset_fd: File descriptor tied to the ruleset that should be extended
  *		with the new rule.
  * @rule_type: Identify the structure type pointed to by @rule_attr:
- *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
+ *             %LANDLOCK_RULE_PATH_BENEATH, %LANDLOCK_RULE_NET_PORT,
+ *             %LANDLOCK_RULE_NAMESPACE, or %LANDLOCK_RULE_CAPABILITY.
  * @rule_attr: Pointer to a rule (matching the @rule_type).
  * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET.
  *
@@ -485,6 +639,10 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 		return add_rule_path_beneath(ruleset, rule_attr, flags);
 	case LANDLOCK_RULE_NET_PORT:
 		return add_rule_net_port(ruleset, rule_attr, flags);
+	case LANDLOCK_RULE_NAMESPACE:
+		return add_rule_namespace(ruleset, rule_attr, flags);
+	case LANDLOCK_RULE_CAPABILITY:
+		return add_rule_capability(ruleset, rule_attr, flags);
 	default:
 		return -EINVAL;
 	}
diff --git a/security/lsm_audit.c b/security/lsm_audit.c
index 737f5a263a8f79..404ccbbbf94cbe 100644
--- a/security/lsm_audit.c
+++ b/security/lsm_audit.c
@@ -403,6 +403,10 @@ void audit_log_lsm_data(struct audit_buffer *ab,
 	case LSM_AUDIT_DATA_NLMSGTYPE:
 		audit_log_format(ab, " nl-msgtype=%hu", a->u.nlmsg_type);
 		break;
+	case LSM_AUDIT_DATA_NS:
+		audit_log_format(ab, " namespace_type=0x%x namespace_id=%llu",
+				 a->u.ns.ns_type, a->u.ns.ns_id);
+		break;
 	} /* switch (a->type) */
 }
 
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 7c0fd17f1601f2..dcd2a228c4f632 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -303,6 +303,7 @@ static void __init lsm_prepare(struct lsm_info *lsm)
 	lsm_blob_size_update(&blobs->lbs_ipc, &blob_sizes.lbs_ipc);
 	lsm_blob_size_update(&blobs->lbs_key, &blob_sizes.lbs_key);
 	lsm_blob_size_update(&blobs->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
+	lsm_blob_size_update(&blobs->lbs_ns, &blob_sizes.lbs_ns);
 	lsm_blob_size_update(&blobs->lbs_perf_event,
 			     &blob_sizes.lbs_perf_event);
 	lsm_blob_size_update(&blobs->lbs_sock, &blob_sizes.lbs_sock);
@@ -450,6 +451,7 @@ int __init security_init(void)
 		lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc);
 		lsm_pr("blob(key) size %d\n", blob_sizes.lbs_key);
 		lsm_pr("blob(msg_msg)_size %d\n", blob_sizes.lbs_msg_msg);
+		lsm_pr("blob(ns) size %d\n", blob_sizes.lbs_ns);
 		lsm_pr("blob(sock) size %d\n", blob_sizes.lbs_sock);
 		lsm_pr("blob(superblock) size %d\n", blob_sizes.lbs_superblock);
 		lsm_pr("blob(perf_event) size %d\n", blob_sizes.lbs_perf_event);
diff --git a/security/security.c b/security/security.c
index 71aea8fdf01429..e0b43ab60d233e 100644
--- a/security/security.c
+++ b/security/security.c
@@ -26,6 +26,7 @@
 #include <linux/string.h>
 #include <linux/xattr.h>
 #include <linux/msg.h>
+#include <linux/ns_common.h>
 #include <linux/overflow.h>
 #include <linux/perf_event.h>
 #include <linux/fs.h>
@@ -381,6 +382,19 @@ static int lsm_superblock_alloc(struct super_block *sb)
 			      GFP_KERNEL);
 }
 
+/**
+ * lsm_ns_alloc - allocate a composite namespace blob
+ * @ns: the namespace that needs a blob
+ *
+ * Allocate the namespace blob for all the modules
+ *
+ * Returns 0, or -ENOMEM if memory can't be allocated.
+ */
+static int lsm_ns_alloc(struct ns_common *ns)
+{
+	return lsm_blob_alloc(&ns->ns_security, blob_sizes.lbs_ns, GFP_KERNEL);
+}
+
 /**
  * lsm_fill_user_ctx - Fill a user space lsm_ctx structure
  * @uctx: a userspace LSM context to be filled
@@ -3358,6 +3372,64 @@ int security_create_user_ns(const struct cred *cred)
 	return call_int_hook(userns_create, cred);
 }
 
+/**
+ * security_namespace_init() - Initialize LSM security data for a namespace
+ * @ns: the namespace being initialized
+ *
+ * Initialize the LSM security blob attached to the namespace. The namespace type
+ * is available via ns->ns_type, and the owning user namespace (if any)
+ * via ns->ops->owner(ns).
+ *
+ * Return: Returns 0 if successful, otherwise < 0 error code.
+ */
+int security_namespace_init(struct ns_common *ns)
+{
+	int rc;
+
+	rc = lsm_ns_alloc(ns);
+	if (unlikely(rc))
+		return rc;
+
+	rc = call_int_hook(namespace_init, ns);
+	if (unlikely(rc))
+		security_namespace_free(ns);
+
+	return rc;
+}
+
+/**
+ * security_namespace_free() - Release LSM security data from a namespace
+ * @ns: the namespace being freed
+ *
+ * Release security data attached to the namespace. Called before the
+ * namespace structure is freed.
+ */
+void security_namespace_free(struct ns_common *ns)
+{
+	if (!ns->ns_security)
+		return;
+
+	call_void_hook(namespace_free, ns);
+
+	kfree(ns->ns_security);
+	ns->ns_security = NULL;
+}
+
+/**
+ * security_namespace_install() - Check permission to install a namespace
+ * @nsset: the target nsset being configured
+ * @ns: the namespace being installed
+ *
+ * Check permission before allowing a namespace to be installed into the
+ * process's set of namespaces via setns(2).
+ *
+ * Return: Returns 0 if permission is granted, otherwise < 0 error code.
+ */
+int security_namespace_install(const struct nsset *nsset, struct ns_common *ns)
+{
+	return call_int_hook(namespace_install, nsset, ns);
+}
+
 /**
  * security_ipc_permission() - Check if sysv ipc access is allowed
  * @ipcp: ipc permission structure
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index cbd3c1669951a4..bcb5bdafd9ed5e 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-	ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(11, landlock_create_ruleset(NULL, 0,
 					      LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
@@ -142,6 +142,24 @@ TEST(errata)
 	ASSERT_EQ(EINVAL, errno);
 }
 
+#define PERM_LAST LANDLOCK_PERM_CAPABILITY_USE
+
+TEST(ruleset_with_unknown_perm)
+{
+	__u64 perm_mask;
+
+	for (perm_mask = 1ULL << 63; perm_mask != PERM_LAST; perm_mask >>= 1) {
+		struct landlock_ruleset_attr ruleset_attr = {
+			.handled_perm = perm_mask,
+		};
+
+		/* Unknown handled_perm values must be rejected. */
+		ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
+						      sizeof(ruleset_attr), 0));
+		ASSERT_EQ(EINVAL, errno);
+	}
+}
+
 /* Tests ordering of syscall argument checks. */
 TEST(create_ruleset_checks_ordering)
 {
diff --git a/tools/testing/selftests/landlock/cap_test.c b/tools/testing/selftests/landlock/cap_test.c
new file mode 100644
index 00000000000000..a86fd72b5639f3
--- /dev/null
+++ b/tools/testing/selftests/landlock/cap_test.c
@@ -0,0 +1,889 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - Capability restriction
+ *
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/capability.h>
+#include <linux/landlock.h>
+#include <sched.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include "audit.h"
+#include "common.h"
+
+static bool list_has_xattr(const char *list, ssize_t len, const char *name)
+{
+	const char *p;
+
+	for (p = list; p < list + len; p += strlen(p) + 1)
+		if (strcmp(p, name) == 0)
+			return true;
+	return false;
+}
+
+static int create_cap_ruleset(void)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_CAPABILITY_USE,
+	};
+
+	return landlock_create_ruleset(&attr, sizeof(attr), 0);
+}
+
+static int add_cap_rule(int ruleset_fd, __u64 cap)
+{
+	const struct landlock_capability_attr attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << cap),
+	};
+
+	return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &attr,
+				 0);
+}
+
+static int add_cap_rule_full(int ruleset_fd, __u64 allowed, __u64 quiet)
+{
+	const struct landlock_capability_attr attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = allowed,
+		.quiet_capabilities = quiet,
+	};
+
+	return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &attr,
+				 0);
+}
+
+TEST(add_rule_bad_attr)
+{
+	const struct landlock_ruleset_attr ns_only_attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,
+	};
+	int ruleset_fd;
+	struct landlock_capability_attr attr = {};
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Empty perm selector returns ENOMSG. */
+	attr.perm = 0;
+	attr.allowed_capabilities = (1ULL << CAP_NET_RAW);
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+					&attr, 0));
+	ASSERT_EQ(ENOMSG, errno);
+
+	/* Useless rule: neither allowed nor quiet capabilities set. */
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_capabilities = 0;
+	attr.quiet_capabilities = 0;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+					&attr, 0));
+	ASSERT_EQ(ENOMSG, errno);
+
+	/* Quiet-only rule (empty allowed set) is legal. */
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_capabilities = 0;
+	attr.quiet_capabilities = (1ULL << CAP_NET_RAW);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &attr, 0));
+
+	/* Allow and quiet different members in the same rule. */
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_capabilities = (1ULL << CAP_SYS_ADMIN);
+	attr.quiet_capabilities = (1ULL << CAP_NET_RAW);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &attr, 0));
+
+	/* perm with unhandled bit. */
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE | LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_capabilities = (1ULL << CAP_NET_RAW);
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+					&attr, 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	/* perm with wrong type. */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_capabilities = (1ULL << CAP_NET_RAW);
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+					&attr, 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	/*
+	 * Unknown capability bits (e.g. bit 63) are silently accepted for
+	 * forward compatibility.  Only known bits are stored.
+	 */
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_capabilities = 1ULL << 63;
+	attr.quiet_capabilities = 0;
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &attr, 0));
+
+	/*
+	 * LANDLOCK_ADD_RULE_QUIET (and any other flag) is filesystem/network
+	 * only and must be rejected for capability rules, even with an
+	 * otherwise-valid attr (cases.md Q-E5).
+	 */
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_capabilities = (1ULL << CAP_NET_RAW);
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+					&attr, LANDLOCK_ADD_RULE_QUIET));
+	ASSERT_EQ(EINVAL, errno);
+
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * Ruleset handles PERM_NAMESPACE_USE but not PERM_CAPABILITY_USE:
+	 * adding a capability rule must be rejected.
+	 */
+	ruleset_fd =
+		landlock_create_ruleset(&ns_only_attr, sizeof(ns_only_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_capabilities = (1ULL << CAP_NET_RAW);
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+					&attr, 0));
+	ASSERT_EQ(EINVAL, errno);
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
+/*
+ * Unknown capability values above CAP_LAST_CAP are silently accepted
+ * (allow-list: they have no effect since the kernel never checks them).
+ */
+TEST(add_rule_unknown)
+{
+	int ruleset_fd;
+	struct landlock_capability_attr attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+	};
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Just above CAP_LAST_CAP should succeed. */
+	attr.allowed_capabilities = (1ULL << (CAP_LAST_CAP + 1));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &attr, 0));
+
+	/* High values (below bit 63) should succeed. */
+	attr.allowed_capabilities = (1ULL << 62);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &attr, 0));
+
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
+/*
+ * A rule that lists only capability bits unknown to the running kernel is
+ * accepted by landlock_add_rule() but has no runtime effect: once the domain is
+ * enforced, any actual CAP_* capability is still denied by the per-category
+ * deny-by-default behaviour.  This documents the forward-compatibility
+ * contract: unknown bits are silently accepted so the same policy can be loaded
+ * across kernels, but they never grant a capability that the running kernel
+ * knows nothing about.
+ */
+TEST(add_rule_unknown_no_runtime_effect)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_perm = LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	struct landlock_capability_attr attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		/* Only unknown bits above CAP_LAST_CAP. */
+		.allowed_capabilities = (1ULL << (CAP_LAST_CAP + 1)) |
+					(1ULL << 62),
+	};
+	int ruleset_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &attr, 0));
+
+	/*
+	 * Isolate hostname changes and establish a baseline: with CAP_SYS_ADMIN
+	 * and absent the domain, sethostname(2) succeeds.  This ensures the
+	 * EPERM below is attributable to Landlock rather than to a missing
+	 * capability, and fails if the hook always allows.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWUTS));
+	ASSERT_EQ(0, sethostname("baseline", 8));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * CAP_SYS_ADMIN is a real, known capability but was not authorised by
+	 * the rule above; deny-by-default applies.  sethostname(2) requires
+	 * CAP_SYS_ADMIN.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, sethostname("test", 4));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/* clang-format off */
+FIXTURE(cap_enforce) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(cap_enforce)
+{
+	const bool is_sandboxed;
+	const bool handle_caps;
+	const __u64 allowed_cap;
+	const int expected_sysadmin;
+	const int expected_chroot;
+};
+
+/*
+ * Unsandboxed baseline: no Landlock domain is enforced.  Both capabilities
+ * should work normally.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_enforce, unsandboxed) {
+	.is_sandboxed = false,
+	.handle_caps = false,
+	.allowed_cap = 0,
+	.expected_sysadmin = 0,
+	.expected_chroot = 0,
+};
+/* clang-format on */
+
+/*
+ * Denied: capabilities are handled but no rule allows them.  All capability
+ * checks must be denied by Landlock even if the capability is effective.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_enforce, denied) {
+	.is_sandboxed = true,
+	.handle_caps = true,
+	.allowed_cap = 0,
+	.expected_sysadmin = EPERM,
+	.expected_chroot = EPERM,
+};
+/* clang-format on */
+
+/*
+ * Allowed: CAP_SYS_ADMIN is allowed by rule, CAP_SYS_CHROOT is not.  Only the
+ * explicitly allowed capability should succeed.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_enforce, allowed) {
+	.is_sandboxed = true,
+	.handle_caps = true,
+	.allowed_cap = CAP_SYS_ADMIN,
+	.expected_sysadmin = 0,
+	.expected_chroot = EPERM,
+};
+/* clang-format on */
+
+/*
+ * Unhandled: the ruleset does not handle LANDLOCK_PERM_CAPABILITY_USE at all
+ * (only handles FS access).  Both capabilities should work since the domain
+ * does not restrict them.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_enforce, unhandled) {
+	.is_sandboxed = true,
+	.handle_caps = false,
+	.allowed_cap = 0,
+	.expected_sysadmin = 0,
+	.expected_chroot = 0,
+};
+/* clang-format on */
+
+FIXTURE_SETUP(cap_enforce)
+{
+	disable_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN(cap_enforce)
+{
+}
+
+/*
+ * Capability enforcement: tests the four fundamental enforcement scenarios
+ * (unsandboxed baseline, denied, allowed, unhandled) using two independent
+ * capability checks (sethostname for CAP_SYS_ADMIN, chroot for CAP_SYS_CHROOT).
+ */
+TEST_F(cap_enforce, use)
+{
+	int ruleset_fd;
+
+	/* Isolate hostname changes from other tests. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWUTS));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	if (variant->is_sandboxed) {
+		if (variant->handle_caps) {
+			ruleset_fd = create_cap_ruleset();
+		} else {
+			const struct landlock_ruleset_attr attr = {
+				.handled_access_fs =
+					LANDLOCK_ACCESS_FS_READ_FILE,
+			};
+
+			ruleset_fd =
+				landlock_create_ruleset(&attr, sizeof(attr), 0);
+		}
+		ASSERT_LE(0, ruleset_fd);
+
+		if (variant->allowed_cap)
+			ASSERT_EQ(0, add_cap_rule(ruleset_fd,
+						  variant->allowed_cap));
+
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	/* Test CAP_SYS_ADMIN via sethostname. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	if (variant->expected_sysadmin) {
+		EXPECT_EQ(-1, sethostname("test", 4));
+		EXPECT_EQ(variant->expected_sysadmin, errno);
+	} else {
+		EXPECT_EQ(0, sethostname("test", 4));
+	}
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* Test CAP_SYS_CHROOT via chroot. */
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	if (variant->expected_chroot) {
+		EXPECT_EQ(-1, chroot("/"));
+		EXPECT_EQ(variant->expected_chroot, errno);
+	} else {
+		EXPECT_EQ(0, chroot("/"));
+	}
+}
+
+/*
+ * Layer stacking: both layers must allow CAP_SYS_ADMIN for the capability to be
+ * exercisable.  Variants cover the three per-layer combinations that exercise
+ * distinct walker paths (allow/deny, allow/allow, deny/allow), an unsandboxed
+ * baseline, and a mixed-layer case where one layer does not handle
+ * PERM_CAPABILITY_USE at all.
+ */
+/* clang-format off */
+FIXTURE(cap_stacking) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(cap_stacking)
+{
+	const bool is_sandboxed;
+	const bool first_layer_allows;
+	const bool second_layer_allows;
+	const bool second_layer_is_fs_only;
+	const int expected_sysadmin;
+	const int expected_chroot;
+};
+
+/*
+ * Unsandboxed baseline: no Landlock layers are stacked.  Both capabilities
+ * should work normally.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_stacking, unsandboxed) {
+	.is_sandboxed = false,
+	.first_layer_allows = false,
+	.second_layer_allows = false,
+	.expected_sysadmin = 0,
+	.expected_chroot = 0,
+};
+/* clang-format on */
+
+/* Layer 1 allows CAP_SYS_ADMIN, layer 2 denies -> denied. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_stacking, deny) {
+	.is_sandboxed = true,
+	.first_layer_allows = true,
+	.second_layer_allows = false,
+	.expected_sysadmin = EPERM,
+	.expected_chroot = EPERM,
+};
+/* clang-format on */
+
+/* Both layers allow CAP_SYS_ADMIN -> sysadmin succeeds, chroot still denied. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_stacking, allow) {
+	.is_sandboxed = true,
+	.first_layer_allows = true,
+	.second_layer_allows = true,
+	.expected_sysadmin = 0,
+	.expected_chroot = EPERM,
+};
+/* clang-format on */
+
+/*
+ * Layer 1 denies CAP_SYS_ADMIN, layer 2 allows -> still denied: a child layer
+ * cannot grant what an ancestor layer withheld.  Complements the
+ * parent-allows/child-denies variant; together they verify the walker checks
+ * both layers and accepts only the (allow, allow) cell.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_stacking, parent_denies) {
+	.is_sandboxed = true,
+	.first_layer_allows = false,
+	.second_layer_allows = true,
+	.expected_sysadmin = EPERM,
+	.expected_chroot = EPERM,
+};
+/* clang-format on */
+
+/*
+ * Mixed layers: first layer handles PERM_CAPABILITY_USE (denies all caps),
+ * second layer is FS-only (does not handle it).  The perm walker iterates from
+ * youngest (layer 1) to oldest (layer 0) and must skip the FS-only layer to
+ * find the denying layer beneath.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(cap_stacking, mixed_layers) {
+	/* clang-format on */
+	.is_sandboxed = true,
+	.first_layer_allows = false,
+	.second_layer_is_fs_only = true,
+	.expected_sysadmin = EPERM,
+	.expected_chroot = EPERM,
+};
+
+FIXTURE_SETUP(cap_stacking)
+{
+	disable_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN(cap_stacking)
+{
+}
+
+TEST_F(cap_stacking, two_layers)
+{
+	int ruleset_fd;
+
+	if (variant->is_sandboxed) {
+		/*
+		 * First layer: handles PERM_CAPABILITY_USE; rule added per
+		 * variant.
+		 */
+		ruleset_fd = create_cap_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		if (variant->first_layer_allows)
+			ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN));
+
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+
+		if (variant->second_layer_is_fs_only) {
+			/*
+			 * Second layer: FS-only (does not handle
+			 * PERM_CAPABILITY_USE).  The perm walker must skip this
+			 * layer.
+			 */
+			const struct landlock_ruleset_attr fs_attr = {
+				.handled_access_fs =
+					LANDLOCK_ACCESS_FS_READ_FILE,
+			};
+
+			ruleset_fd = landlock_create_ruleset(
+				&fs_attr, sizeof(fs_attr), 0);
+		} else {
+			/* Second layer: cap allow or deny. */
+			ruleset_fd = create_cap_ruleset();
+			if (variant->second_layer_allows)
+				ASSERT_EQ(0, add_cap_rule(ruleset_fd,
+							  CAP_SYS_ADMIN));
+		}
+		ASSERT_LE(0, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	/* Test CAP_SYS_ADMIN via sethostname. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	if (variant->expected_sysadmin) {
+		EXPECT_EQ(-1, sethostname("test", 4));
+		EXPECT_EQ(variant->expected_sysadmin, errno);
+	} else {
+		EXPECT_EQ(0, sethostname("test", 4));
+	}
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* Test CAP_SYS_CHROOT via chroot. */
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	if (variant->expected_chroot) {
+		EXPECT_EQ(-1, chroot("/"));
+		EXPECT_EQ(variant->expected_chroot, errno);
+	} else {
+		EXPECT_EQ(0, chroot("/"));
+	}
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+}
+
+/*
+ * Verify that LANDLOCK_PERM_CAPABILITY_USE enforces when the domain is applied
+ * without no_new_privs, using CAP_SYS_ADMIN for landlock_restrict_self()
+ * authorization instead.  Privileged processes (e.g. container managers) can
+ * sandbox themselves this way.
+ */
+TEST(cap_without_nnp)
+{
+	int ruleset_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Allow CAP_SYS_CHROOT but not CAP_SYS_ADMIN. */
+	ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_CHROOT));
+
+	/*
+	 * Enforce WITHOUT NNP: landlock_restrict_self() succeeds when the
+	 * caller has CAP_SYS_ADMIN (checked before the new domain takes
+	 * effect).
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * CAP_SYS_ADMIN is still in effective set but Landlock denies it:
+	 * cap_capable() returns 0, then hook_capable() returns -EPERM.
+	 */
+	EXPECT_EQ(-1, sethostname("test", 4));
+	EXPECT_EQ(EPERM, errno);
+
+	/* CAP_SYS_CHROOT is allowed by the rule. */
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(0, chroot("/"));
+}
+
+/*
+ * Verify that capabilities gained through user namespace ownership are still
+ * restricted by LANDLOCK_PERM_CAPABILITY_USE.  When a process creates a user
+ * namespace, the kernel grants CAP_FULL_SET in the new namespace via
+ * cap_capable_helper()'s ownership bypass.  Landlock's hook_capable() must
+ * still deny capabilities not in the allowed set, ensuring that user namespace
+ * creation cannot be used to escape capability restrictions.
+ */
+TEST(cap_userns_ownership_bypass)
+{
+	pid_t child;
+	int status;
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		int ruleset_fd;
+
+		disable_caps(_metadata);
+
+		ruleset_fd = create_cap_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+
+		/* Allow CAP_SYS_ADMIN only. */
+		ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+
+		/*
+		 * Create a user namespace.  This is unprivileged and does not
+		 * require capabilities.  LANDLOCK_PERM_NAMESPACE_USE is not
+		 * handled so namespace creation is unrestricted.
+		 */
+		ASSERT_EQ(0, unshare(CLONE_NEWUSER));
+
+		/*
+		 * After unshare(CLONE_NEWUSER), the kernel set cap_effective =
+		 * CAP_FULL_SET in the new namespace.  Create a UTS namespace
+		 * (requires CAP_SYS_ADMIN in the new user NS).  Landlock allows
+		 * CAP_SYS_ADMIN.
+		 */
+		ASSERT_EQ(0, unshare(CLONE_NEWUTS))
+		{
+			TH_LOG("unshare(CLONE_NEWUTS): %s", strerror(errno));
+		}
+
+		/*
+		 * sethostname checks against uts_ns->user_ns, which is now the
+		 * new user NS.  CAP_SYS_ADMIN is allowed.
+		 */
+		EXPECT_EQ(0, sethostname("test", 4));
+
+		/*
+		 * chroot checks against current_user_ns(), which is the new
+		 * user NS.  The process has CAP_SYS_CHROOT in cap_effective
+		 * (from user NS creation), so cap_capable() returns 0.  But
+		 * Landlock denies because no rule allows CAP_SYS_CHROOT.
+		 */
+		EXPECT_EQ(-1, chroot("/"));
+		EXPECT_EQ(EPERM, errno);
+
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+/* Audit tests */
+
+static int matches_log_cap(int audit_fd, int cap_number)
+{
+	static const char log_template[] = REGEX_LANDLOCK_PREFIX
+		" blockers=perm\\.capability_use capability=%d $";
+	char log_match[sizeof(log_template) + 10];
+	int log_match_len;
+
+	log_match_len = snprintf(log_match, sizeof(log_match), log_template,
+				 cap_number);
+	if (log_match_len >= sizeof(log_match))
+		return -E2BIG;
+
+	return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
+				  NULL);
+}
+
+FIXTURE(cap_audit)
+{
+	struct audit_filter audit_filter;
+	int audit_fd;
+};
+
+FIXTURE_SETUP(cap_audit)
+{
+	ASSERT_TRUE(is_in_init_user_ns());
+
+	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);
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(cap_audit)
+{
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter));
+}
+
+/*
+ * Verifies that a denied capability produces the expected audit record with the
+ * correct capability number and blocker string.
+ */
+TEST_F(cap_audit, denied)
+{
+	struct audit_records records;
+	int ruleset_fd;
+	__u64 domain_id;
+
+	/* Baseline: chroot works before Landlock. */
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	ASSERT_EQ(0, chroot("/"));
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */
+	ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* Deny CAP_SYS_CHROOT (no allow rule). */
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(-1, chroot("/"));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+
+	EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_CHROOT));
+
+	/*
+	 * The domain allocation record is emitted in the same event as the
+	 * first denial; anchor its status=allocated and enforcing pid.  Both
+	 * access and domain records are now consumed, so none remain.
+	 */
+	EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(),
+						  &domain_id));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+TEST_F(cap_audit, allowed)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN));
+	/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */
+	ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, sethostname("test", 4));
+
+	/* No records: allowed operations never trigger audit logging. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+/*
+ * A quieted capability denial is still denied (EPERM); only its audit record is
+ * suppressed.  Quiet-only rule (empty allowed set).
+ */
+TEST_F(cap_audit, quieted)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */
+	ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));
+	ASSERT_EQ(0,
+		  add_cap_rule_full(ruleset_fd, 0, (1ULL << CAP_SYS_CHROOT)));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(-1, chroot("/"));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+
+	/*
+	 * Positive control: CAP_SYS_ADMIN is denied but not quieted, so its
+	 * denial IS logged.  This proves quieting is per-member, not global.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, sethostname("test", 4));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/*
+	 * The quieted CAP_SYS_CHROOT denial is suppressed; the CAP_SYS_ADMIN
+	 * denial is logged.
+	 */
+	EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_ADMIN));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+/*
+ * Quieting an unknown capability bit has no effect: a rule that quiets only a
+ * bit above CAP_LAST_CAP still logs the denial of a known capability.
+ */
+TEST_F(cap_audit, quiet_unknown_bit_no_effect)
+{
+	struct audit_records records;
+	int ruleset_fd;
+	__u64 domain_id;
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));
+	/* Quiet only an unknown bit (bit 63 is above CAP_LAST_CAP). */
+	ASSERT_EQ(0, add_cap_rule_full(ruleset_fd, 0, (1ULL << 63)));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(-1, chroot("/"));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+
+	/* The unknown quiet bit does not suppress the known denial. */
+	EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_CHROOT));
+	EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(),
+						  &domain_id));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+/*
+ * A capability check made with CAP_OPT_NOAUDIT is denied by Landlock but not
+ * logged: hook_capable() emits no Landlock audit record when the caller passes
+ * CAP_OPT_NOAUDIT, so the denial is silent here even though CAP_SYS_ADMIN is
+ * neither allowed nor quieted.  The probe is the
+ * ns_capable_noaudit(CAP_SYS_ADMIN) call in tmpfs simple_xattr_list(), which
+ * decides whether trusted.* xattrs are visible to listxattr(2).
+ */
+TEST_F(cap_audit, noaudit_probe_not_logged)
+{
+	struct audit_records records;
+	int ruleset_fd, fd;
+	char list[256];
+	ssize_t len;
+
+	/*
+	 * Private tmpfs holding a trusted.* xattr so its listxattr(2)
+	 * visibility depends only on the CAP_SYS_ADMIN noaudit probe.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNS));
+	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+	ASSERT_EQ(0, mount("test", "/tmp", "tmpfs", 0, NULL));
+	fd = open("/tmp/f", O_CREAT | O_RDWR | O_CLOEXEC, 0600);
+	ASSERT_LE(0, fd);
+	ASSERT_EQ(0, fsetxattr(fd, "trusted.landlock_test", "x", 1, 0));
+
+	/* Baseline: with CAP_SYS_ADMIN, the trusted xattr is listed. */
+	len = flistxattr(fd, list, sizeof(list));
+	ASSERT_LE(0, len);
+	ASSERT_TRUE(list_has_xattr(list, len, "trusted.landlock_test"));
+
+	ruleset_fd = create_cap_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	/* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */
+	ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));
+	/* CAP_SYS_ADMIN is neither allowed nor quieted. */
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * CAP_SYS_ADMIN is still effective, but Landlock denies the noaudit
+	 * probe, so the trusted.* xattr is filtered out of the listing.
+	 */
+	len = flistxattr(fd, list, sizeof(list));
+	ASSERT_LE(0, len);
+	EXPECT_FALSE(list_has_xattr(list, len, "trusted.landlock_test"));
+	EXPECT_EQ(0, close(fd));
+
+	/*
+	 * Positive control: a normal (audited) CAP_SYS_ADMIN denial in the same
+	 * domain IS logged.  This proves the empty record set above is caused
+	 * by CAP_OPT_NOAUDIT, not by an always-allow or always-silent bug.
+	 */
+	EXPECT_EQ(-1, sethostname("test", 4));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_ADMIN));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 7206d5105d664b..88c23c1089f811 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -130,6 +130,29 @@ static void __maybe_unused clear_ambient_cap(
 	EXPECT_EQ(0, cap_get_ambient(cap));
 }
 
+/*
+ * Returns true if the current process is in the initial user namespace.
+ * Compares the readlink targets of /proc/self/ns/user and /proc/1/ns/user.
+ */
+static bool __maybe_unused is_in_init_user_ns(void)
+{
+	char self_buf[64], init_buf[64];
+	ssize_t self_len, init_len;
+
+	self_len = readlink("/proc/self/ns/user", self_buf, sizeof(self_buf));
+	if (self_len <= 0 || self_len >= (ssize_t)sizeof(self_buf))
+		return false;
+
+	init_len = readlink("/proc/1/ns/user", init_buf, sizeof(init_buf));
+	if (init_len <= 0 || init_len >= (ssize_t)sizeof(init_buf))
+		return false;
+
+	if (self_len != init_len)
+		return false;
+
+	return memcmp(self_buf, init_buf, self_len) == 0;
+}
+
 /* Receives an FD from a UNIX socket. Returns the received FD, or -errno. */
 static int __maybe_unused recv_fd(int usock)
 {
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 8fe9b461b1fde8..d09b637bf6ca1e 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -3,6 +3,7 @@ CONFIG_AUDIT=y
 CONFIG_CGROUPS=y
 CONFIG_CGROUP_SCHED=y
 CONFIG_INET=y
+CONFIG_IPC_NS=y
 CONFIG_IPV6=y
 CONFIG_KEYS=y
 CONFIG_MPTCP=y
@@ -10,10 +11,14 @@ CONFIG_MPTCP_IPV6=y
 CONFIG_NET=y
 CONFIG_NET_NS=y
 CONFIG_OVERLAY_FS=y
+CONFIG_PID_NS=y
 CONFIG_PROC_FS=y
 CONFIG_SECURITY=y
 CONFIG_SECURITY_LANDLOCK=y
 CONFIG_SHMEM=y
 CONFIG_SYSFS=y
+CONFIG_TIME_NS=y
 CONFIG_TMPFS=y
 CONFIG_TMPFS_XATTR=y
+CONFIG_USER_NS=y
+CONFIG_UTS_NS=y
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 86e08aa6e0a7eb..5f7023658a7937 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -54,13 +54,6 @@ int renameat2(int olddirfd, const char *oldpath, int newdirfd,
 }
 #endif
 
-#ifndef open_tree
-int open_tree(int dfd, const char *filename, unsigned int flags)
-{
-	return syscall(__NR_open_tree, dfd, filename, flags);
-}
-#endif
-
 static int sys_execveat(int dirfd, const char *pathname, char *const argv[],
 			char *const envp[], int flags)
 {
@@ -2454,9 +2447,9 @@ TEST_F_FORK(layout1, refer_mount_root_deny)
 
 	/* Creates a mount object from a non-mount point. */
 	set_cap(_metadata, CAP_SYS_ADMIN);
-	root_fd =
-		open_tree(AT_FDCWD, dir_s1d1,
-			  AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	root_fd = sys_open_tree(AT_FDCWD, dir_s1d1,
+				AT_EMPTY_PATH | OPEN_TREE_CLONE |
+					OPEN_TREE_CLOEXEC);
 	clear_cap(_metadata, CAP_SYS_ADMIN);
 	ASSERT_LE(0, root_fd);
 
diff --git a/tools/testing/selftests/landlock/ns_test.c b/tools/testing/selftests/landlock/ns_test.c
new file mode 100644
index 00000000000000..4377b929de937b
--- /dev/null
+++ b/tools/testing/selftests/landlock/ns_test.c
@@ -0,0 +1,2380 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - Namespace restriction
+ *
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/capability.h>
+#include <linux/landlock.h>
+#include <linux/mount.h>
+#include <linux/nsfs.h>
+#include <sched.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <syscall.h>
+#include <unistd.h>
+
+#include "audit.h"
+#include "common.h"
+
+/*
+ * Max length for /proc/self/ns/<name> paths (longest: "/proc/self/ns/cgroup").
+ */
+#define NS_PROC_PATH_MAX 32
+
+static int create_ns_ruleset(void)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,
+	};
+
+	return landlock_create_ruleset(&attr, sizeof(attr), 0);
+}
+
+static int add_ns_rule(int ruleset_fd, __u64 ns_type)
+{
+	const struct landlock_namespace_attr attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = ns_type,
+	};
+
+	return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0);
+}
+
+static int add_ns_rule_full(int ruleset_fd, __u64 allowed, __u64 quiet)
+{
+	const struct landlock_namespace_attr attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = allowed,
+		.quiet_namespace_types = quiet,
+	};
+
+	return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0);
+}
+
+/*
+ * Returns the /proc/self/NS entry name for a given CLONE_NEW* type, or NULL if
+ * unknown.  Used to check kernel support without side effects.
+ */
+static const char *ns_proc_name(__u64 ns_type)
+{
+	switch (ns_type) {
+	case CLONE_NEWNS:
+		return "mnt";
+	case CLONE_NEWCGROUP:
+		return "cgroup";
+	case CLONE_NEWUTS:
+		return "uts";
+	case CLONE_NEWIPC:
+		return "ipc";
+	case CLONE_NEWUSER:
+		return "user";
+	case CLONE_NEWPID:
+		return "pid";
+	case CLONE_NEWNET:
+		return "net";
+	case CLONE_NEWTIME:
+		return "time";
+	default:
+		return NULL;
+	}
+}
+
+static bool ns_is_supported(__u64 ns_type, char *proc_path, size_t size)
+{
+	const char *ns_name;
+
+	ns_name = ns_proc_name(ns_type);
+	if (!ns_name)
+		return false;
+
+	snprintf(proc_path, size, "/proc/self/ns/%s", ns_name);
+	return access(proc_path, F_OK) == 0;
+}
+
+/* Rule validation tests */
+
+TEST(add_rule_bad_attr)
+{
+	const struct landlock_ruleset_attr cap_only_attr = {
+		.handled_perm = LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	int ruleset_fd;
+	struct landlock_namespace_attr attr = {};
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Empty perm selector returns ENOMSG. */
+	attr.perm = 0;
+	attr.allowed_namespace_types = CLONE_NEWUTS;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+					&attr, 0));
+	ASSERT_EQ(ENOMSG, errno);
+
+	/* perm with unhandled bit. */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE | LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_namespace_types = CLONE_NEWUTS;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+					&attr, 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	/* perm with wrong type. */
+	attr.perm = LANDLOCK_PERM_CAPABILITY_USE;
+	attr.allowed_namespace_types = CLONE_NEWUTS;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+					&attr, 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	/*
+	 * Unknown namespace bits (e.g. bit 63) are silently accepted for
+	 * forward compatibility.  Only known CLONE_NEW* bits are stored.
+	 */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = 1ULL << 63;
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	/* Useless rule: neither allowed nor quiet types set returns ENOMSG. */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = 0;
+	attr.quiet_namespace_types = 0;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+					&attr, 0));
+	ASSERT_EQ(ENOMSG, errno);
+
+	/* Quiet-only rule (empty allowed set) is legal. */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = 0;
+	attr.quiet_namespace_types = CLONE_NEWUTS;
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	/* Allow and quiet in the same rule. */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = CLONE_NEWNET;
+	attr.quiet_namespace_types = CLONE_NEWUTS;
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+	attr.quiet_namespace_types = 0;
+
+	/*
+	 * Bit 1 is not a CLONE_NEW* value but is silently accepted for forward
+	 * compatibility (no hole rejection).
+	 */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = (1ULL << 1);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	/* Multi-bit values are valid (bitmask allows multiple types). */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = CLONE_NEWUTS | CLONE_NEWNET;
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	/*
+	 * LANDLOCK_ADD_RULE_QUIET (and any other flag) is filesystem/network
+	 * only and must be rejected for namespace rules, even with an
+	 * otherwise-valid attr (cases.md Q-E5).
+	 */
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = CLONE_NEWUTS;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+					&attr, LANDLOCK_ADD_RULE_QUIET));
+	ASSERT_EQ(EINVAL, errno);
+
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * Ruleset handles PERM_CAPABILITY_USE but not PERM_NAMESPACE_USE:
+	 * adding a namespace rule must be rejected.
+	 */
+	ruleset_fd = landlock_create_ruleset(&cap_only_attr,
+					     sizeof(cap_only_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	attr.perm = LANDLOCK_PERM_NAMESPACE_USE;
+	attr.allowed_namespace_types = CLONE_NEWUTS;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+					&attr, 0));
+	ASSERT_EQ(EINVAL, errno);
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
+/*
+ * Unknown namespace types in the upper range are silently accepted (allow-list:
+ * they have no effect since the kernel never checks them).
+ */
+TEST(add_rule_unknown)
+{
+	int ruleset_fd;
+	struct landlock_namespace_attr attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+	};
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+
+	/*
+	 * Bit 31 is in the lower 32 bits but not a CLONE_NEW* value.  Silently
+	 * accepted for forward compatibility (no hole rejection).
+	 */
+	attr.allowed_namespace_types = 1ULL << 31;
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	/* Bit 32 is in the unknown upper range: silently accepted. */
+	attr.allowed_namespace_types = 1ULL << 32;
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
+/*
+ * A rule that lists only namespace bits unknown to the running kernel is
+ * accepted by landlock_add_rule() but has no runtime effect: once the domain is
+ * enforced, any actual CLONE_NEW* operation is still denied by the per-category
+ * deny-by-default behaviour.  This documents the forward-compatibility
+ * contract: unknown bits are silently accepted so the same policy can be loaded
+ * across kernels, but they never grant a permission that the running kernel
+ * knows nothing about.
+ */
+TEST(add_rule_unknown_no_runtime_effect)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,
+	};
+	struct landlock_namespace_attr attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		/* Only unknown bits: bit 31 (in lower 32) and bit 32. */
+		.allowed_namespace_types = (1ULL << 31) | (1ULL << 32),
+	};
+	int ruleset_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	/*
+	 * Baseline: absent the domain, creating CLONE_NEWUTS succeeds.  This
+	 * ensures the EPERM below is attributable to Landlock rather than to a
+	 * missing capability, and fails if the hook always allows.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWUTS));
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * CLONE_NEWUTS is a real, known CLONE_NEW* type but was not authorised
+	 * by the rule above; deny-by-default applies.
+	 */
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * The install-hook mirror of add_rule_unknown_no_runtime_effect: a rule listing
+ * only unknown namespace bits has no runtime effect on setns(2) either.  Once
+ * enforced, entering a known namespace type is still denied by the per-category
+ * deny-by-default behaviour.
+ */
+TEST(add_rule_unknown_no_runtime_effect_setns)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE,
+	};
+	struct landlock_namespace_attr attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		/* Only unknown bits: bit 31 (in lower 32) and bit 32. */
+		.allowed_namespace_types = (1ULL << 31) | (1ULL << 32),
+	};
+	int ruleset_fd, ns_fd;
+
+	disable_caps(_metadata);
+
+	/* Open the NS FD before enforcing the domain. */
+	ns_fd = open("/proc/self/ns/uts", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &attr, 0));
+
+	/*
+	 * Baseline: absent the domain, entering the process's own UTS namespace
+	 * succeeds, so the later EPERM is attributable to Landlock rather than
+	 * to a missing capability, and fails if the hook always allows.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, setns(ns_fd, CLONE_NEWUTS));
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * CLONE_NEWUTS is a real, known CLONE_NEW* type but was not authorised
+	 * by the rule above; deny-by-default applies at the install hook.
+	 */
+	EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, close(ns_fd));
+}
+
+/* Namespace creation tests (variant-based positive/negative) */
+
+/* clang-format off */
+FIXTURE(ns_create) {
+	char proc_path[NS_PROC_PATH_MAX];
+};
+/* clang-format on */
+
+FIXTURE_VARIANT(ns_create)
+{
+	const __u64 namespace_types;
+	const bool is_sandboxed;
+	const bool has_rule;
+	const bool drop_all_caps;
+	const int expected_result;
+};
+
+/*
+ * Unsandboxed baseline: no Landlock domain is enforced. User namespace creation
+ * should succeed without any restriction.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, user_unsandboxed) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWUSER,
+	.is_sandboxed = false,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+
+/*
+ * User namespace creation denied: handled by Landlock but no rule allows
+ * CLONE_NEWUSER.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, user_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWUSER,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/* User namespace creation allowed: Landlock rule permits CLONE_NEWUSER. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, user_allowed) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWUSER,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+
+/*
+ * User namespace creation while unprivileged: the process has no capabilities
+ * but unshare(CLONE_NEWUSER) is an unprivileged operation so it still succeeds.
+ * The Landlock rule allows it.  For setns, the capability check (CAP_SYS_ADMIN)
+ * fails first since the process has no capabilities, yielding EPERM.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, user_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWUSER,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = 0,
+};
+
+/*
+ * Unsandboxed baseline for non-user namespace: no Landlock domain, process has
+ * CAP_SYS_ADMIN.  UTS creation should succeed.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, uts_unsandboxed) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWUTS,
+	.is_sandboxed = false,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+
+/*
+ * Non-user namespace denied: process has CAP_SYS_ADMIN (passes ns_capable), but
+ * Landlock denies (no rule).
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, uts_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWUTS,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/*
+ * Non-user namespace allowed: process has CAP_SYS_ADMIN and Landlock rule
+ * permits CLONE_NEWUTS.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, uts_allowed) {
+	.namespace_types = CLONE_NEWUTS,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+/* clang-format on */
+
+/*
+ * Unprivileged namespace creation: process lacks CAP_SYS_ADMIN, so the kernel
+ * denies creation regardless of Landlock rules.  Landlock cannot authorize what
+ * the kernel denied (LSM hooks are restriction-only).  The rule is present to
+ * verify Landlock does not change the error code.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, uts_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWUTS,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, ipc_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWIPC,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, ipc_allowed) {
+	.namespace_types = CLONE_NEWIPC,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+/* clang-format on */
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, ipc_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWIPC,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, mnt_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWNS,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, mnt_allowed) {
+	.namespace_types = CLONE_NEWNS,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+/* clang-format on */
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, mnt_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWNS,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, cgroup_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWCGROUP,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, cgroup_allowed) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWCGROUP,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, cgroup_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWCGROUP,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, pid_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWPID,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, pid_allowed) {
+	.namespace_types = CLONE_NEWPID,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+/* clang-format on */
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, pid_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWPID,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, net_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWNET,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, net_allowed) {
+	.namespace_types = CLONE_NEWNET,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+/* clang-format on */
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, net_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWNET,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, time_denied) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWTIME,
+	.is_sandboxed = true,
+	.has_rule = false,
+	.drop_all_caps = false,
+	.expected_result = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, time_allowed) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWTIME,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = false,
+	.expected_result = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create, time_unprivileged) {
+	/* clang-format on */
+	.namespace_types = CLONE_NEWTIME,
+	.is_sandboxed = true,
+	.has_rule = true,
+	.drop_all_caps = true,
+	.expected_result = EPERM,
+};
+
+FIXTURE_SETUP(ns_create)
+{
+	ASSERT_TRUE(ns_is_supported(variant->namespace_types, self->proc_path,
+				    sizeof(self->proc_path)))
+	{
+		TH_LOG("Namespace type 0x%llx not supported",
+		       (unsigned long long)variant->namespace_types);
+	}
+
+	if (variant->drop_all_caps)
+		drop_caps(_metadata);
+	else
+		disable_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN(ns_create)
+{
+}
+
+TEST_F(ns_create, unshare)
+{
+	int ruleset_fd, err;
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd,
+						 variant->namespace_types));
+
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	/*
+	 * Non-user namespaces need CAP_SYS_ADMIN for the privileged path.  User
+	 * namespaces and unprivileged tests skip this.
+	 */
+	if (!variant->drop_all_caps &&
+	    variant->namespace_types != CLONE_NEWUSER)
+		set_cap(_metadata, CAP_SYS_ADMIN);
+
+	err = unshare(variant->namespace_types);
+	if (variant->expected_result) {
+		EXPECT_EQ(-1, err);
+		EXPECT_EQ(variant->expected_result, errno);
+	} else {
+		EXPECT_EQ(0, err);
+	}
+
+	if (!variant->drop_all_caps &&
+	    variant->namespace_types != CLONE_NEWUSER)
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * clone3 exercises a different kernel entry point than unshare: it goes through
+ * kernel_clone() -> copy_process() -> copy_namespaces() ->
+ * create_new_namespaces().  Both paths converge at __ns_common_init() ->
+ * security_namespace_init(), but the entry point and argument handling differ.
+ */
+TEST_F(ns_create, clone3)
+{
+	int ruleset_fd, status;
+	pid_t pid;
+	struct clone_args args = {};
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd,
+						 variant->namespace_types));
+
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	if (!variant->drop_all_caps &&
+	    variant->namespace_types != CLONE_NEWUSER)
+		set_cap(_metadata, CAP_SYS_ADMIN);
+
+	args.flags = variant->namespace_types;
+	args.exit_signal = SIGCHLD;
+	pid = sys_clone3(&args, sizeof(args));
+	if (pid == 0)
+		_exit(EXIT_SUCCESS);
+
+	if (variant->expected_result) {
+		EXPECT_EQ(-1, pid);
+		EXPECT_EQ(variant->expected_result, errno);
+	} else {
+		EXPECT_LE(0, pid);
+		ASSERT_EQ(pid, waitpid(pid, &status, 0));
+		ASSERT_EQ(1, WIFEXITED(status));
+		ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+	}
+
+	if (!variant->drop_all_caps &&
+	    variant->namespace_types != CLONE_NEWUSER)
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * setns exercises the namespace install path: validate_ns() ->
+ * security_namespace_install() -> hook_namespace_install().  This is a
+ * different LSM hook than creation, so it must be tested separately for each
+ * type.
+ *
+ * Mount namespace setns requires both CAP_SYS_ADMIN and CAP_SYS_CHROOT (checked
+ * by mntns_install), so the allowed variant sets both.
+ */
+TEST_F(ns_create, setns)
+{
+	int ruleset_fd, ns_fd, err, expected;
+
+	/*
+	 * setns into the process's own user NS returns EINVAL from
+	 * userns_install() (rejects re-entry), but when Landlock denies the
+	 * operation, security_namespace_install() returns EPERM before
+	 * userns_install() runs.
+	 */
+	if (variant->namespace_types == CLONE_NEWUSER &&
+	    !variant->expected_result) {
+		expected = EINVAL;
+	} else {
+		expected = variant->expected_result;
+	}
+
+	/* Open the NS FD before enforcing the domain. */
+	ns_fd = open(self->proc_path, O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd,
+						 variant->namespace_types));
+
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	if (!variant->drop_all_caps) {
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		/*
+		 * mntns_install() requires CAP_SYS_CHROOT in addition to
+		 * CAP_SYS_ADMIN.
+		 */
+		if (variant->namespace_types == CLONE_NEWNS)
+			set_cap(_metadata, CAP_SYS_CHROOT);
+	}
+
+	err = setns(ns_fd, variant->namespace_types);
+	if (expected) {
+		EXPECT_EQ(-1, err);
+		EXPECT_EQ(expected, errno);
+	} else {
+		EXPECT_EQ(0, err);
+	}
+
+	if (!variant->drop_all_caps) {
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		if (variant->namespace_types == CLONE_NEWNS)
+			clear_cap(_metadata, CAP_SYS_CHROOT);
+	}
+
+	EXPECT_EQ(0, close(ns_fd));
+}
+
+/* Additional namespace creation tests */
+
+/*
+ * When LANDLOCK_PERM_NAMESPACE_USE is not handled by any domain, namespace
+ * creation must produce the same result as without Landlock.  Unlike the
+ * unsandboxed variants of ns_create (which have no domain at all), this test
+ * verifies that a domain handling only FS access does not interfere with
+ * namespace operations.
+ */
+TEST(ns_create_unhandled)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
+	};
+	int ruleset_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* User namespace creation should still work (unhandled). */
+	EXPECT_EQ(0, unshare(CLONE_NEWUSER));
+}
+
+/*
+ * Layer stacking: both layers must allow CLONE_NEWUSER for the operation to
+ * succeed.  Variants exercise the three combinations of per-layer allow/deny
+ * that exercise distinct semantics; the (deny, deny) combination is omitted
+ * because it is covered by every other "deny" test in this file.
+ */
+/* clang-format off */
+FIXTURE(ns_stacking) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(ns_stacking)
+{
+	bool first_layer_allows;
+	bool second_layer_allows;
+};
+
+/* Layer 1 allows, layer 2 denies -> child denies. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_stacking, deny) {
+	/* clang-format on */
+	.first_layer_allows = true,
+	.second_layer_allows = false,
+};
+
+/* Both layers allow CLONE_NEWUSER -> operation succeeds. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_stacking, allow) {
+	/* clang-format on */
+	.first_layer_allows = true,
+	.second_layer_allows = true,
+};
+
+/*
+ * Layer 1 denies, layer 2 allows -> still denied: a child layer cannot grant
+ * what an ancestor layer withheld.  This complements the
+ * parent-allows/child-denies variant above; together they verify the walker
+ * checks both layers and accepts only the (allow, allow) cell.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_stacking, parent_denies) {
+	/* clang-format on */
+	.first_layer_allows = false,
+	.second_layer_allows = true,
+};
+
+FIXTURE_SETUP(ns_stacking)
+{
+	disable_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN(ns_stacking)
+{
+}
+
+/*
+ * Verify that any layer can deny an operation: enforcement requires all layers
+ * to allow.  Variants exercise the three combinations that exercise distinct
+ * walker paths (allow/deny, allow/allow, deny/allow); only allow/allow lets the
+ * operation through.
+ */
+TEST_F(ns_stacking, two_layers)
+{
+	int ruleset_fd;
+	const bool expect_success = variant->first_layer_allows &&
+				    variant->second_layer_allows;
+
+	/* First layer: allow or deny depending on variant. */
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	if (variant->first_layer_allows)
+		ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUSER));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* Second layer: allow or deny depending on variant. */
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	if (variant->second_layer_allows)
+		ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUSER));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	if (expect_success) {
+		EXPECT_EQ(0, unshare(CLONE_NEWUSER));
+	} else {
+		EXPECT_EQ(-1, unshare(CLONE_NEWUSER));
+		EXPECT_EQ(EPERM, errno);
+	}
+}
+
+/*
+ * Combined capability and namespace permissions in a single domain.  Verifies
+ * that both permission types can coexist and are enforced independently.
+ */
+TEST(combined_cap_ns)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_CAPABILITY_USE |
+				LANDLOCK_PERM_NAMESPACE_USE,
+	};
+	const struct landlock_capability_attr cap_attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_ADMIN),
+	};
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWUSER,
+	};
+	int ruleset_fd;
+
+	/* Isolate hostname changes from other tests. */
+	ASSERT_EQ(0, unshare(CLONE_NEWUTS));
+
+	disable_caps(_metadata);
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &cap_attr, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* CAP_SYS_ADMIN use allowed by capability rule. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, sethostname("test", 4));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* CAP_SYS_CHROOT denied (not in allowed capability rules). */
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(-1, chroot("/"));
+	EXPECT_EQ(EPERM, errno);
+
+	/*
+	 * UTS namespace creation denied by Landlock (not in allowed namespace
+	 * rules).  CAP_SYS_ADMIN is needed for the kernel's ns_capable() check
+	 * to pass, so that Landlock's hook is actually reached.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* User namespace creation allowed by namespace rule. */
+	EXPECT_EQ(0, unshare(CLONE_NEWUSER));
+}
+
+/*
+ * Partial allow: one namespace type is allowed, another is denied.  Verifies
+ * that rules are per-type.
+ */
+TEST(ns_create_partial)
+{
+	int ruleset_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Only allow UTS namespace creation. */
+	ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* UTS namespace should be allowed. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, unshare(CLONE_NEWUTS));
+
+	/* User namespace should be denied (no rule). */
+	EXPECT_EQ(-1, unshare(CLONE_NEWUSER));
+	EXPECT_EQ(EPERM, errno);
+}
+
+/*
+ * open_tree(2) and fsmount(2) acquire a file descriptor referring to a
+ * newly-created mount namespace.  Both call paths funnel into
+ * security_namespace_init() with CLONE_NEWNS, gated by
+ * LANDLOCK_PERM_NAMESPACE_USE.  Without coverage here, regressions in those
+ * paths would slip past the suite.
+ */
+/* clang-format off */
+FIXTURE(ns_mount_fd) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(ns_mount_fd)
+{
+	bool sandboxed;
+	bool has_rule;
+	int expected_errno;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_mount_fd, denied) {
+	/* clang-format on */
+	.sandboxed = true,
+	.has_rule = false,
+	.expected_errno = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_mount_fd, allowed) {
+	/* clang-format on */
+	.sandboxed = true,
+	.has_rule = true,
+	.expected_errno = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_mount_fd, unsandboxed) {
+	/* clang-format on */
+	.sandboxed = false,
+	.has_rule = false,
+	.expected_errno = 0,
+};
+
+FIXTURE_SETUP(ns_mount_fd)
+{
+}
+
+FIXTURE_TEARDOWN(ns_mount_fd)
+{
+}
+
+/*
+ * open_tree(OPEN_TREE_CLONE) creates an anonymous mount namespace to hold the
+ * cloned mount tree.  hook_namespace_init() fires with CLONE_NEWNS.
+ */
+TEST_F(ns_mount_fd, open_tree_clone)
+{
+	int ruleset_fd, fd;
+
+	disable_caps(_metadata);
+
+	if (variant->sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	fd = sys_open_tree(AT_FDCWD, "/",
+			   OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE);
+	if (variant->expected_errno) {
+		EXPECT_EQ(-1, fd);
+		EXPECT_EQ(variant->expected_errno, errno);
+	} else {
+		ASSERT_LE(0, fd);
+		EXPECT_EQ(0, close(fd));
+	}
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * open_tree(OPEN_TREE_NAMESPACE) clones the mount tree into a new
+ * (non-anonymous) mount namespace.  Same hook (CLONE_NEWNS) but a different
+ * code path inside fs/namespace.c (open_new_namespace -> alloc_mnt_ns).
+ * OPEN_TREE_NAMESPACE and OPEN_TREE_CLONE are mutually exclusive.
+ */
+TEST_F(ns_mount_fd, open_tree_namespace)
+{
+	int ruleset_fd, fd;
+
+	disable_caps(_metadata);
+
+	if (variant->sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	fd = sys_open_tree(AT_FDCWD, "/",
+			   OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC |
+				   AT_RECURSIVE);
+	if (variant->expected_errno) {
+		EXPECT_EQ(-1, fd);
+		EXPECT_EQ(variant->expected_errno, errno);
+	} else {
+		ASSERT_LE(0, fd);
+		EXPECT_EQ(0, close(fd));
+	}
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * fsmount(2) without FSMOUNT_NAMESPACE creates an anonymous mount namespace to
+ * attach the new superblock.  hook_namespace_init() fires with CLONE_NEWNS.
+ * The fs context (fsopen + fsconfig) is set up before sandboxing because
+ * Landlock here only handles the namespace permission.
+ */
+TEST_F(ns_mount_fd, fsmount_default)
+{
+	int ruleset_fd, fs_fd, mnt_fd;
+
+	disable_caps(_metadata);
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	fs_fd = sys_fsopen("tmpfs", 0);
+	ASSERT_LE(0, fs_fd);
+	ASSERT_EQ(0, sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
+
+	if (variant->sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	mnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC, 0);
+	if (variant->expected_errno) {
+		EXPECT_EQ(-1, mnt_fd);
+		EXPECT_EQ(variant->expected_errno, errno);
+	} else {
+		ASSERT_LE(0, mnt_fd);
+		EXPECT_EQ(0, close(mnt_fd));
+	}
+	EXPECT_EQ(0, close(fs_fd));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * fsmount(2) with FSMOUNT_NAMESPACE creates a (non-anonymous) mount namespace
+ * for the new mount.  Same hook as the default path, different code path.
+ */
+TEST_F(ns_mount_fd, fsmount_namespace)
+{
+	int ruleset_fd, fs_fd, mnt_fd;
+
+	disable_caps(_metadata);
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	fs_fd = sys_fsopen("tmpfs", 0);
+	ASSERT_LE(0, fs_fd);
+	ASSERT_EQ(0, sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
+
+	if (variant->sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	mnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC | FSMOUNT_NAMESPACE, 0);
+	if (variant->expected_errno) {
+		EXPECT_EQ(-1, mnt_fd);
+		EXPECT_EQ(variant->expected_errno, errno);
+	} else {
+		ASSERT_LE(0, mnt_fd);
+		EXPECT_EQ(0, close(mnt_fd));
+	}
+	EXPECT_EQ(0, close(fs_fd));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * unshare(2) with multiple CLONE_NEW* flags: when LANDLOCK_PERM_NAMESPACE_USE
+ * denies any of the requested types, the entire syscall fails with EPERM.  This
+ * documents the kernel's atomic behavior: namespaces are created sequentially
+ * in __ns_common_init() via copy_namespaces(), and the first Landlock denial
+ * rolls back the whole operation.  Mixing CLONE_NEWUSER (no capability check)
+ * with another CLONE_NEW* type is the typical container-runtime bootstrap
+ * pattern.
+ */
+/* clang-format off */
+FIXTURE(ns_create_multi_flag) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(ns_create_multi_flag)
+{
+	__u64 allowed_types;
+	int expected_errno;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create_multi_flag, partial_denied) {
+	/* clang-format on */
+	/* User namespace allowed; UTS namespace denied. */
+	.allowed_types = CLONE_NEWUSER,
+	.expected_errno = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_create_multi_flag, both_allowed) {
+	/* clang-format on */
+	.allowed_types = CLONE_NEWUSER | CLONE_NEWUTS,
+	.expected_errno = 0,
+};
+
+FIXTURE_SETUP(ns_create_multi_flag)
+{
+}
+
+FIXTURE_TEARDOWN(ns_create_multi_flag)
+{
+}
+
+TEST_F(ns_create_multi_flag, unshare)
+{
+	int ruleset_fd, status, err;
+	pid_t child;
+
+	disable_caps(_metadata);
+
+	/* Run unshare(2) in a child to avoid polluting the test process. */
+	child = fork();
+	ASSERT_LE(0, child);
+
+	if (child == 0) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		ASSERT_EQ(0, add_ns_rule(ruleset_fd, variant->allowed_types));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+
+		err = unshare(CLONE_NEWUSER | CLONE_NEWUTS);
+		if (variant->expected_errno) {
+			EXPECT_EQ(-1, err);
+			EXPECT_EQ(variant->expected_errno, errno);
+		} else {
+			EXPECT_EQ(0, err);
+		}
+		_exit(_metadata->exit_code);
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+/* clang-format off */
+FIXTURE(setns_cross_process) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(setns_cross_process)
+{
+	bool is_sandboxed;
+	bool has_rule;
+	int expected_setns;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(setns_cross_process, denied) {
+	/* clang-format on */
+	.is_sandboxed = true,
+	.has_rule = false,
+	.expected_setns = EPERM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(setns_cross_process, sandboxed_allowed) {
+	/* clang-format on */
+	.is_sandboxed = true,
+	.has_rule = true,
+	.expected_setns = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(setns_cross_process, unsandboxed) {
+	/* clang-format on */
+	.is_sandboxed = false,
+	.has_rule = false,
+	.expected_setns = 0,
+};
+
+FIXTURE_SETUP(setns_cross_process)
+{
+}
+
+FIXTURE_TEARDOWN(setns_cross_process)
+{
+}
+
+/*
+ * setns into a child's UTS namespace: when sandboxed with
+ * LANDLOCK_PERM_NAMESPACE_USE denying UTS, the rule-based check applies
+ * regardless of which process created the namespace.  This fixture exercises
+ * only CLONE_NEWUTS; the same enforcement applies to every namespace type (see
+ * hook_namespace_install() in security/landlock/ns.c), so per-type variants
+ * would not exercise different code paths.
+ */
+TEST_F(setns_cross_process, setns)
+{
+	int ruleset_fd, ns_fd, status;
+	pid_t child;
+	int pipe_parent[2], pipe_child[2];
+	char buf, path[64];
+
+	disable_caps(_metadata);
+
+	/*
+	 * Enable dumpable so the parent can read /proc/<child>/ns/uts.  Without
+	 * this, ptrace access checks (PTRACE_MODE_READ) prevent opening another
+	 * process's namespace entries.
+	 */
+	ASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0));
+
+	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]));
+
+		/* Child: create a UTS namespace. */
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		ASSERT_EQ(0, unshare(CLONE_NEWUTS));
+
+		drop_caps(_metadata);
+		ASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0));
+
+		/* Signal parent that the namespace is ready. */
+		ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+
+		/* Wait for parent to finish testing. */
+		ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+		_exit(_metadata->exit_code);
+	}
+
+	EXPECT_EQ(0, close(pipe_parent[0]));
+	EXPECT_EQ(0, close(pipe_child[1]));
+
+	/* Wait for child namespace. */
+	ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
+	EXPECT_EQ(0, close(pipe_child[0]));
+
+	/* Open the child's NS FD BEFORE creating the domain. */
+	snprintf(path, sizeof(path), "/proc/%d/ns/uts", child);
+	ns_fd = open(path, O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		if (variant->has_rule)
+			ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+	}
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	if (variant->expected_setns) {
+		EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));
+		EXPECT_EQ(variant->expected_setns, errno);
+	} else {
+		EXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS));
+	}
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, close(ns_fd));
+
+	/* Release child. */
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+	EXPECT_EQ(0, close(pipe_parent[1]));
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+/*
+ * Verify that LANDLOCK_PERM_NAMESPACE_USE and LANDLOCK_PERM_CAPABILITY_USE
+ * apply simultaneously: creating/entering a non-user namespace requires both
+ * the namespace type to be allowed AND CAP_SYS_ADMIN to be allowed.  User
+ * namespace creation is the exception (no capable() call from the kernel).
+ */
+TEST(setns_and_create)
+{
+	int ruleset_fd, ns_fd;
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |
+				LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWUTS,
+	};
+	const struct landlock_capability_attr cap_attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_ADMIN),
+	};
+
+	disable_caps(_metadata);
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &cap_attr, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* UTS unshare: allowed by NS rule + CAP_SYS_ADMIN allowed. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWUTS));
+
+	/* IPC unshare: denied by NS rule (type not allowed). */
+	EXPECT_EQ(-1, unshare(CLONE_NEWIPC));
+	EXPECT_EQ(EPERM, errno);
+
+	/* setns into current UTS: allowed by NS rule. */
+	ns_fd = open("/proc/self/ns/uts", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+	EXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, close(ns_fd));
+
+	/*
+	 * User namespace creation: only LANDLOCK_PERM_NAMESPACE_USE needed (no
+	 * capable() call from the kernel for user NS).  Denied because
+	 * CLONE_NEWUSER is not in the allowed namespace types.
+	 */
+	EXPECT_EQ(-1, unshare(CLONE_NEWUSER));
+	EXPECT_EQ(EPERM, errno);
+}
+
+/*
+ * Verify that LANDLOCK_PERM_CAPABILITY_USE can deny the CAP_SYS_ADMIN check
+ * that the kernel performs before the Landlock namespace hook is reached.  The
+ * NS type is allowed but the required capability is not, so the operation fails
+ * on the capability check.
+ *
+ * User namespace creation is the exception: no capable() call, so the operation
+ * succeeds with just LANDLOCK_PERM_NAMESPACE_USE.
+ */
+TEST(two_perm_cap_denied)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |
+				LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWUTS | CLONE_NEWUSER,
+	};
+	/* CAP_SYS_ADMIN is NOT allowed. */
+	const struct landlock_capability_attr cap_attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_CHROOT),
+	};
+	int ruleset_fd, ns_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &cap_attr, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * UTS creation: the process holds CAP_SYS_ADMIN but Landlock denies it
+	 * (not in the cap rule), so the kernel's ns_capable(CAP_SYS_ADMIN) gate
+	 * fails before the namespace hook is reached.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+
+	/*
+	 * setns into the current UTS namespace: the type is allowed but
+	 * CAP_SYS_ADMIN is denied, so the kernel's ns_capable(CAP_SYS_ADMIN)
+	 * gate fails before the namespace hook is reached (the setns capability
+	 * leg, complementing the unshare/create leg above).
+	 */
+	ns_fd = open("/proc/self/ns/uts", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+	EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	EXPECT_EQ(0, close(ns_fd));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/*
+	 * User NS creation: no capable() call from the kernel, so only
+	 * LANDLOCK_PERM_NAMESPACE_USE applies.  CLONE_NEWUSER is in the allowed
+	 * set, so this succeeds.
+	 */
+	EXPECT_EQ(0, unshare(CLONE_NEWUSER));
+}
+
+/*
+ * Combining CLONE_NEWUSER with another namespace type in a single unshare(2)
+ * call does not exempt the non-user namespace from the capability check: the
+ * kernel checks ns_capable(new_user_ns, CAP_SYS_ADMIN) for it, and the
+ * namespace-agnostic capability hook denies it when the domain handles
+ * LANDLOCK_PERM_CAPABILITY_USE without allowing CAP_SYS_ADMIN.  Both namespace
+ * types are allowed here, yet the combined call fails atomically, exactly like
+ * the equivalent two-call sequence.
+ */
+TEST(two_perm_combined_unshare_denied)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |
+				LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	/* Both namespace types are allowed; no capability is allowed. */
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWUSER | CLONE_NEWUTS,
+	};
+	int ruleset_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * CAP_SYS_ADMIN is held so the kernel's commoncap check passes (via
+	 * ownership of the new user namespace) and the denial is attributable
+	 * to Landlock.  The UTS leg needs CAP_SYS_ADMIN, which the capability
+	 * hook denies, so the atomic unshare(2) fails and no namespace is
+	 * created.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUSER | CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* The user namespace alone has no capability check, so it succeeds. */
+	EXPECT_EQ(0, unshare(CLONE_NEWUSER));
+}
+
+/*
+ * Positive complement to two_perm_combined_unshare_denied: allowing
+ * CAP_SYS_ADMIN lets the same combined unshare(CLONE_NEWUSER | CLONE_NEWUTS)
+ * succeed, proving the denial above comes from the missing capability allowance
+ * and not the namespace rule.  Run in a child because a successful unshare(2)
+ * mutates the caller.
+ */
+TEST(two_perm_combined_unshare_allowed)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |
+				LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWUSER | CLONE_NEWUTS,
+	};
+	const struct landlock_capability_attr cap_attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_ADMIN),
+	};
+	int ruleset_fd, status;
+	pid_t child;
+
+	disable_caps(_metadata);
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+		ASSERT_LE(0, ruleset_fd);
+		ASSERT_EQ(0,
+			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+					    &ns_attr, 0));
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_CAPABILITY,
+					       &cap_attr, 0));
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		EXPECT_EQ(0, unshare(CLONE_NEWUSER | CLONE_NEWUTS));
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		_exit(_metadata->exit_code);
+	}
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+/*
+ * Inverse of two_perm_cap_denied: the required capability is allowed but the
+ * namespace type is not, so the Landlock namespace hook denies the operation
+ * (the capability leg passes).  A positive control (an allowed type creates
+ * successfully with the same allowed capability) proves CAP_SYS_ADMIN is
+ * genuinely exercisable, so the UTS denial is attributable to the namespace
+ * hook rather than the capability hook.
+ */
+TEST(two_perm_ns_denied)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |
+				LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	/* CLONE_NEWUTS is NOT allowed; CLONE_NEWNET is. */
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWNET,
+	};
+	const struct landlock_capability_attr cap_attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_ADMIN),
+	};
+	int ruleset_fd;
+
+	disable_caps(_metadata);
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &cap_attr, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * Positive control: CLONE_NEWNET is allowed and CAP_SYS_ADMIN is
+	 * allowed, so the kernel's ns_capable() gate passes and the namespace
+	 * hook allows it.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, unshare(CLONE_NEWNET));
+
+	/*
+	 * CAP_SYS_ADMIN is allowed (ns_capable passes), but CLONE_NEWUTS is not
+	 * in the allowed namespace types, so the Landlock namespace hook denies
+	 * it.
+	 */
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/*
+ * Mount namespace setns is unique: the kernel checks both CAP_SYS_ADMIN and
+ * CAP_SYS_CHROOT in mntns_install().  Verify that allowing CAP_SYS_ADMIN alone
+ * is not sufficient.
+ */
+TEST(two_perm_mnt_setns)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |
+				LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWNS,
+	};
+	const struct landlock_capability_attr cap_admin = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_ADMIN),
+	};
+	const struct landlock_capability_attr cap_admin_chroot = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_ADMIN) |
+					(1ULL << CAP_SYS_CHROOT),
+	};
+	int ruleset_fd, ns_fd;
+
+	disable_caps(_metadata);
+
+	/* Layer 1: allow mount NS + CAP_SYS_ADMIN only (no CAP_SYS_CHROOT). */
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &cap_admin, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	ns_fd = open("/proc/self/ns/mnt", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	/*
+	 * Fails: mntns_install() checks CAP_SYS_ADMIN (allowed) then
+	 * CAP_SYS_CHROOT (denied by LANDLOCK_PERM_CAPABILITY_USE).
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWNS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+
+	/* Layer 2: also allows CAP_SYS_CHROOT. */
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &cap_admin_chroot, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * Still fails: layer 1 still denies CAP_SYS_CHROOT.  Landlock layer
+	 * stacking means the most restrictive layer wins.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWNS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(0, close(ns_fd));
+}
+
+/*
+ * Positive complement to two_perm_mnt_setns: a single layer that allows the
+ * mount namespace type AND both CAP_SYS_ADMIN and CAP_SYS_CHROOT lets
+ * setns(CLONE_NEWNS) into the process's own mount namespace succeed.  Proves
+ * the denial legs above are caused by the withheld CAP_SYS_CHROOT, not an
+ * unconditional block of mount setns.
+ */
+TEST(two_perm_mnt_setns_allowed)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_perm = LANDLOCK_PERM_NAMESPACE_USE |
+				LANDLOCK_PERM_CAPABILITY_USE,
+	};
+	const struct landlock_namespace_attr ns_attr = {
+		.perm = LANDLOCK_PERM_NAMESPACE_USE,
+		.allowed_namespace_types = CLONE_NEWNS,
+	};
+	const struct landlock_capability_attr cap_attr = {
+		.perm = LANDLOCK_PERM_CAPABILITY_USE,
+		.allowed_capabilities = (1ULL << CAP_SYS_ADMIN) |
+					(1ULL << CAP_SYS_CHROOT),
+	};
+	int ruleset_fd, ns_fd;
+
+	disable_caps(_metadata);
+
+	/* Open the NS FD before enforcing the domain. */
+	ns_fd = open("/proc/self/ns/mnt", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE,
+				       &ns_attr, 0));
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY,
+				       &cap_attr, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * mntns_install() checks CAP_SYS_ADMIN then CAP_SYS_CHROOT (both
+	 * allowed by the rule) and the mount NS type is allowed, so setns into
+	 * the process's own mount namespace succeeds.
+	 */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	set_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(0, setns(ns_fd, CLONE_NEWNS));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	clear_cap(_metadata, CAP_SYS_CHROOT);
+	EXPECT_EQ(0, close(ns_fd));
+}
+
+/*
+ * setns() through a namespace fd inherited across fork() is still governed by
+ * the child's own Landlock domain: the child enforces a domain denying
+ * CLONE_NEWUTS, then joining the inherited /proc/self/ns/uts fd is denied.
+ */
+TEST(setns_inherited_fd)
+{
+	int ns_fd, status;
+	pid_t child;
+
+	disable_caps(_metadata);
+
+	/* Open the UTS ns fd before fork() so the child inherits it. */
+	ns_fd = open("/proc/self/ns/uts", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		int ruleset_fd;
+
+		/*
+		 * Baseline: joining the inherited fd works before the domain,
+		 * so the later EPERM is attributable to Landlock.
+		 */
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		ASSERT_EQ(0, setns(ns_fd, CLONE_NEWUTS));
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+
+		ruleset_fd = create_ns_ruleset();
+		ASSERT_LE(0, ruleset_fd);
+		/* No rule allows UTS. */
+		enforce_ruleset(_metadata, ruleset_fd);
+		EXPECT_EQ(0, close(ruleset_fd));
+
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));
+		EXPECT_EQ(EPERM, errno);
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	EXPECT_EQ(0, close(ns_fd));
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+/* Audit tests */
+
+static int matches_log_ns_create(int audit_fd, __u64 ns_type)
+{
+	static const char log_template[] = REGEX_LANDLOCK_PREFIX
+		" blockers=perm\\.namespace_use"
+		" namespace_type=0x%x"
+		" namespace_id=0$";
+	char log_match[sizeof(log_template) + 10];
+	int log_match_len;
+
+	log_match_len = snprintf(log_match, sizeof(log_match), log_template,
+				 (unsigned int)ns_type);
+	if (log_match_len >= sizeof(log_match))
+		return -E2BIG;
+
+	return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
+				  NULL);
+}
+
+static int matches_log_ns_setns(int audit_fd, __u64 ns_type, __u64 ns_id)
+{
+	static const char log_template[] = REGEX_LANDLOCK_PREFIX
+		" blockers=perm\\.namespace_use"
+		" namespace_type=0x%x"
+		" namespace_id=%llu$";
+	char log_match[sizeof(log_template) + 32];
+	int log_match_len;
+
+	log_match_len = snprintf(log_match, sizeof(log_match), log_template,
+				 (unsigned int)ns_type,
+				 (unsigned long long)ns_id);
+	if (log_match_len >= sizeof(log_match))
+		return -E2BIG;
+
+	return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
+				  NULL);
+}
+
+FIXTURE(ns_audit)
+{
+	struct audit_filter audit_filter;
+	int audit_fd;
+};
+
+FIXTURE_SETUP(ns_audit)
+{
+	ASSERT_TRUE(is_in_init_user_ns());
+
+	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);
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(ns_audit)
+{
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter));
+}
+
+/*
+ * Verifies that a denied namespace creation produces the expected audit record
+ * with the perm.namespace_use blocker string and namespace_type.
+ */
+TEST_F(ns_audit, create_denied)
+{
+	struct audit_records records;
+	int ruleset_fd;
+	__u64 domain_id;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS));
+
+	/*
+	 * The domain allocation record is emitted in the same event as the
+	 * first denial; anchor its status=allocated and enforcing pid.  Both
+	 * access and domain records are now consumed, so none remain.
+	 */
+	EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(),
+						  &domain_id));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+TEST_F(ns_audit, create_allowed)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, unshare(CLONE_NEWUTS));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* No records: allowed operations never trigger audit logging. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+TEST_F(ns_audit, setns_allowed)
+{
+	struct audit_records records;
+	int ruleset_fd, ns_fd;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	ns_fd = open("/proc/self/ns/uts", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	/* Allowed: should succeed with no audit record. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, close(ns_fd));
+
+	/* No records: allowed setns never triggers audit logging. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+TEST_F(ns_audit, setns_denied)
+{
+	struct audit_records records;
+	int ruleset_fd, ns_fd;
+	__u64 ns_id, domain_id;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	/* No rule allows UTS -> denied. */
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	ns_fd = open("/proc/self/ns/uts", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	/* The audit record logs the target namespace's exact ns_id. */
+	ASSERT_EQ(0, ioctl(ns_fd, NS_GET_ID, &ns_id));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, close(ns_fd));
+
+	/* Verify the audit record for setns denial, anchored to the ns_id. */
+	EXPECT_EQ(0, matches_log_ns_setns(self->audit_fd, CLONE_NEWUTS, ns_id));
+
+	/*
+	 * The domain allocation record is emitted in the same event as the
+	 * first denial; anchor its status=allocated and enforcing pid.  Both
+	 * access and domain records are now consumed, so none remain.
+	 */
+	EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(),
+						  &domain_id));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+/*
+ * A quieted denial is still denied (EPERM); only its audit record is
+ * suppressed.  Quiet-only rule (empty allowed set).
+ */
+TEST_F(ns_audit, create_quieted)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* The denial is suppressed: no access record. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+/*
+ * A quieted setns denial is still denied (EPERM); only its audit record is
+ * suppressed.  The install-hook mirror of create_quieted.
+ */
+TEST_F(ns_audit, setns_quieted)
+{
+	struct audit_records records;
+	int ruleset_fd, ns_fd;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	ns_fd = open("/proc/self/ns/uts", O_RDONLY);
+	ASSERT_LE(0, ns_fd);
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, close(ns_fd));
+
+	/* The denial is suppressed: no access record. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+/*
+ * Quiet is per-member: quieting one namespace type does not silence the denial
+ * of another type denied by the same layer.
+ */
+TEST_F(ns_audit, quiet_is_per_member)
+{
+	struct audit_records records;
+	int ruleset_fd;
+	__u64 domain_id;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	/*
+	 * Quiet CLONE_NEWNET only; CLONE_NEWUTS is neither allowed nor quiet.
+	 */
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWNET));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* The unquieted CLONE_NEWUTS denial is still logged. */
+	EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS));
+	EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(),
+						  &domain_id));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+/*
+ * Quieting an unknown namespace bit has no effect: a rule that quiets only a
+ * bit the running kernel does not know still logs the denial of a known type.
+ * Mirrors the forward-compatibility contract for the quiet mask.
+ */
+TEST_F(ns_audit, quiet_unknown_bit_no_effect)
+{
+	struct audit_records records;
+	int ruleset_fd;
+	__u64 domain_id;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	/* Quiet only an unknown bit (bit 31 is not a CLONE_NEW* value). */
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, (1ULL << 31)));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* The unknown quiet bit does not suppress the known denial. */
+	EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS));
+	EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(),
+						  &domain_id));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(0, records.domain);
+}
+
+/* A single rule can both allow one member and quiet the denial of another. */
+TEST_F(ns_audit, allow_and_quiet)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWNET, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* Allowed member: succeeds. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, unshare(CLONE_NEWNET));
+	/* Quieted member: denied but not logged. */
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+/*
+ * Quieting a member that is also allowed is inert: an allowed member is never
+ * denied, so its quiet bit has no effect.
+ */
+TEST_F(ns_audit, allow_and_quiet_same_member)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	/* Same member both allowed and quieted. */
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWNET, CLONE_NEWNET));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* The member is allowed (quiet is inert), so it succeeds. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(0, unshare(CLONE_NEWNET));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* Nothing denied, nothing logged. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+/*
+ * Only the youngest denying layer decides quieting: a parent that quiets a
+ * member cannot silence a denial by a deeper layer that handles but does not
+ * quiet it.
+ */
+TEST_F(ns_audit, quiet_youngest_layer_wins)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	/* Parent layer: handles namespaces and quiets CLONE_NEWUTS. */
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* Child layer: handles namespaces but does not quiet CLONE_NEWUTS. */
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* The child layer denies and does not quiet: logged. */
+	EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS));
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+/*
+ * A parent layer that quiets and denies a member suppresses its own denial even
+ * when it is not the youngest layer: a child layer allows the member, so the
+ * parent is the youngest (and only) denying layer and its stored quiet mask
+ * fires.  Complements quiet_youngest_layer_wins.
+ */
+TEST_F(ns_audit, quiet_parent_only_denying_layer)
+{
+	struct audit_records records;
+	int ruleset_fd;
+
+	/* Parent layer: quiet-only, so it denies CLONE_NEWUTS. */
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* Child layer: allows CLONE_NEWUTS, so only the parent denies it. */
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, unshare(CLONE_NEWUTS));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/* The parent quiets and is the youngest denying layer: suppressed. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+}
+
+/* clang-format off */
+FIXTURE(ns_proc_open) {
+	struct audit_filter audit_filter;
+	int audit_fd;
+};
+/* clang-format on */
+
+FIXTURE_VARIANT(ns_proc_open)
+{
+	__u64 ns_type;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_proc_open, mnt) {
+	/* clang-format on */
+	.ns_type = CLONE_NEWNS,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ns_proc_open, user) {
+	/* clang-format on */
+	.ns_type = CLONE_NEWUSER,
+};
+
+FIXTURE_SETUP(ns_proc_open)
+{
+	ASSERT_TRUE(is_in_init_user_ns());
+
+	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);
+	clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+FIXTURE_TEARDOWN(ns_proc_open)
+{
+	set_cap(_metadata, CAP_AUDIT_CONTROL);
+	EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter));
+}
+
+/*
+ * Opening /proc/self/ns/<type> only acquires a procfs reference, not membership
+ * or an acquired fd of the kind LANDLOCK_PERM_NAMESPACE_USE gates.  Verify the
+ * open is unrestricted even when the permission is handled with no rules.
+ */
+TEST_F(ns_proc_open, open_unrestricted)
+{
+	char proc_path[NS_PROC_PATH_MAX];
+	struct audit_records records;
+	int ruleset_fd, fd;
+
+	ASSERT_TRUE(
+		ns_is_supported(variant->ns_type, proc_path, sizeof(proc_path)))
+	{
+		TH_LOG("Namespace type 0x%llx not supported",
+		       (unsigned long long)variant->ns_type);
+	}
+
+	ruleset_fd = create_ns_ruleset();
+	ASSERT_LE(0, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	fd = open(proc_path, O_RDONLY);
+	ASSERT_LE(0, fd)
+	{
+		TH_LOG("open(%s) failed: %s", proc_path, strerror(errno));
+	}
+
+	/* No Landlock denial. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+
+	EXPECT_EQ(0, close(fd));
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/wrappers.h b/tools/testing/selftests/landlock/wrappers.h
index 65548323e45d9f..e6fe46b7c2cc4e 100644
--- a/tools/testing/selftests/landlock/wrappers.h
+++ b/tools/testing/selftests/landlock/wrappers.h
@@ -9,6 +9,7 @@
 
 #define _GNU_SOURCE
 #include <linux/landlock.h>
+#include <linux/sched.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -45,3 +46,31 @@ static inline pid_t sys_gettid(void)
 {
 	return syscall(__NR_gettid);
 }
+
+static inline pid_t sys_clone3(struct clone_args *args, size_t size)
+{
+	return syscall(__NR_clone3, args, size);
+}
+
+static inline int sys_open_tree(int dfd, const char *filename,
+				unsigned int flags)
+{
+	return syscall(__NR_open_tree, dfd, filename, flags);
+}
+
+static inline int sys_fsopen(const char *fsname, unsigned int flags)
+{
+	return syscall(__NR_fsopen, fsname, flags);
+}
+
+static inline int sys_fsconfig(int fs_fd, unsigned int cmd, const char *key,
+			       const void *value, int aux)
+{
+	return syscall(__NR_fsconfig, fs_fd, cmd, key, value, aux);
+}
+
+static inline int sys_fsmount(int fs_fd, unsigned int flags,
+			      unsigned int attr_flags)
+{
+	return syscall(__NR_fsmount, fs_fd, flags, attr_flags);
+}

3/2 2026/07/27 06:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 75838
							output: 33
							thoughts: 817
						

Thoughts:
**Fuzzing Landlock LSM Enhancements**

Okay, so we have this patch that's expanding the Landlock LSM with capability and namespace restrictions. Functionally, this is a significant change, so it *absolutely* needs fuzzing. We're touching the core kernel, specifically in `security/landlock/`, and adding new files – `cap.c`, `cap.h`, `ns.c`, `ns.h` – and modifying `include/uapi/linux/landlock.h` to include `LANDLOCK_RULE_NAMESPACE` and `LANDLOCK_RULE_CAPABILITY`. We're also introducing new LSM hooks: `capable`, `namespace_init`, `namespace_free`, and `namespace_install`.

Now, let's nail down the critical kernel functions to target with the fuzzer. The new syscall entrypoints are the key: we have `landlock_create_ruleset` (which has been modified to handle `handled_perm`), `landlock_add_rule` (now accepting `LANDLOCK_RULE_NAMESPACE` and `LANDLOCK_RULE_CAPABILITY`), and `landlock_restrict_self` (no direct code change, but will enforce the new rules).

Thinking about the hooks themselves, they're called during the calls to `security_capable`, `security_namespace_init`, `security_namespace_install`. Since we can't focus on static functions we're now focusing on calling contexts. The hooks are: `hook_capable` in `security/landlock/cap.c`, and `hook_namespace_init`, `hook_namespace_install` in `security/landlock/ns.c`.

I'm checking `security/landlock/cap.c` and `security/landlock/ns.c` to see if there are any non-static functions we can fuzz. `hook_capable` is static, as is `landlock_add_cap_hooks`, but is only called at init. The syscalls themselves are the ones that should be fuzz targets: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, and `sys_landlock_restrict_self`.

The guidance says to favor non-static, non-inlined API entrypoints. So, that means `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, and `sys_landlock_restrict_self` are prime candidates. We should check these, and follow the calls to `security_namespace_init` and `security_namespace_install` in `security/security.c` and make sure they are correct.

I'm cross-referencing this against the actual syscall implementation in `security/landlock/syscalls.c`. Yep, there they are: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, and `sys_landlock_restrict_self`. Also, `landlock_create_ruleset` and the hooks.

The real fuzzing targets are looking like: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, `sys_landlock_restrict_self`, and the calls to `security_namespace_init`, and `security_namespace_install`.

Just to be thorough, I'm verifying the Landlock config in `Kconfig`. `SECURITY_LANDLOCK` is what controls it. No new configs are added. I should probably double-check that with `grepper` too to be absolutely certain.



4/2 2026/07/27 06:25 tool grepper
0m Args:
{
  "Expression": "SYSCALL_DEFINE.*landlock",
  "PathPrefix": "security/landlock/"
}

Results:
{
  "Output": "security/landlock/syscalls.c=191=const int landlock_abi_version = 11;\n--\nsecurity/landlock/syscalls.c-227- */\nsecurity/landlock/syscalls.c:228:SYSCALL_DEFINE3(landlock_create_ruleset,\nsecurity/landlock/syscalls.c-229-\t\tconst struct landlock_ruleset_attr __user *const, attr,\n--\nsecurity/landlock/syscalls.c=517=static int add_rule_capability(struct landlock_ruleset *const ruleset,\n--\nsecurity/landlock/syscalls.c-619- */\nsecurity/landlock/syscalls.c:620:SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,\nsecurity/landlock/syscalls.c-621-\t\tconst enum landlock_rule_type, rule_type,\n--\nsecurity/landlock/syscalls.c-683- */\nsecurity/landlock/syscalls.c:684:SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,\nsecurity/landlock/syscalls.c-685-\t\tflags)\n"
}

5/2 2026/07/27 06:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 77768
							output: 31
							thoughts: 63
						

Thoughts:
Here's my take on those syscalls and the related kernel code:

**Deep Dive into Landlock System Calls**

Okay, so we're looking at Landlock, and the three core syscalls are jumping out at me: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, and `sys_landlock_restrict_self`. These define the creation, rule addition, and enforcement lifecycle of a Landlock ruleset, respectively. Pretty standard pattern for a security framework like this; you build your policy, then you enforce it.

Now, where do I start digging for the core initialization and installation logic? My gut says `security/security.c` is the place to start. I'll need to check for `security_namespace_init` and `security_namespace_install` functions. Those seem like the likely candidates for the initial setup and the hook installation needed to get Landlock up and running within the security namespace architecture. This would provide the framework for integrating these Landlock syscalls into the overall Linux security infrastructure and make sure they are properly integrated within the system's security contexts. I expect to find some interesting interaction with existing security modules and their potential role in the Landlock implementation. Let's see how well it's all integrated.



6/2 2026/07/27 06:25 tool grepper
0m Args:
{
  "Expression": "^int security_namespace_",
  "PathPrefix": "security/security.c"
}

Results:
{
  "Output": "security/security.c=3370=int security_create_user_ns(const struct cred *cred)\n--\nsecurity/security.c-3384- */\nsecurity/security.c:3385:int security_namespace_init(struct ns_common *ns)\nsecurity/security.c-3386-{\n--\nsecurity/security.c=3407=void security_namespace_free(struct ns_common *ns)\n--\nsecurity/security.c-3427- */\nsecurity/security.c:3428:int security_namespace_install(const struct nsset *nsset, struct ns_common *ns)\nsecurity/security.c-3429-{\n"
}

7/2 2026/07/27 06:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 78315
							output: 38
							thoughts: 39
						

Thoughts:
Okay, here's my take on those functions and the thought process, from my expert perspective:

**Analyzing Potential Security Implications**

Alright, I see we're dealing with `security_namespace_init` and `security_namespace_install`. These function names immediately scream "security context" to me, likely related to a Landlock-style namespace implementation, given the naming convention. That suggests they're crucial in setting up isolated environments, perhaps for sandboxing or containerization. My immediate instinct is to dig deeper to understand their visibility and potential entry points. Where are they being called from? What parameters are passed?

My first step is to check for any non-static functions in `security/landlock/cap.c`. Non-static functions are the public interface, the entry points that the kernel or other modules *could* call into. I want to identify any functions outside of that file, as that may allow them to be used out of context, or misued. I need to understand whether these namespace functions are truly isolated to Landlock, or if something else outside is reaching into them. The goal is to see how they're exposed and if there's any risk of unintended external access.



8/2 2026/07/27 06:25 tool grepper
0m Args:
{
  "Expression": "^[a-zA-Z_].*\\(",
  "PathPrefix": "security/landlock/cap.c"
}

Results:
{
  "Output": "security/landlock/cap.c=21=static const struct access_masks cap_perm = {\n--\nsecurity/landlock/cap.c-40- */\nsecurity/landlock/cap.c:41:static int hook_capable(const struct cred *cred, struct user_namespace *ns,\nsecurity/landlock/cap.c-42-\t\t\tint cap, unsigned int opts)\n--\nsecurity/landlock/cap.c=69=static struct security_hook_list landlock_hooks[] __ro_after_init = {\n--\nsecurity/landlock/cap.c-72-\nsecurity/landlock/cap.c:73:__init void landlock_add_cap_hooks(void)\nsecurity/landlock/cap.c-74-{\n--\nsecurity/landlock/cap.c-82-\nsecurity/landlock/cap.c:83:static void test_cap_to_bit(struct kunit *const test)\nsecurity/landlock/cap.c-84-{\n--\nsecurity/landlock/cap.c-93-\nsecurity/landlock/cap.c:94:static void test_cap_to_bit_invalid(struct kunit *const test)\nsecurity/landlock/cap.c-95-{\n--\nsecurity/landlock/cap.c-99-\nsecurity/landlock/cap.c:100:static void test_caps_to_bits_valid(struct kunit *const test)\nsecurity/landlock/cap.c-101-{\n--\nsecurity/landlock/cap.c-107-\nsecurity/landlock/cap.c:108:static void test_caps_to_bits_unknown(struct kunit *const test)\nsecurity/landlock/cap.c-109-{\n--\nsecurity/landlock/cap.c-113-\nsecurity/landlock/cap.c:114:static void test_caps_to_bits_zero(struct kunit *const test)\nsecurity/landlock/cap.c-115-{\n--\nsecurity/landlock/cap.c=130=static struct kunit_suite test_suite = {\n--\nsecurity/landlock/cap.c-134-\nsecurity/landlock/cap.c:135:kunit_test_suite(test_suite);\nsecurity/landlock/cap.c-136-\n"
}

9/2 2026/07/27 06:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 79226
							output: 38
							thoughts: 45
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Focusing on Landlock's Initialization**

Alright, so the core of Landlock's initialization seems to hinge on `landlock_add_cap_hooks`, marked as `__init`. That's not entirely surprising; it's the typical pattern for kernel modules to set up their crucial hooks and data structures during initialization. The fact that it's *the only* non-static function immediately signals that this is the entry point, the primary point of interest for understanding how Landlock integrates into the broader kernel security framework.

My next immediate thought is to check `security/landlock/ns.c`. That file seems like a logical place to find any other non-static functions within Landlock. It likely contains functions that are exported or used by other parts of the kernel. Understanding any non-static functions will be key to working out the broader Landlock API. I need to see what other functionalities are available outside of the initialization.



10/2 2026/07/27 06:25 tool grepper
0m Args:
{
  "Expression": "^[a-zA-Z_].*\\(",
  "PathPrefix": "security/landlock/ns.c"
}

Results:
{
  "Output": "security/landlock/ns.c-22-/* Ensures the audit ns_id field can hold ns_common.ns_id without truncation. */\nsecurity/landlock/ns.c:23:static_assert(sizeof(((struct common_audit_data *)NULL)-\u003eu.ns.ns_id) \u003e=\nsecurity/landlock/ns.c-24-\t      sizeof(((struct ns_common *)NULL)-\u003ens_id));\n--\nsecurity/landlock/ns.c=26=static const struct access_masks ns_perm = {\n--\nsecurity/landlock/ns.c-41- */\nsecurity/landlock/ns.c:42:static int check_ns_type(struct ns_common *const ns)\nsecurity/landlock/ns.c-43-{\n--\nsecurity/landlock/ns.c-67-\nsecurity/landlock/ns.c:68:static int hook_namespace_init(struct ns_common *const ns)\nsecurity/landlock/ns.c-69-{\n--\nsecurity/landlock/ns.c-72-\nsecurity/landlock/ns.c:73:static int hook_namespace_install(const struct nsset *const nsset,\nsecurity/landlock/ns.c-74-\t\t\t\t  struct ns_common *const ns)\n--\nsecurity/landlock/ns.c=79=static struct security_hook_list landlock_hooks[] __ro_after_init = {\n--\nsecurity/landlock/ns.c-83-\nsecurity/landlock/ns.c:84:__init void landlock_add_ns_hooks(void)\nsecurity/landlock/ns.c-85-{\n--\nsecurity/landlock/ns.c-103-\nsecurity/landlock/ns.c:104:static void test_ns_type_to_bit(struct kunit *const test)\nsecurity/landlock/ns.c-105-{\n--\nsecurity/landlock/ns.c-112-\nsecurity/landlock/ns.c:113:static void test_ns_type_to_bit_unknown(struct kunit *const test)\nsecurity/landlock/ns.c-114-{\n--\nsecurity/landlock/ns.c-117-\nsecurity/landlock/ns.c:118:static void test_ns_types_to_bits_all(struct kunit *const test)\nsecurity/landlock/ns.c-119-{\n--\nsecurity/landlock/ns.c-129-\nsecurity/landlock/ns.c:130:static void test_ns_types_to_bits_single(struct kunit *const test)\nsecurity/landlock/ns.c-131-{\n--\nsecurity/landlock/ns.c-134-\nsecurity/landlock/ns.c:135:static void test_ns_types_to_bits_zero(struct kunit *const test)\nsecurity/landlock/ns.c-136-{\n--\nsecurity/landlock/ns.c=149=static struct kunit_suite test_suite = {\n--\nsecurity/landlock/ns.c-153-\nsecurity/landlock/ns.c:154:kunit_test_suite(test_suite);\nsecurity/landlock/ns.c-155-\n"
}

11/2 2026/07/27 06:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 80342
							output: 29
							thoughts: 338
						

Thoughts:
**Analyzing Landlock Functionality for Targeted Testing**

Okay, so I'm looking at the Landlock code and trying to figure out which functions are most relevant for testing, while also adhering to the prompt's constraint about avoiding generic hot-path functions. The core of this seems to be centered around the system calls and namespace setup.

The first thing that jumps out is that `landlock_add_ns_hooks` is the only function I see that's marked as `__init`, which immediately narrows the scope. That suggests focusing on the functions called after initialization.

So, let's start with the system calls: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, and `sys_landlock_restrict_self`. Those seem to be the primary entry points for interacting with the Landlock ruleset. They're definitely in scope.

Now, considering namespace operations, `security_namespace_init` and `security_namespace_install` are also candidates. They're crucial for setting up and installing Landlock within namespaces, which is not a common operation, so it fits the criteria.

However, I need to be careful. The prompt specifically warns against listing generic "hot-path" functions. Thinking about that, `security_capable` is a very common function in security checks, and is likely a very hot path, so I should omit that from the list. It's too generic.

I'll double-check to make sure there aren't any other obvious functions in the core files to consider. I've confirmed from `security/landlock/syscalls.c` that the primary Landlock syscalls are indeed `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, and `sys_landlock_restrict_self`. And from `security/security.c`, I've got `security_namespace_init` and `security_namespace_install`.

Lastly, I need to check for relevant configurations. `SECURITY_LANDLOCK` is the obvious one, but I'll quickly glance at `security/landlock/Kconfig` to confirm there aren't any other feature-specific configurations that might be important for the test setup.



12/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "config ",
  "PathPrefix": "security/landlock/Kconfig"
}

Results:
{
  "Output": "security/landlock/Kconfig-2-\nsecurity/landlock/Kconfig:3:config SECURITY_LANDLOCK\nsecurity/landlock/Kconfig-4-\tbool \"Landlock support\"\n--\nsecurity/landlock/Kconfig-23-\nsecurity/landlock/Kconfig:24:config SECURITY_LANDLOCK_KUNIT_TEST\nsecurity/landlock/Kconfig-25-\tbool \"KUnit tests for Landlock\" if !KUNIT_ALL_TESTS\n--\nsecurity/landlock/Kconfig-34-\t  Run all KUnit tests for Landlock with:\nsecurity/landlock/Kconfig:35:\t  ./tools/testing/kunit/kunit.py run --kunitconfig security/landlock\nsecurity/landlock/Kconfig-36-\n"
}

13/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 81380
							output: 29
							thoughts: 26
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Landlock Configuration Deep Dive**

Alright, so the initial configs I'm dealing with are clearly `SECURITY_LANDLOCK`. My first instinct is to dive into the heart of the matter – I need to understand the dependencies and the overall architectural structure of this landlock implementation within the kernel. That naturally leads me to `security/landlock/Kconfig`. This is where the crucial details are. I'll need to meticulously examine the Kconfig file to identify all the dependencies. I need to figure out what other security options, kernel features, or even hardware capabilities are prerequisites for `SECURITY_LANDLOCK` to function correctly. I'll be looking for things like other security modules that it integrates with, and, how it interacts with the broader kernel subsystems.



14/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "depends on",
  "PathPrefix": "security/landlock/Kconfig"
}

Results:
{
  "Output": "security/landlock/Kconfig=3=config SECURITY_LANDLOCK\nsecurity/landlock/Kconfig-4-\tbool \"Landlock support\"\nsecurity/landlock/Kconfig:5:\tdepends on SECURITY\nsecurity/landlock/Kconfig-6-\tselect SECURITY_NETWORK\n--\nsecurity/landlock/Kconfig=24=config SECURITY_LANDLOCK_KUNIT_TEST\nsecurity/landlock/Kconfig-25-\tbool \"KUnit tests for Landlock\" if !KUNIT_ALL_TESTS\nsecurity/landlock/Kconfig:26:\tdepends on KUNIT=y\nsecurity/landlock/Kconfig:27:\tdepends on SECURITY_LANDLOCK\nsecurity/landlock/Kconfig-28-\tdefault KUNIT_ALL_TESTS\n"
}

15/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 81801
							output: 28
							thoughts: 82
						

Thoughts:
**Dependencies and Namespace Configurations**

Okay, so I see `SECURITY_LANDLOCK` is clearly dependent on `SECURITY`. That's good to know, makes sense. Now, let me see if there are any other configuration variables I need to be aware of, especially related to namespaces. This patch is touching `fs/namespace.c`, `kernel/nscommon.c`, and `kernel/nsproxy.c`, which is a strong indicator. Right, namespaces are controlled by `NAMESPACES`. I should confirm whether `NAMESPACES` is a *requirement* for this whole thing to work. Need to make sure all the pieces are in place before I dive deeper.



16/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "config NAMESPACES",
  "PathPrefix": "init/Kconfig"
}

Results:
{
  "Output": "init/Kconfig=1395=endif # CGROUPS\ninit/Kconfig-1396-\ninit/Kconfig:1397:menuconfig NAMESPACES\ninit/Kconfig-1398-\tbool \"Namespaces support\" if EXPERT\n"
}

17/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 82129
							output: 39
							thoughts: 106
						

Thoughts:
**Namespace Considerations**

Okay, so the fundamental need for `NAMESPACES` is clear, no question there. When diving into the specifics, I'm thinking about `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, `NET_NS`, `CGROUP_NS`, and `TIME_NS` – those are the concrete namespaces we're dealing with. The patch seems to cover them comprehensively, which is good. We should definitely be including `NAMESPACES` and `USER_NS` at the very least, but that's a given.

Now, let me just quickly cross-reference that with the configuration used in the landlock self-tests. I'll need to check the `tools/testing/selftests/landlock/config` file to see which configurations are enabled, so that will make sure they do not conflict.



18/2 2026/07/27 06:26 tool read-file
0m Args:
{
  "File": "tools/testing/selftests/landlock/config",
  "FirstLine": 1,
  "LineCount": 100
}

Results:
{
  "Contents": "   1:\tCONFIG_AF_UNIX_OOB=y\n   2:\tCONFIG_AUDIT=y\n   3:\tCONFIG_CGROUPS=y\n   4:\tCONFIG_CGROUP_SCHED=y\n   5:\tCONFIG_INET=y\n   6:\tCONFIG_IPC_NS=y\n   7:\tCONFIG_IPV6=y\n   8:\tCONFIG_KEYS=y\n   9:\tCONFIG_MPTCP=y\n  10:\tCONFIG_MPTCP_IPV6=y\n  11:\tCONFIG_NET=y\n  12:\tCONFIG_NET_NS=y\n  13:\tCONFIG_OVERLAY_FS=y\n  14:\tCONFIG_PID_NS=y\n  15:\tCONFIG_PROC_FS=y\n  16:\tCONFIG_SECURITY=y\n  17:\tCONFIG_SECURITY_LANDLOCK=y\n  18:\tCONFIG_SHMEM=y\n  19:\tCONFIG_SYSFS=y\n  20:\tCONFIG_TIME_NS=y\n  21:\tCONFIG_TMPFS=y\n  22:\tCONFIG_TMPFS_XATTR=y\n  23:\tCONFIG_USER_NS=y\n  24:\tCONFIG_UTS_NS=y\n"
}

19/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 82777
							output: 30
							thoughts: 76
						

Thoughts:
**My Configuration Checklist**

Okay, time to solidify this environment. I need to make sure I've got the right kernel configurations enabled. Let's see... first, I absolutely need `SECURITY_LANDLOCK`. That's non-negotiable for the level of isolation I'm aiming for. Then come the foundational namespaces: `NAMESPACES`, followed by the critical ones I need: `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, and `NET_NS`. I also know I'll need `CGROUPS` for resource control, and then `TIME_NS`. Just going through my mental checklist here. Ah, one more potential namespace to check... is `CGROUP_NS` a valid config? I'll need to verify that before I can be sure this set up will work as expected.



20/2 2026/07/27 06:26 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config CGROUP_NS",
  "PathPrefix": "init/Kconfig"
}

Results:
{
  "error": "no matches"
}

21/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 83078
							output: 28
							thoughts: 15
						

Thoughts:
Here's my thought process, summarized as if I were considering this myself:

**Namespace Configuration - Checking the Kconfig**

Okay, before diving any deeper, I need to make absolutely sure I'm not missing something obvious. Time to pause and double-check the `init/Kconfig` file. Specifically, I'm looking for any configuration options related to namespaces. This is a crucial step – I need to understand if there are any existing settings that might be relevant or conflict with whatever I'm considering. The Kconfig is the source of truth for these sorts of things, so I'd better confirm there's nothing already defined there that I need to be aware of before proceeding. I'm going to quickly scan for any mentions of "namespace," "network namespace," "pid namespace," or similar keywords. This'll help me avoid reinventing the wheel or unintentionally breaking some pre-existing functionality. Let's see what we've got.



22/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "config .*_NS",
  "PathPrefix": "init/Kconfig"
}

Results:
{
  "Output": "init/Kconfig=1407=if NAMESPACES\ninit/Kconfig-1408-\ninit/Kconfig:1409:config UTS_NS\ninit/Kconfig-1410-\tbool \"UTS namespace\"\n--\ninit/Kconfig-1415-\ninit/Kconfig:1416:config TIME_NS\ninit/Kconfig-1417-\tbool \"TIME namespace\"\n--\ninit/Kconfig-1422-\ninit/Kconfig:1423:config TIME_NS_VDSO\ninit/Kconfig-1424-\tdef_bool TIME_NS \u0026\u0026 GENERIC_GETTIMEOFDAY\ninit/Kconfig-1425-\ninit/Kconfig:1426:config IPC_NS\ninit/Kconfig-1427-\tbool \"IPC namespace\"\n--\ninit/Kconfig-1433-\ninit/Kconfig:1434:config USER_NS\ninit/Kconfig-1435-\tbool \"User namespace\"\n--\ninit/Kconfig-1447-\ninit/Kconfig:1448:config PID_NS\ninit/Kconfig-1449-\tbool \"PID Namespaces\"\n--\ninit/Kconfig-1455-\ninit/Kconfig:1456:config NET_NS\ninit/Kconfig-1457-\tbool \"Network namespace\"\n"
}

23/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 83673
							output: 26
							thoughts: 63
						

Thoughts:
Okay, here's my take on those namespaces...

**Namespace Navigation and Initial Considerations**

Alright, let's see. I've got `UTS_NS`, `TIME_NS`, `IPC_NS`, `USER_NS`, `PID_NS`, and `NET_NS` all rattling around in my head. Thinking about those core namespaces always brings up the question, "What about `CGROUP_NS`?" Is it defined as a namespace? Where is the configuration for it located? It makes sense to start poking around `init/Kconfig` or maybe even deeper in the kernel, specifically, `kernel/cgroup/Kconfig`. I'll have to investigate that further.



24/2 2026/07/27 06:26 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config CGROUP_NS",
  "PathPrefix": ""
}

Results:
{
  "error": "no matches"
}

25/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 83924
							output: 37
							thoughts: 25
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Investigating `CLONE_NEWCGROUP`**

Okay, hold on a second... `CLONE_NEWCGROUP` just popped into my head. I need to be sure I understand this correctly, and more importantly, where this specific flag is defined. I'm going to have to dive into the kernel headers or relevant documentation to pinpoint the exact definition and any associated implications. I need to refresh my understanding of how this flag interacts with the overall namespace infrastructure, especially in relation to the control group hierarchy. What is the architecture to it all, and are there any subtleties I might be missing? Time to dig in and re-familiarize myself.



26/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "CLONE_NEWCGROUP",
  "PathPrefix": "include/uapi/linux/sched.h"
}

Results:
{
  "Output": "include/uapi/linux/sched.h-27-#define CLONE_CHILD_SETTID\t0x01000000\t/* set the TID in the child */\ninclude/uapi/linux/sched.h:28:#define CLONE_NEWCGROUP\t\t0x02000000\t/* New cgroup namespace */\ninclude/uapi/linux/sched.h-29-#define CLONE_NEWUTS\t\t0x04000000\t/* New utsname namespace */\n"
}

27/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 84278
							output: 30
							thoughts: 7
						

Thoughts:
Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:

**Navigating the Configuration Landscape**

Hmm, interesting problem. The first thing that pops into my head is always: is there a configuration option, or a set of configuration options, that addresses this directly? Before I start digging into the code, debugging, or considering workarounds, I need to know if the system *already* provides a mechanism for this. I'm thinking about the standard places: the main configuration files, environment variables, any dedicated settings GUIs… I need to check the documentation, of course, but I'm also going to recall any similar projects or systems I've worked on recently and see if they had a config for it. My instinct says I should start there, and if it doesn't then I will dig into more complex solutions.



28/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "config .*CGROUP.*",
  "PathPrefix": "init/Kconfig"
}

Results:
{
  "Output": "init/Kconfig=1041=config SLAB_OBJ_EXT\n--\ninit/Kconfig-1043-\ninit/Kconfig:1044:menuconfig CGROUPS\ninit/Kconfig-1045-\tbool \"Control Group support\"\n--\ninit/Kconfig=1060=config PAGE_COUNTER\n--\ninit/Kconfig-1062-\ninit/Kconfig:1063:config CGROUP_FAVOR_DYNMODS\ninit/Kconfig-1064-\tbool \"Favor dynamic modification latency reduction by default\"\n--\ninit/Kconfig=1096=config MEMCG_V1\n--\ninit/Kconfig-1112-\ninit/Kconfig:1113:config BLK_CGROUP\ninit/Kconfig-1114-\tbool \"IO controller\"\n--\ninit/Kconfig-1134-\ninit/Kconfig:1135:config CGROUP_WRITEBACK\ninit/Kconfig-1136-\tbool\n--\ninit/Kconfig-1139-\ninit/Kconfig:1140:menuconfig CGROUP_SCHED\ninit/Kconfig-1141-\tbool \"CPU controller\"\n--\ninit/Kconfig=1212=config UCLAMP_TASK_GROUP\n--\ninit/Kconfig-1233-\ninit/Kconfig:1234:config CGROUP_PIDS\ninit/Kconfig-1235-\tbool \"PIDs controller\"\n--\ninit/Kconfig-1249-\ninit/Kconfig:1250:config CGROUP_RDMA\ninit/Kconfig-1251-\tbool \"RDMA controller\"\n--\ninit/Kconfig-1259-\ninit/Kconfig:1260:config CGROUP_DMEM\ninit/Kconfig-1261-\tbool \"Device memory controller (DMEM)\"\n--\ninit/Kconfig-1269-\ninit/Kconfig:1270:config CGROUP_FREEZER\ninit/Kconfig-1271-\tbool \"Freezer controller\"\n--\ninit/Kconfig-1280-\ninit/Kconfig:1281:config CGROUP_HUGETLB\ninit/Kconfig-1282-\tbool \"HugeTLB controller\"\n--\ninit/Kconfig=1324=config PROC_PID_CPUSET\n--\ninit/Kconfig-1328-\ninit/Kconfig:1329:config CGROUP_DEVICE\ninit/Kconfig-1330-\tbool \"Device controller\"\n--\ninit/Kconfig-1334-\ninit/Kconfig:1335:config CGROUP_CPUACCT\ninit/Kconfig-1336-\tbool \"Simple CPU accounting controller\"\n--\ninit/Kconfig-1340-\ninit/Kconfig:1341:config CGROUP_PERF\ninit/Kconfig-1342-\tbool \"Perf controller\"\n--\ninit/Kconfig-1351-\ninit/Kconfig:1352:config CGROUP_BPF\ninit/Kconfig-1353-\tbool \"Support for eBPF programs attached to cgroups\"\n--\ninit/Kconfig-1364-\ninit/Kconfig:1365:config CGROUP_MISC\ninit/Kconfig-1366-\tbool \"Misc resource controller\"\n--\ninit/Kconfig-1378-\ninit/Kconfig:1379:config CGROUP_DEBUG\ninit/Kconfig-1380-\tbool \"Debug controller\"\n--\ninit/Kconfig-1390-\ninit/Kconfig:1391:config SOCK_CGROUP_DATA\ninit/Kconfig-1392-\tbool\n"
}

29/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 85445
							output: 44
							thoughts: 314
						

Thoughts:
**My Configuration Investigation**

Okay, so it looks like `CGROUPS` is the key configuration area I need to focus on. I need to summarize the configurations related to this, so I will list them: `SECURITY_LANDLOCK`, `NAMESPACES`, `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, `NET_NS`, `TIME_NS`, and `CGROUPS`.

Now, I need to figure out if `SECURITY_LANDLOCK` is enough, or if I also require `SECURITY`. I know `SECURITY` is usually enabled, but it's worth checking. The prompt specifically instructs me *not* to add a "CONFIG_" prefix to the output array. I must keep that in mind.

Next, I have to identify the significant symbols related to this. I've noted down these key functions: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, `sys_landlock_restrict_self`, `security_namespace_init`, and `security_namespace_install`.

Good, `security_namespace_init` and `security_namespace_install` look promising. What about `hook_capable`? That's a static function, so that's not usable. Okay, how about `security_capable`? It is non-static, but it looks like it's a hot path, so I need to weigh that. Is there anything else? Ah, here's one: `landlock_create_ruleset` (the internal version in `security/landlock/ruleset.c`) is non-static. Let's check `security/landlock/ruleset.c`.



30/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "^struct landlock_ruleset \\*landlock_create_ruleset",
  "PathPrefix": "security/landlock/ruleset.c"
}

Results:
{
  "Output": "security/landlock/ruleset.c=32=static struct landlock_ruleset *create_ruleset(const u32 num_layers)\n--\nsecurity/landlock/ruleset.c-56-\nsecurity/landlock/ruleset.c:57:struct landlock_ruleset *landlock_create_ruleset(\nsecurity/landlock/ruleset.c-58-\tconst access_mask_t fs_access_mask, const access_mask_t net_access_mask,\n"
}

31/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 86298
							output: 38
							thoughts: 543
						

Thoughts:
**Landlock Symbol Analysis**

Okay, so I'm digging into these Landlock syscalls and kernel functions. First, I confirmed that `landlock_create_ruleset` is, as expected, a non-static function. Now, I need to nail down how `landlock_add_rule` is defined. Ah, right, the syscall itself is `sys_landlock_add_rule`. There *isn't* a non-static function with that exact name, it's directly the syscall. And since it's defined using `SYSCALL_DEFINE4`, the actual symbol is either `__x64_sys_landlock_add_rule` or simply `sys_landlock_add_rule`. Given Syzkaller's preferences, `sys_landlock_add_rule` or `__x64_sys_landlock_add_rule` is the safer and more direct approach. Good, Syzkaller seems to understand `sys_landlock_add_rule` anyway.

I need to re-examine `security/landlock/ns.c` for any other potentially relevant non-static functions. Let's see... `landlock_add_ns_hooks` is marked as `__init`, so not what I'm looking for. Similarly, in `security/landlock/cap.c`, `landlock_add_cap_hooks` is `__init`.

Therefore, the key symbols I need to focus on for this investigation are:

*   `sys_landlock_create_ruleset`
*   `sys_landlock_add_rule`
*   `sys_landlock_restrict_self`
*   `security_namespace_init`
*   `security_namespace_install`

Wait, hang on, `security_namespace_init` and `security_namespace_install` aren't in `landlock`, they're in `security/security.c`. Let's think, are these in the hot paths? `security_namespace_init` is called during namespace creation (like with `unshare`, `clone`, etc.) – that's not a performance-critical path. And `security_namespace_install` is called during namespace installation (`setns`). Also not a hot path.

Now, let's circle back to `security/landlock/ruleset.c` to see if any modifications occurred there. Let's run through these:
`merge_ruleset` is static.
`inherit_ruleset` is also static.
`landlock_merge_ruleset` is non-static - that might be useful.
`landlock_union_access_masks` is static inline.
`landlock_add_perm_mask` is static inline.

Finally, I need to check `security/landlock/cred.c`.



32/2 2026/07/27 06:26 tool grepper
0m Args:
{
  "Expression": "^[a-zA-Z_].*\\(",
  "PathPrefix": "security/landlock/cred.c"
}

Results:
{
  "Output": "security/landlock/cred.c-18-\nsecurity/landlock/cred.c:19:static void hook_cred_transfer(struct cred *const new,\nsecurity/landlock/cred.c-20-\t\t\t       const struct cred *const old)\n--\nsecurity/landlock/cred.c-28-\nsecurity/landlock/cred.c:29:static int hook_cred_prepare(struct cred *const new,\nsecurity/landlock/cred.c-30-\t\t\t     const struct cred *const old, const gfp_t gfp)\n--\nsecurity/landlock/cred.c-35-\nsecurity/landlock/cred.c:36:static void hook_cred_free(struct cred *const cred)\nsecurity/landlock/cred.c-37-{\n--\nsecurity/landlock/cred.c-45-\nsecurity/landlock/cred.c:46:static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)\nsecurity/landlock/cred.c-47-{\n--\nsecurity/landlock/cred.c=55=static struct security_hook_list landlock_hooks[] __ro_after_init = {\n--\nsecurity/landlock/cred.c-64-\nsecurity/landlock/cred.c:65:__init void landlock_add_cred_hooks(void)\nsecurity/landlock/cred.c-66-{\n"
}

33/2 2026/07/27 06:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 87850
							output: 37
							thoughts: 39
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Cred.c Review and Landlock Interaction**

Alright, so the `cred.c` module has been reviewed, and it looks like we're not introducing any new non-static functions within it, which is good. That likely means we're not adding any direct new entry points or exposed functionality that could have security implications. Now, the next logical step is to dive into how this relates to Landlock. We need to check the definition of `landlock_perm_is_denied`. It's housed in `security/landlock/cred.h`. I need to ensure there are no subtle interactions there or, even better, understand if any changes I'm making in `cred.c` require corresponding adjustments to access controls via Landlock. I want to make sure the Landlock permission check isn't becoming a bottleneck or causing any unexpected behavior.



34/2 2026/07/27 06:27 tool grepper
0m Args:
{
  "Expression": "landlock_perm_is_denied",
  "PathPrefix": "security/landlock/cred.h"
}

Results:
{
  "Output": "security/landlock/cred.h=122=landlock_get_applicable_subject(const struct cred *const cred,\n--\nsecurity/landlock/cred.h-156-/**\nsecurity/landlock/cred.h:157: * landlock_perm_is_denied - Check if a permission bitmask request is denied\nsecurity/landlock/cred.h-158- *\n--\nsecurity/landlock/cred.h=172=static inline size_t\nsecurity/landlock/cred.h:173:landlock_perm_is_denied(const struct landlock_ruleset *const domain,\nsecurity/landlock/cred.h-174-\t\t\tconst access_mask_t perm_bit, const u64 request_value)\n"
}

35/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 88322
							output: 36
							thoughts: 172
						

Thoughts:
**Analyzing Landlock Functionality for Fuzzing**

Okay, so I'm looking at this code and it's dealing with a static inline function, right? That means I need to pay attention to the symbols it's directly using. From the looks of it, the key ones I need to zero in on are: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, `sys_landlock_restrict_self`, `security_namespace_init`, and `security_namespace_install`.

Now, wait a minute... `sys_landlock_create_ruleset` and `sys_landlock_add_rule` are *syscalls*. Excellent! Syzkaller is designed to automatically fuzz syscalls, but explicitly calling them out in `FocusSymbols` will force the fuzzer to prioritize these specific syscalls, which is exactly what I want. Ensures they get plenty of attention.

I wonder, though, are there any other related functions I should be including to give Syzkaller as many leads as possible? Ah, yes: `landlock_create_ruleset` and `landlock_merge_ruleset`. I'm going to add these as well to improve fuzzing capabilities. I should double check `security/landlock/syscalls.c` again just to be sure I haven't missed anything.



36/2 2026/07/27 06:27 tool grepper
0m Args:
{
  "Expression": "^static int add_rule_",
  "PathPrefix": "security/landlock/syscalls.c"
}

Results:
{
  "Output": "security/landlock/syscalls.c=347=static int get_path_from_fd(const s32 fd, struct path *const path)\n--\nsecurity/landlock/syscalls.c-371-\nsecurity/landlock/syscalls.c:372:static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,\nsecurity/landlock/syscalls.c-373-\t\t\t\t const void __user *const rule_attr, u32 flags)\n--\nsecurity/landlock/syscalls.c-414-\nsecurity/landlock/syscalls.c:415:static int add_rule_net_port(struct landlock_ruleset *ruleset,\nsecurity/landlock/syscalls.c-416-\t\t\t     const void __user *const rule_attr, u32 flags)\n--\nsecurity/landlock/syscalls.c-452-\nsecurity/landlock/syscalls.c:453:static int add_rule_namespace(struct landlock_ruleset *const ruleset,\nsecurity/landlock/syscalls.c-454-\t\t\t      const void __user *const rule_attr,\n--\nsecurity/landlock/syscalls.c-516-\nsecurity/landlock/syscalls.c:517:static int add_rule_capability(struct landlock_ruleset *const ruleset,\nsecurity/landlock/syscalls.c-518-\t\t\t       const void __user *const rule_attr,\n"
}

37/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 89195
							output: 38
							thoughts: 19
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Diving into `security/landlock/audit.c`**

Alright, these thoughts are pretty straightforward – no dynamic context or prior analysis to consider. Seems like we're heading directly into the audit functionality within Landlock. The intention is clear: let's jump into `security/landlock/audit.c`. This is where I'll expect to find the logging mechanisms, permission checks that generate audit events, and likely the interface for how these events are stored or transmitted. I anticipate a fair bit of code dedicated to formatting audit messages and potentially interacting with the kernel's audit subsystem or some custom logging solution. Let's see what interesting techniques they've employed here.



38/2 2026/07/27 06:27 tool grepper
0m Args:
{
  "Expression": "^[a-zA-Z_].*\\(",
  "PathPrefix": "security/landlock/audit.c"
}

Results:
{
  "Output": "security/landlock/audit.c=25=static const char *const fs_access_strings[] = {\n--\nsecurity/landlock/audit.c-44-\nsecurity/landlock/audit.c:45:static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);\nsecurity/landlock/audit.c-46-\nsecurity/landlock/audit.c=47=static const char *const net_access_strings[] = {\n--\nsecurity/landlock/audit.c-54-\nsecurity/landlock/audit.c:55:static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);\nsecurity/landlock/audit.c-56-\nsecurity/landlock/audit.c=57=static __attribute_const__ const char *\nsecurity/landlock/audit.c:58:get_blocker(const enum landlock_request_type type,\nsecurity/landlock/audit.c-59-\t    const unsigned long access_bit)\n--\nsecurity/landlock/audit.c-100-\nsecurity/landlock/audit.c:101:static void log_blockers(struct audit_buffer *const ab,\nsecurity/landlock/audit.c-102-\t\t\t const enum landlock_request_type type,\n--\nsecurity/landlock/audit.c-117-\nsecurity/landlock/audit.c:118:static void log_domain(struct landlock_hierarchy *const hierarchy)\nsecurity/landlock/audit.c-119-{\n--\nsecurity/landlock/audit.c=150=static struct landlock_hierarchy *\nsecurity/landlock/audit.c:151:get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)\nsecurity/landlock/audit.c-152-{\n--\nsecurity/landlock/audit.c-170-\nsecurity/landlock/audit.c:171:static void test_get_hierarchy(struct kunit *const test)\nsecurity/landlock/audit.c-172-{\n--\nsecurity/landlock/audit.c-197-/* Get the youngest layer that denied the access_request. */\nsecurity/landlock/audit.c:198:static size_t get_denied_layer(const struct landlock_ruleset *const domain,\nsecurity/landlock/audit.c-199-\t\t\t       access_mask_t *const access_request,\n--\nsecurity/landlock/audit.c-215-\nsecurity/landlock/audit.c:216:static void test_get_denied_layer(struct kunit *const test)\nsecurity/landlock/audit.c-217-{\n--\nsecurity/landlock/audit.c=259=static size_t\nsecurity/landlock/audit.c:260:get_layer_from_deny_masks(access_mask_t *const access_request,\nsecurity/landlock/audit.c-261-\t\t\t  const access_mask_t all_existing_optional_access,\n--\nsecurity/landlock/audit.c-313-\nsecurity/landlock/audit.c:314:static void test_get_layer_from_deny_masks(struct kunit *const test)\nsecurity/landlock/audit.c-315-{\n--\nsecurity/landlock/audit.c-503-\nsecurity/landlock/audit.c:504:static bool is_valid_request(const struct landlock_request *const request)\nsecurity/landlock/audit.c-505-{\n--\nsecurity/landlock/audit.c=537=static access_mask_t\nsecurity/landlock/audit.c:538:pick_access_mask_for_request_type(const enum landlock_request_type type,\nsecurity/landlock/audit.c-539-\t\t\t\t  const struct access_masks access_masks)\n--\nsecurity/landlock/audit.c-558- */\nsecurity/landlock/audit.c:559:void landlock_log_denial(const struct landlock_cred_security *const subject,\nsecurity/landlock/audit.c-560-\t\t\t const struct landlock_request *const request)\n--\nsecurity/landlock/audit.c-714- */\nsecurity/landlock/audit.c:715:void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)\nsecurity/landlock/audit.c-716-{\n--\nsecurity/landlock/audit.c=754=static struct kunit_suite test_suite = {\n--\nsecurity/landlock/audit.c-758-\nsecurity/landlock/audit.c:759:kunit_test_suite(test_suite);\nsecurity/landlock/audit.c-760-\n"
}

39/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 90615
							output: 38
							thoughts: 29
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Navigating the Landlock Denial Logic**

Right, `landlock_log_denial`... that's not static, which means it has broader scope than a simple module-internal function. Okay, that's useful context. Now, where is it defined and used? Time to dive into the codebase. Let's start with `security/landlock/setup.c`. That seems like the logical first place to check the setup and initialization of the landlock security module. Hopefully, its definition and usage will provide the clues I need to understand its behavior and why it's not declared as static. Let's see what we can find.



40/2 2026/07/27 06:27 tool grepper
0m Args:
{
  "Expression": "^[a-zA-Z_].*\\(",
  "PathPrefix": "security/landlock/setup.c"
}

Results:
{
  "Output": "security/landlock/setup.c=39=int landlock_errata __ro_after_init;\nsecurity/landlock/setup.c-40-\nsecurity/landlock/setup.c:41:static void __init compute_errata(void)\nsecurity/landlock/setup.c-42-{\n--\nsecurity/landlock/setup.c-65-\nsecurity/landlock/setup.c:66:static int __init landlock_init(void)\nsecurity/landlock/setup.c-67-{\n--\nsecurity/landlock/setup.c-80-\nsecurity/landlock/setup.c:81:DEFINE_LSM(LANDLOCK_NAME) = {\nsecurity/landlock/setup.c-82-\t.id = \u0026landlock_lsmid,\n"
}

41/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 91048
							output: 26
							thoughts: 262
						

Thoughts:
**Landlock Focus: Syscalls, Configurations, and Dependencies**

Okay, so I'm looking at these Landlock-related symbols: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, `sys_landlock_restrict_self`, and then the namespace-related ones: `security_namespace_init`, `security_namespace_install`, along with the core Landlock functions `landlock_create_ruleset` and `landlock_merge_ruleset`. Immediately, `sys_landlock_create_ruleset` jumps out – that's a *syscall*, which is the entry point. I need to make sure I understand the relationship between that and the other functions, are there any other functions to worry about here? I also see `landlock_perm_is_denied` is inline static, good to know. And `landlock_log_denial` is non-static, alright.

Time to revisit the configuration options. Right, we have `SECURITY_LANDLOCK`, then the namespace options like `NAMESPACES`, `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, `NET_NS`, and `TIME_NS`. Also, `CGROUPS`. Now, a quick check...is `CGROUPS` sufficient, or do we need `CGROUP_NS`? Wait a minute, in `init/Kconfig` I don't see `CGROUP_NS`. I need to look into `kernel/cgroup/Kconfig` or somewhere similar to clarify that dependency.



42/2 2026/07/27 06:27 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config CGROUP_NS",
  "PathPrefix": ""
}

Results:
{
  "error": "no matches"
}

43/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 91690
							output: 29
							thoughts: 33
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Investigating `CLONE_NEWCGROUP`**

Alright, let's see... I'm looking at this `CLONE_NEWCGROUP` flag. Right, so it's defined in `include/uapi/linux/sched.h`. Standard include path, that's good. But wait, before I dive into the code that's *using* it, I need to know - is this gated behind a config option? I need to quickly determine whether the presence or absence of that flag is a compile-time decision controlled by some `CONFIG_*` setting. It's crucial for understanding portability and how widely this feature can be used in different kernel builds. I'll need to check the relevant Kconfig files in order to understand how to best incorporate this in my project. I need to figure out the dependencies. Knowing the underlying configuration options will inform how I need to handle it in my project's build process, as well as its availability in various environments I'm targeting. No point building features around something if the flag isn't even supported! Gotta make sure everything is rock solid before going further down the rabbit hole.



44/2 2026/07/27 06:27 tool grepper
0m Args:
{
  "Expression": "CLONE_NEWCGROUP",
  "PathPrefix": "kernel/"
}

Results:
{
  "Output": "kernel/cgroup/cgroup.c=6960=void cgroup_post_fork(struct task_struct *child,\n--\nkernel/cgroup/cgroup.c-7033-\t/* Make the new cset the root_cset of the new cgroup namespace. */\nkernel/cgroup/cgroup.c:7034:\tif (kargs-\u003eflags \u0026 CLONE_NEWCGROUP) {\nkernel/cgroup/cgroup.c-7035-\t\tstruct css_set *rcset = child-\u003ensproxy-\u003ecgroup_ns-\u003eroot_cset;\n--\nkernel/cgroup/namespace.c=48=struct cgroup_namespace *copy_cgroup_ns(u64 flags,\n--\nkernel/cgroup/namespace.c-57-\nkernel/cgroup/namespace.c:58:\tif (!(flags \u0026 CLONE_NEWCGROUP)) {\nkernel/cgroup/namespace.c-59-\t\tget_cgroup_ns(old_ns);\n--\nkernel/nscommon.c=12=static void ns_debug(struct ns_common *ns, const struct proc_ns_operations *ops)\n--\nkernel/nscommon.c-15-#ifdef CONFIG_CGROUPS\nkernel/nscommon.c:16:\tcase CLONE_NEWCGROUP:\nkernel/nscommon.c-17-\t\tVFS_WARN_ON_ONCE(ops != \u0026cgroupns_operations);\n--\nkernel/nsproxy.c=301=static int check_setns_flags(unsigned long flags)\n--\nkernel/nsproxy.c-322-#ifndef CONFIG_CGROUPS\nkernel/nsproxy.c:323:\tif (flags \u0026 CLONE_NEWCGROUP)\nkernel/nsproxy.c-324-\t\treturn -EINVAL;\n--\nkernel/nsproxy.c=404=static int validate_nsset(struct nsset *nsset, struct pid *pid)\n--\nkernel/nsproxy.c-498-#ifdef CONFIG_CGROUPS\nkernel/nsproxy.c:499:\tif (flags \u0026 CLONE_NEWCGROUP) {\nkernel/nsproxy.c-500-\t\tret = validate_ns(nsset, \u0026nsp-\u003ecgroup_ns-\u003ens);\n--\nkernel/nstree.c=286=static struct ns_tree_root *ns_tree_from_type(int ns_type)\n--\nkernel/nstree.c-288-\tswitch (ns_type) {\nkernel/nstree.c:289:\tcase CLONE_NEWCGROUP:\nkernel/nstree.c-290-\t\treturn \u0026cgroup_ns_tree;\n"
}

45/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 92683
							output: 24
							thoughts: 140
						

Thoughts:
**Analyzing the Landlock Security Context**

Okay, so this revolves around `CONFIG_CGROUPS` – that's the starting point. I see the relevant configurations being scrutinized are `SECURITY_LANDLOCK`, `NAMESPACES`, `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, `NET_NS`, `TIME_NS`, and `CGROUPS`. Seems like a fairly standard, comprehensive list for a modern security context.

Now, let's dig a bit deeper. The patch touches `security/landlock/cap.c`. I know that uses capabilities like `CAP_SYS_ADMIN`, `CAP_SYS_CHROOT`, and the like. It's almost guaranteed these are enabled on any contemporary Linux system, but given the granularity of this investigation, it's worth a double-check. I'll need to confirm if there are any specific configurations governing their behavior within this particular security domain. Just making sure no stone is left unturned.



46/2 2026/07/27 06:27 tool grepper
0m Args:
{
  "Expression": "config .*CAPABILITIES",
  "PathPrefix": ""
}

Results:
{
  "Output": "drivers/mmc/host/sdhci-msm.c=2644=static int sdhci_msm_probe(struct platform_device *pdev)\n--\ndrivers/mmc/host/sdhci-msm.c-2820-\tif (core_major \u003e= 1 \u0026\u0026 core_minor != 0x11 \u0026\u0026 core_minor != 0x12) {\ndrivers/mmc/host/sdhci-msm.c:2821:\t\tconfig = readl_relaxed(host-\u003eioaddr + SDHCI_CAPABILITIES);\ndrivers/mmc/host/sdhci-msm.c-2822-\t\tconfig |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT;\n"
}

47/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 93253
							output: 27
							thoughts: 878
						

Thoughts:
**Landlock LSM Feature Focus**

Okay, so I'm honing in on this new Landlock LSM feature.  Capabilities are always enabled, that's a given. Now, let's nail down the key configuration element: `SECURITY_LANDLOCK`.

I need to identify the core symbols I should focus the fuzzer on.  Let's see: I've got `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, `sys_landlock_restrict_self`, which are the syscalls. Then I see `security_namespace_init` and `security_namespace_install`, along with `landlock_create_ruleset` and `landlock_merge_ruleset`.

Quick sanity check: `sys_landlock_create_ruleset` is a syscall, so the kernel likely uses the `__x64_sys_landlock_create_ruleset` or just `sys_landlock_create_ruleset`. Syzkaller's using `sys_landlock_create_ruleset`, so let's stick with that for consistency across the syscalls.

I'm confirming these are all externally visible symbols.  `landlock_create_ruleset` and `landlock_merge_ruleset` are global in `security/landlock/ruleset.c`, and the `security_namespace` functions are global in `security/security.c`. The `hook_*` functions seem to be static within their respective files, and thus not directly relevant for the fuzzer.  `landlock_add_cap_hooks` and `landlock_add_ns_hooks` are `__init`, so they are fine to ignore.

So, the definitive list for fuzzing is:
*   `sys_landlock_create_ruleset`
*   `sys_landlock_add_rule`
*   `sys_landlock_restrict_self`
*   `security_namespace_init`
*   `security_namespace_install`

That's the core.  I believe this will allow the fuzzer to explore these new code paths effectively.

Let's quickly confirm that `sys_landlock_create_ruleset` is indeed the right name. Yup, `SYSCALL_DEFINE3(landlock_create_ruleset, ...)` in `security/landlock/syscalls.c` confirms it.

Now, a quick recap of the patch's impact: It's adding new Landlock LSM features that restrict both capabilities and namespaces. This is a big deal. It introduces new rule types (`LANDLOCK_RULE_CAPABILITY`, `LANDLOCK_RULE_NAMESPACE`) and permission flags (`LANDLOCK_PERM_CAPABILITY_USE`, `LANDLOCK_PERM_NAMESPACE_USE`). The changes include modifications to the `landlock_create_ruleset` and `landlock_add_rule` syscalls.  Plus, there are now new LSM hooks for `capable`, `namespace_init`, and `namespace_install`. This is a significant functional change, and clearly demands fuzzing.

Alright, let's confirm the full configuration picture. I know `SECURITY_LANDLOCK` is essential.  To properly test namespace restrictions, I will need `NAMESPACES`, `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, `NET_NS`, `TIME_NS`, and `CGROUPS`. Given that `SECURITY_LANDLOCK` *selects* `SECURITY_NETWORK`, that will be automatically configured. I will need `SECURITY` since `SECURITY_LANDLOCK` depends on it. I also need `AUDIT`, because the patch enables auditing, which needs to be tested for the new restrictions.



48/2 2026/07/27 06:27 tool grepper
0m Args:
{
  "Expression": "config AUDIT",
  "PathPrefix": "init/Kconfig"
}

Results:
{
  "Output": "init/Kconfig=526=config CROSS_MEMORY_ATTACH\n--\ninit/Kconfig-535-\ninit/Kconfig:536:config AUDIT\ninit/Kconfig-537-\tbool \"Auditing support\"\n--\ninit/Kconfig=545=config HAVE_ARCH_AUDITSYSCALL\n--\ninit/Kconfig-547-\ninit/Kconfig:548:config AUDITSYSCALL\ninit/Kconfig-549-\tdef_bool y\n"
}

49/2 2026/07/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 95100
							output: 269
							thoughts: 371
						

Thoughts:
**My Analysis of the Kernel Configuration for Landlock LSM Feature Integration**

Okay, so I'm looking at this patch and, yes, `AUDIT` is definitely a configuration option we're dealing with here. My understanding is that the configurations impacted by this patch are: `SECURITY_LANDLOCK`, `NAMESPACES`, `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, `NET_NS`, `TIME_NS`, `CGROUPS`, and of course, `AUDIT` itself.

Now, I need to check if `SECURITY` in general is required for this to function correctly. Yep, looks like it.

Therefore, the complete list of configurations that are now essential are: `SECURITY_LANDLOCK`, `NAMESPACES`, `USER_NS`, `UTS_NS`, `IPC_NS`, `PID_NS`, `NET_NS`, `TIME_NS`, `CGROUPS`, `AUDIT`, and `SECURITY`.

I'm keying in on a few crucial symbols here: `sys_landlock_create_ruleset`, `sys_landlock_add_rule`, `sys_landlock_restrict_self`, `security_namespace_init`, and `security_namespace_install`. Those will be key to understanding the implementation.

The patch itself is adding Landlock LSM features to improve security by restricting capabilities and namespaces. It's adding new rule types, specifically `LANDLOCK_RULE_CAPABILITY` and `LANDLOCK_RULE_NAMESPACE`, alongside new permission flags: `LANDLOCK_PERM_CAPABILITY_USE` and `LANDLOCK_PERM_NAMESPACE_USE`. The change involves modifying the `landlock_create_ruleset` and `landlock_add_rule` syscalls to incorporate these new rules and permissions. Furthermore, it's introducing new LSM hooks for `capable`, `namespace_init`, and `namespace_install` to actually *enforce* these restrictions. This is a pretty significant functional change to the core kernel.

Given its core nature and the functional impact, this definitely needs fuzzing.

Alright, time to log these findings with `set-results`.



50/2 2026/07/27 06:27 tool set-results
0m Args:
{
  "EnableConfigs": [
    "SECURITY_LANDLOCK",
    "NAMESPACES",
    "USER_NS",
    "UTS_NS",
    "IPC_NS",
    "PID_NS",
    "NET_NS",
    "TIME_NS",
    "CGROUPS",
    "AUDIT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "sys_landlock_create_ruleset",
    "sys_landlock_add_rule",
    "sys_landlock_restrict_self",
    "security_namespace_init",
    "security_namespace_install"
  ],
  "Reasoning": "The patch adds new Landlock LSM features to restrict capabilities and namespaces. It introduces new rule types (LANDLOCK_RULE_CAPABILITY, LANDLOCK_RULE_NAMESPACE) and new permission flags (LANDLOCK_PERM_CAPABILITY_USE, LANDLOCK_PERM_NAMESPACE_USE). It modifies the landlock_create_ruleset and landlock_add_rule syscalls to handle these new rules and permissions. It also adds new LSM hooks for capable, namespace_init, and namespace_install to enforce these restrictions. This is a functional change to the core kernel and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "SECURITY_LANDLOCK",
    "NAMESPACES",
    "USER_NS",
    "UTS_NS",
    "IPC_NS",
    "PID_NS",
    "NET_NS",
    "TIME_NS",
    "CGROUPS",
    "AUDIT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "sys_landlock_create_ruleset",
    "sys_landlock_add_rule",
    "sys_landlock_restrict_self",
    "security_namespace_init",
    "security_namespace_install"
  ],
  "Reasoning": "The patch adds new Landlock LSM features to restrict capabilities and namespaces. It introduces new rule types (LANDLOCK_RULE_CAPABILITY, LANDLOCK_RULE_NAMESPACE) and new permission flags (LANDLOCK_PERM_CAPABILITY_USE, LANDLOCK_PERM_NAMESPACE_USE). It modifies the landlock_create_ruleset and landlock_add_rule syscalls to handle these new rules and permissions. It also adds new LSM hooks for capable, namespace_init, and namespace_install to enforce these restrictions. This is a functional change to the core kernel and should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)