Landlock update for v7.4-rc1

-----BEGIN PGP SIGNATURE-----
 
 iIYEABYKAC4WIQSVyBthFV4iTW/VU1/l49DojIL20gUCatjkPhAcbWljQGRpZ2lr
 b2QubmV0AAoJEOXj0OiMgvbS45sBAPNe/CnhCS8x7P8LeZSH80rAs5mg7pmM5AWx
 dKr/n38OAP4l+qqQ4nIZUJqx0qfCuo7hzwX1xnKn+TUdUsNNS/9uBw==
 =EVuG
 -----END PGP SIGNATURE-----

Merge tag 'landlock-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux

Pull Landlock update from Mickaël Salaün:
 "The main change adds composable Landlock controls for namespace types
  and Linux capabilities, thanks to new LSM hooks (merged via the LSM
  tree). A umount race condition is also fixed"

* tag 'landlock-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux:
  landlock: Fix superblock use-after-free in release_inode()
  selftests/landlock: Fix snprintf truncation checks in test files
  landlock: Add documentation for capability and namespace restrictions
  samples/landlock: Add capability and namespace restriction support
  selftests/landlock: Add capability restriction tests
  selftests/landlock: Add namespace restriction tests
  landlock: Enforce capability restrictions
  landlock: Enforce namespace use restrictions
  landlock: Wrap per-layer access masks in struct layer_config
  landlock: Rename quiet_masks to quiet_access
This commit is contained in:
Linus Torvalds 2026-10-22 09:58:44 -10:00
commit 3815f7d55a
42 changed files with 6279 additions and 187 deletions

View File

@ -7,7 +7,7 @@ Landlock: system-wide management
================================
:Author: Mickaël Salaün
:Date: August 2026
:Date: October 2026
Landlock can leverage the audit framework to log events.
@ -20,10 +20,12 @@ Audit
Denied access requests are logged by default for a sandboxed program if `audit`
is enabled. This default behavior can be changed with the
sys_landlock_restrict_self() flags (cf.
Documentation/userspace-api/landlock.rst), or suppressed on a per-object
basis by using ``LANDLOCK_ADD_RULE_QUIET`` (ABI 10+). Landlock logs can
also be masked thanks to audit rules. Landlock can generate 2 audit
record types.
Documentation/userspace-api/landlock.rst), suppressed on a per-object
basis by using ``LANDLOCK_ADD_RULE_QUIET`` (ABI 10+), or suppressed for
specific capability or namespace members with the corresponding permission
rule's ``quiet_*`` field (ABI 12+). Quiet rules only suppress audit submission;
they do not suppress denial trace events. Landlock logs can also be masked
thanks to audit rules. Landlock can generate two audit record types.
Record types
------------
@ -65,14 +67,28 @@ AUDIT_LANDLOCK_ACCESS
- scope.abstract_unix_socket - Abstract UNIX socket connection denied
- scope.signal - Signal sending denied
**namespace.*** - Namespace restrictions (ABI 12+):
- namespace.use - Namespace use was denied (creation via
:manpage:`unshare(2)`, :manpage:`clone(2)`, :manpage:`clone3(2)`,
:manpage:`open_tree(2)`, or :manpage:`fsmount(2)`, or joining via
:manpage:`setns(2)`);
``namespace_type`` indicates the type (hex ``CLONE_NEW*`` bitmask),
and ``namespace_id`` identifies the target namespace for
:manpage:`setns(2)` operations (zero for creation)
**capability.*** - Capability restrictions (ABI 12+):
- 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
@ -178,8 +194,9 @@ If you get spammed with audit logs related to Landlock, this is either an
attack attempt or a bug in the security policy. We can put in place some
filters to limit noise with two complementary ways:
- with sys_landlock_restrict_self()'s flags, or
``LANDLOCK_ADD_RULE_QUIET`` (ABI 10+) if we can fix the sandboxed
- with sys_landlock_restrict_self()'s flags,
``LANDLOCK_ADD_RULE_QUIET`` (ABI 10+), or the permission rules'
per-member ``quiet_*`` fields (ABI 12+) if we can fix the sandboxed
programs,
- or with audit rules (see :manpage:`auditctl(8)`).
@ -243,8 +260,10 @@ with these exceptions:
- **NOAUDIT hooks**: Some LSM hooks suppress logging for speculative
permission probes (e.g., reading ``/proc/<pid>/status`` uses
``PTRACE_MODE_NOAUDIT``). When NOAUDIT is set, neither audit records
nor trace events are emitted, and the denial is not counted in
``PTRACE_MODE_NOAUDIT``, and :manpage:`listxattr(2)` probes
``CAP_SYS_ADMIN`` with ``CAP_OPT_NOAUDIT`` to decide whether to list
``trusted.*`` extended attributes). When NOAUDIT is set, neither audit
records nor trace events are emitted, and the denial is not counted in
``denials``. The denial is still enforced. This avoids performance
overhead and noise from speculative probes that test permissions
without performing an actual access.
@ -269,7 +288,8 @@ Observability security considerations
Both audit records and trace events expose information about all
Landlock-sandboxed processes on the system, including filesystem paths
being accessed, network ports, and process identities. System
being accessed, network ports, capability numbers, namespace types and IDs,
and process identities. System
administrators must ensure that access to audit logs (controlled by the
audit subsystem configuration) and to trace events (requiring
``CAP_SYS_ADMIN`` or ``CAP_BPF`` + ``CAP_PERFMON``) is restricted to

View File

@ -8,7 +8,7 @@ Landlock LSM: kernel documentation
==================================
:Author: Mickaël Salaün
:Date: August 2026
:Date: October 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,
@ -130,6 +130,166 @@ 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_permissions``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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_PERMISSION_*`` 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 or its trace event, which reports ``logged=0``. Suppression 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_PERMISSION_*`` flags in
``handled_permissions`` 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_permissions``). 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_permissions``
and add rules for each; the kernel enforces each dimension at its own LSM
hook. ``LANDLOCK_PERMISSION_NAMESPACE_USE`` and
``LANDLOCK_PERMISSION_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
=====
@ -151,6 +311,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
------------------
@ -198,7 +370,8 @@ whether or not the kernel is built with audit support, so a
tracepoints-only build reports the selection audit would make. A quiet
rule (``LANDLOCK_ADD_RULE_QUIET`` with the access in the ``quiet_*``
fields of ``struct landlock_ruleset_attr``) suppresses logging by
setting ``logged=0`` the same way.
setting ``logged=0`` the same way, as do the per-member ``quiet_*``
masks of capability and namespace rules.
See Documentation/admin-guide/LSM/landlock.rst for audit record format,
tracepoint usage, and filtering examples.

View File

@ -6,7 +6,7 @@ Landlock Trace Events
=====================
:Author: Mickaël Salaün
:Date: September 2026
:Date: October 2026
Landlock emits trace events for sandbox lifecycle operations and access
denials. These events can be consumed by ftrace (for human-readable
@ -35,6 +35,8 @@ Landlock trace events are organized in four categories:
- ``landlock_add_rule_net_port``: a network port rule is added to a ruleset
- ``landlock_create_domain``: a new domain is created from a ruleset
- ``landlock_enforce_domain``: a domain is enforced on a thread
- ``landlock_add_rule_namespace``: a namespace rule is added to a ruleset
- ``landlock_add_rule_capability``: a capability rule is added to a ruleset
**Denial events** are emitted when an access is denied:
@ -44,6 +46,8 @@ Landlock trace events are organized in four categories:
- ``landlock_deny_scope_signal``: signal delivery denied
- ``landlock_deny_scope_abstract_unix_socket``: abstract unix socket
access denied
- ``landlock_deny_permission_namespace``: namespace use denied
- ``landlock_deny_permission_capability``: capability use denied
**Rule evaluation events** are emitted during rule matching:
@ -84,7 +88,7 @@ Landlock, not from regular file permissions::
$ LC_ALL=C LL_FS_RO=/usr:/lib:/lib64:/bin:/etc/ld.so.cache LL_FS_RW=/tmp \
./sandboxer cat /etc/passwd
$ cat /sys/kernel/tracing/trace_pipe
cat-127 [...] landlock_create_ruleset: ruleset=195cc6b76.0 handled_fs=execute|write_file|read_file|read_dir|remove_dir|remove_file|make_char|make_dir|make_reg|make_sock|make_fifo|make_block|make_sym|refer|truncate|ioctl_dev|resolve_unix handled_net= scoped=
cat-127 [...] landlock_create_ruleset: ruleset=195cc6b76.0 handled_fs=execute|write_file|read_file|read_dir|remove_dir|remove_file|make_char|make_dir|make_reg|make_sock|make_fifo|make_block|make_sym|refer|truncate|ioctl_dev|resolve_unix handled_net= scoped= handled_permissions=
cat-127 [...] landlock_create_domain: domain=195cc6b7c parent=0 ruleset=195cc6b76.6
cat-127 [...] landlock_enforce_domain: domain=195cc6b7c complete=1 process_wide=1 no_new_privs=1
cat-127 [...] landlock_deny_access_fs: domain=195cc6b7c same_exec=0 logged=0 blockers=read_file dev=0:17 ino=5901179 path=/etc/passwd
@ -119,10 +123,13 @@ in some field formats:
Audit uses string ``dev="<s_id>"``. Numeric format is more precise
for machine parsing.
- **Denied access field**: The ``deny_access_fs`` and ``deny_access_net``
tracepoints use the ``blockers=`` field name (same as audit). Both
render the blocked access rights as names: audit prefixes the category
and separates with commas (e.g., ``blockers=fs.read_file``), while the
- **Blocker field**: Denial tracepoints that carry a blocker use the
``blockers=`` field name (same as audit). The ``deny_access_fs`` and
``deny_access_net`` tracepoints render the blocked access rights, the
``deny_permission_namespace`` and ``deny_permission_capability``
tracepoints render the blocked permission, and a ``change_topology``
denial renders its request type. Audit prefixes the category and
separates with commas (e.g., ``blockers=fs.read_file``), while the
tracepoints omit the category (carried by the event name) and separate
with ``|`` (e.g., ``blockers=read_file``). Scope and ptrace
tracepoints omit ``blockers`` because the event name identifies the
@ -160,10 +167,34 @@ Ruleset versioning
==================
Syscall events include a ruleset version (``ruleset=<hex_id>.<version>``)
that tracks the number of rules added to the ruleset. The version is
incremented on each ``landlock_add_rule()`` call and frozen at
``landlock_restrict_self()`` time. This enables trace consumers to
correlate a domain with the exact set of rules it was created from.
that tracks the number of successful add-rule calls on the ruleset. The
version is incremented on each successful ``landlock_add_rule()`` call and
frozen at ``landlock_restrict_self()`` time. Namespace and capability calls
increment it even when their effective known masks are empty or already
present, so the event stream preserves every successful call. This enables
trace consumers to correlate a domain with the exact rule history from which
it was created.
Permission rule and denial events
=================================
``landlock_create_ruleset`` reports handled permissions as symbolic names in
``handled_permissions=``. ``landlock_add_rule_namespace`` and
``landlock_add_rule_capability`` report ``permissions=`` plus the effective
known allowed and quiet member masks. Capability masks and the raw
``CLONE_NEW*`` namespace masks are printed in hexadecimal. Unknown-only input
therefore appears as zero while still advancing the ruleset version, and adding
a member already present still emits an event.
``landlock_deny_permission_namespace`` reports the denied raw
``CLONE_NEW*`` value. ``namespace_id`` is zero for namespace creation and is
the exact target namespace ID for :manpage:`setns(2)`.
``landlock_deny_permission_capability`` reports the denied ``CAP_*`` number.
Like every denial event, both include the denying domain, ``same_exec``, and
``logged``.
Per-member quiet masks suppress audit submission but never these trace events,
which report ``logged=0``.
Domain enforcement
==================
@ -290,8 +321,8 @@ per-domain state in BPF maps:
``domain=`` key (join to the ``create_domain`` recorded in step 1),
building the per-domain thread set; filter ``complete==1`` for a
one-event-per-operation summary.
3. On ``landlock_deny_access_*``: look up the domain, decide whether
to count, alert, or ignore the denial based on custom policy.
3. On ``landlock_deny_*``: look up the domain, decide whether to count,
alert, or ignore the denial based on custom policy.
4. On ``landlock_free_domain``: clean up the per-domain state, log
final statistics.
@ -304,11 +335,12 @@ reconcile incomplete state.
Audit filtering equivalence
===========================
The ``logged`` field reflects the domain's log policy but not the global
``audit_enabled`` toggle, so it does not change when audit is turned on
or off. When audit is enabled, ``logged==1`` selects the denials the
domain submits to audit (audit-side rate-limiting and exclude rules may
still drop some), so a stateless ftrace filter can select them::
The ``logged`` field reflects the domain's log policy and per-request quiet
selection, but not the global ``audit_enabled`` toggle, so it does not
change when audit is turned on or off. When audit is enabled, ``logged==1``
selects the denials the domain submits to audit (audit-side rate-limiting and
exclude rules may still drop some), so a stateless ftrace filter can select
them::
# Show only denials that audit would also log:
echo 'logged==1' > \

View File

@ -8,7 +8,7 @@ Landlock: unprivileged access control
=====================================
:Author: Mickaël Salaün
:Date: August 2026
:Date: October 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 v12)
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 v12)
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_permissions =
LANDLOCK_PERMISSION_NAMESPACE_USE |
LANDLOCK_PERMISSION_CAPABILITY_USE,
};
Because we may not know which kernel version an application will be executed
@ -140,6 +152,13 @@ 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:
case 11:
/* Removes LANDLOCK_PERMISSION_* for ABI < 12 */
ruleset_attr.handled_permissions &=
~(LANDLOCK_PERMISSION_NAMESPACE_USE |
LANDLOCK_PERMISSION_CAPABILITY_USE);
}
This enables the creation of an inclusive ruleset that will contain our rules.
@ -242,6 +261,56 @@ 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:
``permissions`` identifies the permission category (a single
``LANDLOCK_PERMISSION_*`` 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 = {
.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE,
.allowed_capabilities = (1ULL << CAP_SYS_CHROOT),
};
cap_attr.permissions &= ruleset_attr.handled_permissions;
if (cap_attr.permissions)
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 = {
.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE,
.allowed_namespace_types = CLONE_NEWUSER,
};
ns_attr.permissions &= ruleset_attr.handled_permissions;
if (ns_attr.permissions)
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):
@ -442,9 +511,139 @@ 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_permissions`` 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_PERMISSION_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_PERMISSION_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_PERMISSION_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_PERMISSION_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 or suppresses its trace event, which
reports ``logged=0``. 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 from audit.
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_PERMISSION_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_PERMISSION_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_PERMISSION_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 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_PERMISSION_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
----------------
@ -610,7 +809,7 @@ Access rights
-------------
.. kernel-doc:: include/uapi/linux/landlock.h
:identifiers: fs_access net_access scope
:identifiers: fs_access net_access scope permission
Creating a new ruleset
----------------------
@ -629,7 +828,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
-------------------
@ -834,6 +1034,23 @@ with ``LANDLOCK_RESTRICT_SELF_TSYNC``, no_new_privs is set on all threads
of the process. As explained in the tutorial above, leaving no_new_privs
unset is risky even when Landlock does not require it.
Capability (ABI < 12)
---------------------
Starting with the Landlock ABI version 12, it is possible to restrict
:manpage:`capabilities(7)` with the new ``LANDLOCK_PERMISSION_CAPABILITY_USE``
permission flag and ``LANDLOCK_RULE_CAPABILITY`` rule type.
Namespace (ABI < 12)
--------------------
Starting with the Landlock ABI version 12, 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_PERMISSION_NAMESPACE_USE`` permission flag and
``LANDLOCK_RULE_NAMESPACE`` rule type.
.. _kernel_support:
Kernel support

View File

@ -13,14 +13,16 @@
#include <uapi/linux/landlock.h>
/*
* Access-right and scope names, shared between the audit records (get_blocker()
* in security/landlock/audit.c) and the trace events
* Access-right, scope, and permission names, shared between the audit records
* (get_blocker() in security/landlock/audit.c) and the trace events
* (include/trace/events/landlock.h). A consumer defines
* _LANDLOCK_NAME_ENTRY(mask, name) before expanding a list and undefines it
* afterwards: audit maps each entry to a "[bit] = name" slot for O(1) lookup,
* the trace events map it to a __print_flags() { mask, name } pair. The bit
* value lives only in the LANDLOCK_* UAPI constant each entry references.
* Names are unprefixed; audit prepends the "fs."/"net."/"scope." category.
* Access-right and scope names are unprefixed; audit prepends the
* "fs."/"net."/"scope." category. Permission entries carry an action and a
* domain for the qualified and bare views below.
*/
#define _LANDLOCK_ACCESS_FS_NAMES \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_EXECUTE, "execute"), \
@ -53,4 +55,25 @@
"abstract_unix_socket"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_SIGNAL, "signal")
#define _LANDLOCK_PERMISSION_NAMESPACE_NAME "namespace"
#define _LANDLOCK_PERMISSION_CAPABILITY_NAME "capability"
#define _LANDLOCK_PERMISSION_LIST(entry) \
entry(LANDLOCK_PERMISSION_NAMESPACE_USE, "use", \
_LANDLOCK_PERMISSION_NAMESPACE_NAME), \
entry(LANDLOCK_PERMISSION_CAPABILITY_USE, "use", \
_LANDLOCK_PERMISSION_CAPABILITY_NAME)
#define _LANDLOCK_PERMISSION_QUALIFIED_ENTRY(mask, action, domain) \
_LANDLOCK_NAME_ENTRY(mask, domain "." action)
#define _LANDLOCK_PERMISSION_BARE_ENTRY(mask, action, ...) \
_LANDLOCK_NAME_ENTRY(mask, action)
#define _LANDLOCK_PERMISSION_NAMES \
_LANDLOCK_PERMISSION_LIST(_LANDLOCK_PERMISSION_QUALIFIED_ENTRY)
#define _LANDLOCK_PERMISSION_BLOCKER_NAMES \
_LANDLOCK_PERMISSION_LIST(_LANDLOCK_PERMISSION_BARE_ENTRY)
#endif /* _LINUX_LANDLOCK_H */

View File

@ -36,6 +36,8 @@ static_assert(sizeof(access_mask_t) <= sizeof(u64));
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY);
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS);
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS);
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NAMESPACE);
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_CAPABILITY);
#ifdef CREATE_TRACE_POINTS
@ -276,13 +278,14 @@ static inline const char *__trace_landlock_print_layers(
* Every denial event shares three fields. domain is the ID of the
* innermost domain that blocked the access. same_exec tells whether the
* current task is the same executable that entered that domain. logged is
* the domain's audit-logging decision for this denial (its log_status is
* enabled and the per-execution flag selected by same_exec is set); a
* stateless ftrace filter can select the denials the domain submits to
* audit with logged==1, without reconstructing it from the per-execution
* log flags. Denial events order their fields as domain, same_exec,
* logged, then blockers (deny_access events only), then the type-specific
* object fields, then any variable-length field.
* the complete audit-submission selection for this denial: the domain's
* log_status and per-execution flag, per-object or per-member quiet rules,
* and request-specific no-audit options. This excludes global audit state
* and audit-side filters. A stateless ftrace filter can select the denials
* the domain submits to audit with logged==1, without reconstructing those
* inputs. Denial events order their shared fields as domain, same_exec,
* and logged. Access and permission denial events follow these with the
* blockers verdict input, type-specific fields, and any variable-length field.
*
* Relational referents
* ~~~~~~~~~~~~~~~~~~~~~
@ -302,9 +305,9 @@ static inline const char *__trace_landlock_print_layers(
* Blocker fields
* ~~~~~~~~~~~~~~
*
* The filesystem and network blocker arguments identify the request type
* and carry its final missing access subset when applicable. The type
* determines how to interpret the access value.
* Blocker arguments identify the request type and carry its final missing
* access subset when applicable. The type determines how to interpret the
* access value.
*/
/*
@ -343,26 +346,31 @@ TRACE_EVENT(landlock_create_ruleset,
TP_ARGS(ruleset),
TP_STRUCT__entry(
__field( u64, ruleset_id )
__field( u64, ruleset_version )
__field( access_mask_t, handled_fs )
__field( access_mask_t, handled_net )
__field( access_mask_t, scoped )
__field( u64, ruleset_id )
__field( u64, ruleset_version )
__field( access_mask_t, handled_fs )
__field( access_mask_t, handled_net )
__field( access_mask_t, scoped )
__field( access_mask_t, handled_permissions )
),
TP_fast_assign(
__entry->ruleset_id = ruleset->id;
__entry->ruleset_version = ruleset->version;
__entry->handled_fs = ruleset->handled_masks.fs;
__entry->handled_net = ruleset->handled_masks.net;
__entry->scoped = ruleset->handled_masks.scope;
__entry->handled_fs = ruleset->layer.handled.fs;
__entry->handled_net = ruleset->layer.handled.net;
__entry->scoped = ruleset->layer.handled.scope;
__entry->handled_permissions =
ruleset->layer.handled.permissions;
),
TP_printk("ruleset=%llx.%llu handled_fs=%s handled_net=%s scoped=%s",
TP_printk("ruleset=%llx.%llu handled_fs=%s handled_net=%s scoped=%s handled_permissions=%s",
__entry->ruleset_id, __entry->ruleset_version,
__print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES),
__print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES),
__print_flags(__entry->scoped, "|", _LANDLOCK_SCOPE_NAMES))
__print_flags(__entry->scoped, "|", _LANDLOCK_SCOPE_NAMES),
__print_flags(__entry->handled_permissions, "|",
_LANDLOCK_PERMISSION_NAMES))
);
/**
@ -496,6 +504,103 @@ TRACE_EVENT(landlock_add_rule_net_port,
__entry->port)
);
/**
* landlock_add_rule_namespace - Namespace rule added to a ruleset
*
* @ruleset: Source ruleset (never NULL).
* @flags: Complete validated landlock_add_rule_flags value supplied by this
* successful call, not the rule's accumulated quiet state.
* @permissions: Validated permission mask from the rule attribute.
* @allowed_namespace_types: Effective known namespace types allowed by this
* call, using CLONE_NEW* values.
* @quiet_namespace_types: Effective known namespace types quieted by this
* call, using CLONE_NEW* values.
*
* Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so
* the reported ruleset is a stable snapshot that no concurrent writer can
* change.
*/
TRACE_EVENT(landlock_add_rule_namespace,
TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,
u64 permissions, u64 allowed_namespace_types,
u64 quiet_namespace_types),
TP_ARGS(ruleset, flags, permissions, allowed_namespace_types,
quiet_namespace_types),
TP_STRUCT__entry(
__field( u64, ruleset_id )
__field( u64, ruleset_version )
__field( access_mask_t, permissions )
__field( u64, allowed_namespace_types )
__field( u64, quiet_namespace_types )
),
TP_fast_assign(
lockdep_assert_held(&ruleset->lock);
__entry->ruleset_id = ruleset->id;
__entry->ruleset_version = ruleset->version;
__entry->permissions = permissions;
__entry->allowed_namespace_types = allowed_namespace_types;
__entry->quiet_namespace_types = quiet_namespace_types;
),
TP_printk("ruleset=%llx.%llu permissions=%s allowed_namespace_types=0x%llx quiet_namespace_types=0x%llx",
__entry->ruleset_id, __entry->ruleset_version,
__print_flags(__entry->permissions, "|",
_LANDLOCK_PERMISSION_NAMES),
__entry->allowed_namespace_types,
__entry->quiet_namespace_types)
);
/**
* landlock_add_rule_capability - Capability rule added to a ruleset
*
* @ruleset: Source ruleset (never NULL).
* @flags: Complete validated landlock_add_rule_flags value supplied by this
* successful call, not the rule's accumulated quiet state.
* @permissions: Validated permission mask from the rule attribute.
* @allowed_capabilities: Effective known capabilities allowed by this call.
* @quiet_capabilities: Effective known capabilities quieted by this call.
*
* Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so
* the reported ruleset is a stable snapshot that no concurrent writer can
* change.
*/
TRACE_EVENT(landlock_add_rule_capability,
TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,
u64 permissions, u64 allowed_capabilities,
u64 quiet_capabilities),
TP_ARGS(ruleset, flags, permissions, allowed_capabilities,
quiet_capabilities),
TP_STRUCT__entry(
__field( u64, ruleset_id )
__field( u64, ruleset_version )
__field( access_mask_t, permissions )
__field( u64, allowed_capabilities )
__field( u64, quiet_capabilities )
),
TP_fast_assign(
lockdep_assert_held(&ruleset->lock);
__entry->ruleset_id = ruleset->id;
__entry->ruleset_version = ruleset->version;
__entry->permissions = permissions;
__entry->allowed_capabilities = allowed_capabilities;
__entry->quiet_capabilities = quiet_capabilities;
),
TP_printk("ruleset=%llx.%llu permissions=%s allowed_capabilities=0x%llx quiet_capabilities=0x%llx",
__entry->ruleset_id, __entry->ruleset_version,
__print_flags(__entry->permissions, "|",
_LANDLOCK_PERMISSION_NAMES),
__entry->allowed_capabilities, __entry->quiet_capabilities)
);
/**
* landlock_create_domain - New domain created
*
@ -873,6 +978,105 @@ TRACE_EVENT(landlock_deny_access_net,
__entry->port)
);
/**
* landlock_deny_permission_namespace - Namespace use denied
*
* @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
* domain field.
* @same_exec: Whether the current task entered the denying domain itself.
* @logged: Whether this denial was selected for audit logging.
* @blockers: Request type and final missing permission subset (never NULL).
* @namespace_type: CLONE_NEW* namespace type that was denied.
* @namespace_id: Namespace ID, or 0 when creation was denied.
*
* Emitted when a Landlock domain denies namespace use.
*/
TRACE_EVENT(landlock_deny_permission_namespace,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
bool logged, const struct landlock_blockers *blockers,
u32 namespace_type, u64 namespace_id),
TP_ARGS(hierarchy, same_exec, logged, blockers, namespace_type,
namespace_id),
TP_STRUCT__entry(
__field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
__field( enum landlock_request_type, blockers_type )
__field( access_mask_t, blockers_access )
__field( u32, namespace_type )
__field( u64, namespace_id )
),
TP_fast_assign(
__entry->domain_id = hierarchy->id;
__entry->same_exec = same_exec;
__entry->logged = logged;
__entry->blockers_type = blockers->type;
__entry->blockers_access = blockers->access;
__entry->namespace_type = namespace_type;
__entry->namespace_id = namespace_id;
),
TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s namespace_type=0x%x namespace_id=%llu",
__entry->domain_id, __entry->same_exec, __entry->logged,
__entry->blockers_type == LANDLOCK_REQUEST_NAMESPACE ?
__print_flags(__entry->blockers_access, "|",
_LANDLOCK_PERMISSION_BLOCKER_NAMES) :
"unknown",
__entry->namespace_type, __entry->namespace_id)
);
/**
* landlock_deny_permission_capability - Capability use denied
*
* @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
* domain field.
* @same_exec: Whether the current task entered the denying domain itself.
* @logged: Whether this denial was selected for audit logging.
* @blockers: Request type and final missing permission subset (never NULL).
* @capability: CAP_* number that was denied.
*
* Emitted when a Landlock domain denies capability use, except for checks
* made with CAP_OPT_NOAUDIT, which are denied without emitting this event.
*/
TRACE_EVENT(landlock_deny_permission_capability,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
bool logged, const struct landlock_blockers *blockers,
int capability),
TP_ARGS(hierarchy, same_exec, logged, blockers, capability),
TP_STRUCT__entry(
__field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
__field( enum landlock_request_type, blockers_type )
__field( access_mask_t, blockers_access )
__field( int, capability )
),
TP_fast_assign(
__entry->domain_id = hierarchy->id;
__entry->same_exec = same_exec;
__entry->logged = logged;
__entry->blockers_type = blockers->type;
__entry->blockers_access = blockers->access;
__entry->capability = capability;
),
TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s capability=%d",
__entry->domain_id, __entry->same_exec, __entry->logged,
__entry->blockers_type == LANDLOCK_REQUEST_CAPABILITY ?
__print_flags(__entry->blockers_access, "|",
_LANDLOCK_PERMISSION_BLOCKER_NAMES) :
"unknown",
__entry->capability)
);
/**
* landlock_deny_ptrace - Ptrace access denied by a Landlock domain
*

View File

@ -33,14 +33,14 @@
* (and that they have tested with a kernel that supported them all).
*
* @quiet_access_fs and @quiet_access_net are bitmasks of actions for which a
* denial by this layer will not trigger a log if the corresponding object (or
* its children, for filesystem rules) is marked with the "quiet" bit via
* %LANDLOCK_ADD_RULE_QUIET, even if logging would normally take place per
* denial by this layer will not trigger an audit record if the corresponding
* object (or its children, for filesystem rules) is marked with the "quiet" bit
* via %LANDLOCK_ADD_RULE_QUIET, even if logging would normally take place per
* landlock_restrict_self() flags. @quiet_scoped is similar, except that it
* does not require marking any objects as quiet - if the ruleset is created
* with any bits set in @quiet_scoped, then denial of such scoped resources will
* not trigger any log. These 3 fields are available since Landlock ABI version
* 10.
* not trigger an audit record. These three fields are available since Landlock
* ABI version 10.
*
* @quiet_access_fs, @quiet_access_net and @quiet_scoped must be a subset of
* @handled_access_fs, @handled_access_net and @scoped respectively.
@ -66,18 +66,24 @@ struct landlock_ruleset_attr {
__u64 scoped;
/**
* @quiet_access_fs: Bitmask of filesystem actions which should not be
* logged if per-object quiet flag is set.
* submitted to audit if the per-object quiet flag is set.
*/
__u64 quiet_access_fs;
/**
* @quiet_access_net: Bitmask of network actions which should not be
* logged if per-object quiet flag is set.
* submitted to audit if the per-object quiet flag is set.
*/
__u64 quiet_access_net;
/**
* @quiet_scoped: Bitmask of scoped actions which should not be logged.
* @quiet_scoped: Bitmask of scoped actions which should not be
* submitted to audit.
*/
__u64 quiet_scoped;
/**
* @handled_permissions: Bitmask of handled permissions (cf. `Permission
* flags`_).
*/
__u64 handled_permissions;
};
/**
@ -228,6 +234,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,
};
/**
@ -281,6 +296,77 @@ 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 {
/**
* @permissions: Must be set to %LANDLOCK_PERMISSION_NAMESPACE_USE.
*/
__u64 permissions;
/**
* @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 submitted to audit,
* even when landlock_restrict_self() enables audit logging. Only audit
* records attributed to this layer are suppressed; denial tracepoints
* still fire (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. Programs should omit this rule when they
* neither allow nor quiet a namespace type.
*/
__u64 quiet_namespace_types;
};
/**
* struct landlock_capability_attr - Capability definition
*
* Argument of sys_landlock_add_rule() with %LANDLOCK_RULE_CAPABILITY.
*/
struct landlock_capability_attr {
/**
* @permissions: Must be set to %LANDLOCK_PERMISSION_CAPABILITY_USE.
*/
__u64 permissions;
/**
* @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 submitted to audit, even if
* audit logging would normally take place per landlock_restrict_self()
* flags. Only audit records attributed to this layer are suppressed;
* denial tracepoints still fire (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. Programs should omit this rule
* when they neither allow nor quiet a capability.
*/
__u64 quiet_capabilities;
};
/**
* DOC: fs_access
*
@ -507,4 +593,41 @@ struct landlock_net_port_attr {
#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1)
/* clang-format on*/
/**
* DOC: permission
*
* Permission flags
* ~~~~~~~~~~~~~~~~
*
* These flags restrict the use of members of a category, each member being
* identified by a constant from another kernel subsystem (e.g. CLONE_NEW*
* namespace types, CAP_* capabilities). A flag covers every kernel path that
* uses a member of its category, and members that no rule explicitly allows are
* denied. Values unknown to the running kernel are silently accepted for
* forward compatibility and stay denied 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_PERMISSION_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.
* Support added in Landlock ABI version 12.
* - %LANDLOCK_PERMISSION_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. Support added in
* Landlock ABI version 12.
*/
#define LANDLOCK_PERMISSION_NAMESPACE_USE (1ULL << 0)
#define LANDLOCK_PERMISSION_CAPABILITY_USE (1ULL << 1)
#endif /* _UAPI_LINUX_LANDLOCK_H */

View File

@ -166,8 +166,17 @@ config SAMPLE_LANDLOCK
bool "Landlock example"
depends on CC_CAN_LINK && HEADERS_INSTALL
help
Build a simple Landlock sandbox manager able to start a process
restricted by a user-defined filesystem access control policy.
Build a Landlock sandbox manager able to start a process restricted
by user-defined filesystem, network, scope, namespace, and capability
policies.
The optional libcap development headers and library enable capability
names. Without them, the sample still builds, accepts capability
numbers, and warns when capability settings are used.
config SAMPLE_LANDLOCK_LIBCAP
def_bool $(cc_can_link_user,-include sys/capability.h -lcap)
depends on SAMPLE_LANDLOCK
config SAMPLE_PIDFD
bool "pidfd sample"

View File

@ -4,6 +4,11 @@ userprogs-always-y := sandboxer
userccflags += -I usr/include
ifeq ($(CONFIG_SAMPLE_LANDLOCK_LIBCAP),y)
userccflags += -DUSE_LIBCAP
userldlibs += -lcap
endif
.PHONY: all clean
all:

View File

@ -12,8 +12,11 @@
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/landlock.h>
#include <linux/sched.h>
#include <linux/socket.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -22,7 +25,10 @@
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdbool.h>
#ifdef USE_LIBCAP
#include <sys/capability.h>
#endif
#if defined(__GLIBC__)
#include <linux/prctl.h>
@ -62,6 +68,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_NS_NAME "LL_NS"
#define ENV_NS_QUIET_NAME "LL_NS_QUIET"
#define ENV_CAP_NAME "LL_CAP"
#define ENV_CAP_QUIET_NAME "LL_CAP_QUIET"
#define ENV_SCOPED_NAME "LL_SCOPED"
#define ENV_QUIET_ACCESS_NAME "LL_QUIET_ACCESS"
#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
@ -69,6 +79,18 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_UDP_CONNECT_SEND_NAME "LL_UDP_CONNECT_SEND"
#define ENV_DELIMITER ":"
#ifdef USE_LIBCAP
#define CAP_VALUE_DESCRIPTION "names or numbers"
#define CAP_VALUE_EXAMPLES "cap_net_bind_service, cap_sys_admin, 18"
#define CAP_VALUE_EXAMPLE "cap_sys_admin"
#else
#define CAP_VALUE_DESCRIPTION "numbers"
#define CAP_VALUE_EXAMPLES "10, 18, 21"
#define CAP_VALUE_EXAMPLE "21"
#endif
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
static int str2num(const char *numstr, __u64 *num_dst)
{
char *endptr = NULL;
@ -232,6 +254,185 @@ 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 < ARRAY_SIZE(ns_map); i++) {
if (strcmp(name, ns_map[i].name) == 0)
return ns_map[i].value;
}
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 = {
.permissions = LANDLOCK_PERMISSION_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;
}
static int str2cap(const char *const strcap, __u64 *const cap)
{
#ifdef USE_LIBCAP
cap_value_t value;
#endif
if (!str2num(strcap, cap))
return 0;
#ifdef USE_LIBCAP
if (cap_from_name(strcap, &value))
return 1;
*cap = value;
return 0;
#else
return 1;
#endif
}
/*
* Parses a colon-delimited list of capability values 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))) {
__u64 cap;
if (strcmp(strcap, "") == 0)
continue;
if (str2cap(strcap, &cap)) {
fprintf(stderr, "Failed to parse capability \"%s\"\n",
strcap);
goto out_free_name;
}
if (cap >= sizeof(*mask) * CHAR_BIT) {
fprintf(stderr, "Capability \"%s\" is out of range\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 = {
.permissions = LANDLOCK_PERMISSION_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;
}
/* Returns true on error, false otherwise. */
static bool check_ruleset_scope(const char *const env_var,
struct landlock_ruleset_attr *ruleset_attr)
@ -369,7 +570,7 @@ static int add_quiet_access(const char *const env_var,
return 0;
}
#define LANDLOCK_ABI_LAST 11
#define LANDLOCK_ABI_LAST 12
#define XSTR(s) #s
#define STR(s) XSTR(s)
@ -397,6 +598,23 @@ 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_NS_NAME ": namespace types allowed to use\n"
" (cgroup, ipc, mnt, net, pid, time, user, uts)\n"
"* " ENV_NS_QUIET_NAME
": namespace types whose denial should not be audit logged\n"
" (same value format as " ENV_NS_NAME
"; quieting an allowed member is inert,\n"
" as an allowed member is never denied; setting it alone still\n"
" restricts namespace use, denying every type)\n"
"* " ENV_CAP_NAME ": capabilities allowed to use, as "
CAP_VALUE_DESCRIPTION "\n"
" (e.g. " CAP_VALUE_EXAMPLES ")\n"
"* " ENV_CAP_QUIET_NAME
": capabilities whose denial should not be audit logged\n"
" (same value format as " ENV_CAP_NAME
"; quieting an allowed member is inert,\n"
" as an allowed member is never denied; setting it alone still\n"
" restricts capability use, denying every capability)\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 +641,8 @@ static const char help[] =
ENV_TCP_BIND_NAME "=\"9418\" "
ENV_TCP_CONNECT_NAME "=\"80:443\" "
ENV_UDP_CONNECT_SEND_NAME "=\"53\" "
ENV_NS_NAME "=\"user:uts:net\" "
ENV_CAP_NAME "=\"" CAP_VALUE_EXAMPLE "\" "
ENV_SCOPED_NAME "=\"a:s\" "
"%1$s bash -i\n"
"\n"
@ -451,6 +671,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_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE |
LANDLOCK_PERMISSION_CAPABILITY_USE,
};
bool quiet_supported = true;
int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON |
@ -552,7 +774,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
supported_restrict_flags &=
~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
set_restrict_flags &= ~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
__attribute__((fallthrough));
case 11:
/* Removes LANDLOCK_PERMISSION_* for ABI < 12 */
ruleset_attr.handled_permissions &=
~(LANDLOCK_PERMISSION_NAMESPACE_USE |
LANDLOCK_PERMISSION_CAPABILITY_USE);
/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
fprintf(stderr,
"Hint: You should update the running kernel "
@ -597,6 +824,32 @@ int main(const int argc, char *const argv[], char *const *const envp)
~LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
}
/* Removes namespace handling if not set by a user. */
if (!getenv(ENV_NS_NAME) && !getenv(ENV_NS_QUIET_NAME))
ruleset_attr.handled_permissions &=
~LANDLOCK_PERMISSION_NAMESPACE_USE;
if (!(ruleset_attr.handled_permissions &
LANDLOCK_PERMISSION_NAMESPACE_USE)) {
unsetenv(ENV_NS_NAME);
unsetenv(ENV_NS_QUIET_NAME);
}
#ifndef USE_LIBCAP
if (getenv(ENV_CAP_NAME) || getenv(ENV_CAP_QUIET_NAME))
fprintf(stderr,
"Warning: Built without libcap; use capability numbers, not names.\n");
#endif
/* Removes capability handling if not set by a user. */
if (!getenv(ENV_CAP_NAME) && !getenv(ENV_CAP_QUIET_NAME))
ruleset_attr.handled_permissions &=
~LANDLOCK_PERMISSION_CAPABILITY_USE;
if (!(ruleset_attr.handled_permissions &
LANDLOCK_PERMISSION_CAPABILITY_USE)) {
unsetenv(ENV_CAP_NAME);
unsetenv(ENV_CAP_QUIET_NAME);
}
if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
return 1;
@ -680,6 +933,16 @@ int main(const int argc, char *const argv[], char *const *const envp)
}
}
if ((ruleset_attr.handled_permissions &
LANDLOCK_PERMISSION_NAMESPACE_USE) &&
populate_ruleset_ns(ENV_NS_NAME, ENV_NS_QUIET_NAME, ruleset_fd))
goto err_close_ruleset;
if ((ruleset_attr.handled_permissions &
LANDLOCK_PERMISSION_CAPABILITY_USE) &&
populate_ruleset_cap(ENV_CAP_NAME, ENV_CAP_QUIET_NAME, ruleset_fd))
goto err_close_ruleset;
if (!(set_restrict_flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) &&
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("Failed to restrict privileges");

View File

@ -9,7 +9,9 @@ landlock-y := \
task.o \
fs.o \
tsync.o \
domain.o
domain.o \
ns.o \
cap.o
landlock-$(CONFIG_INET) += net.o

View File

@ -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 domain->handled_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
* domain->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 permissions can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_PERMISSION);
/* 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. */
/* Access and permission 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 permissions : LANDLOCK_NUM_PERMISSION;
} __packed __aligned(sizeof(u32));
union access_masks_all {
@ -61,6 +64,46 @@ union access_masks_all {
static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
sizeof(typeof_member(union access_masks_all, all)));
/**
* struct permission_masks - Per-permission member bitmasks
*/
struct permission_masks {
/**
* @ns_types: Namespace type member mask, indexed in FOR_EACH_NS_TYPE()
* order.
*/
u64 ns_types : LANDLOCK_NUM_NAMESPACE_TYPE;
/**
* @caps: Capability member mask, indexed by CAP_* values.
*/
u64 caps : LANDLOCK_NUM_CAPABILITY;
} __packed __aligned(sizeof(u64));
static_assert(sizeof(struct permission_masks) == sizeof(u64));
/* All permission_masks bitfields must fit in a single u64. */
static_assert(LANDLOCK_NUM_CAPABILITY + LANDLOCK_NUM_NAMESPACE_TYPE <=
BITS_PER_TYPE(u64));
/**
* struct layer_config - Per-layer access configuration
*
* A ruleset stores one mutable layer and a domain stores a flexible array of
* immutable layers. Unlike filesystem and network access rights, namespace
* types and capabilities use flat bitmasks because their keyspaces are small
* and bounded.
*/
struct layer_config {
/**
* @allowed: Members allowed by each handled permission.
*/
struct permission_masks allowed;
/**
* @handled: Bitmask of access rights and permissions handled (i.e.
* restricted) by this layer.
*/
struct access_masks handled;
};
#define _LANDLOCK_LAYER_MASK_PADDING \
(BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \
IS_ENABLED(CONFIG_SECURITY_LANDLOCK_LOG))
@ -131,17 +174,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. */

View File

@ -21,10 +21,10 @@
#include "log.h"
/*
* Access-right and scope names are built from the lists shared with the trace
* events (see <linux/landlock.h>). The designated initializer places each name
* at its bit index, so the lookup stays O(1) and does not depend on the entry
* order. log_blockers() adds the "fs."/"net."/"scope." category prefix.
* Access-right, scope, and permission names are built from the lists shared
* with the trace events (see <linux/landlock.h>). The designated initializer
* places each name at its bit index, so the lookup stays O(1) and does not
* depend on the entry order. log_blockers() adds the related category prefix.
*/
#define _LANDLOCK_NAME_ENTRY(mask, name) [BIT_INDEX(mask)] = name
@ -40,6 +40,12 @@ static const char *const scope_strings[] = { _LANDLOCK_SCOPE_NAMES };
static_assert(ARRAY_SIZE(scope_strings) == LANDLOCK_NUM_SCOPE);
static const char *const permission_strings[] = {
_LANDLOCK_PERMISSION_BLOCKER_NAMES
};
static_assert(ARRAY_SIZE(permission_strings) == LANDLOCK_NUM_PERMISSION);
#undef _LANDLOCK_NAME_ENTRY
static __attribute_const__ const char *
@ -73,6 +79,12 @@ get_blocker(const enum landlock_request_type type,
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
WARN_ON_ONCE(access_bit != -1);
return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)];
case LANDLOCK_REQUEST_NAMESPACE:
case LANDLOCK_REQUEST_CAPABILITY:
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(permission_strings)))
return "unknown";
return permission_strings[access_bit];
}
WARN_ON_ONCE(1);
@ -82,8 +94,8 @@ get_blocker(const enum landlock_request_type type,
/*
* Returns the audit category prefix prepended to the unprefixed blocker name
* returned by get_blocker() (filesystem and network access rights,
* change_topology, and scopes). The ptrace blocker is standalone and carries
* its full name in get_blocker(), so it uses no prefix.
* change_topology, scopes, and permissions). The ptrace blocker is standalone:
* its full name comes from get_blocker(), so it uses no prefix.
*/
static __attribute_const__ const char *
blocker_prefix(const enum landlock_request_type type)
@ -102,6 +114,12 @@ blocker_prefix(const enum landlock_request_type type)
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
return "scope.";
case LANDLOCK_REQUEST_NAMESPACE:
return _LANDLOCK_PERMISSION_NAMESPACE_NAME ".";
case LANDLOCK_REQUEST_CAPABILITY:
return _LANDLOCK_PERMISSION_CAPABILITY_NAME ".";
}
WARN_ON_ONCE(1);
@ -163,7 +181,7 @@ static void log_domain(struct landlock_hierarchy *const hierarchy)
*
* @request: Detail of the user space request.
* @youngest_denied: The youngest hierarchy node that denied the access.
* @missing: The set of denied access rights.
* @missing: The set of denied access rights or permissions.
* @logged: Whether the denial is selected for logging, as computed by
* landlock_log_denial() (domain policy and quiet rules).
*

159
security/landlock/cap.c Normal file
View File

@ -0,0 +1,159 @@
// 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 "cap.h"
#include "cred.h"
#include "domain.h"
#include "limits.h"
#include "log.h"
#include "ruleset.h"
#include "setup.h"
static const struct access_masks cap_permission = {
.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE,
};
/**
* hook_capable - Deny capability use for Landlock-sandboxed processes
*
* @cred: Credentials being checked.
* @ns: Target user namespace (intentionally ignored; policy depends on the
* acting credentials and @cap, not namespace ownership).
* @cap: Capability number (CAP_*).
* @opts: Capability check options. CAP_OPT_NOAUDIT skips the denial record:
* no audit record, no trace event, and no denial count.
*
* 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_permission, NULL);
if (!subject)
return 0;
denied_layer = landlock_permission_is_denied(
subject->domain, LANDLOCK_PERMISSION_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,
.permission =
LANDLOCK_PERMISSION_CAPABILITY_USE,
.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)
{
if (!IS_ENABLED(CONFIG_BUG))
kunit_skip(test, "requires CONFIG_BUG");
/* clang-format off */
kunit_warning_suppress(test) {
/* clang-format on */
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)
{
if (!IS_ENABLED(CONFIG_BUG))
kunit_skip(test, "requires CONFIG_BUG");
/* clang-format off */
kunit_warning_suppress(test) {
/* clang-format on */
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 */

48
security/landlock/cap.h Normal file
View File

@ -0,0 +1,48 @@
/* 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/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 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 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 */

View File

@ -138,7 +138,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->handled_masks[layer_level],
.masks = domain->layers[layer_level].handled,
};
if (layer.all & masks_all.all) {

View File

@ -50,7 +50,7 @@ static struct landlock_domain *create_domain(const u32 num_layers)
struct landlock_domain *new_domain;
build_check_domain();
new_domain = kzalloc_flex(*new_domain, handled_masks, num_layers,
new_domain = kzalloc_flex(*new_domain, layers, num_layers,
GFP_KERNEL_ACCOUNT);
if (!new_domain)
return ERR_PTR(-ENOMEM);
@ -328,8 +328,8 @@ static int merge_ruleset(struct landlock_domain *const dst,
if (WARN_ON_ONCE(dst->num_layers < 1))
return -EINVAL;
dst->handled_masks[dst->num_layers - 1] =
landlock_upgrade_handled_access_masks(src->handled_masks);
dst->layers[dst->num_layers - 1] =
landlock_upgrade_handled_layer_config(src->layer);
/* Merges the @src inode tree. */
err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
@ -404,8 +404,8 @@ static int inherit_ruleset(struct landlock_domain *const parent,
/*
* Copies the parent layer stack and leaves a space for the new layer.
*/
memcpy(child->handled_masks, parent->handled_masks,
flex_array_size(parent, handled_masks, parent->num_layers));
memcpy(child->layers, parent->layers,
flex_array_size(parent, layers, parent->num_layers));
if (WARN_ON_ONCE(!parent->hierarchy))
return -EINVAL;
@ -479,7 +479,8 @@ landlock_merge_ruleset(struct landlock_domain *const parent,
return ERR_PTR(err);
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
new_dom->hierarchy->quiet_access = ruleset->quiet_access;
new_dom->hierarchy->quiet_permission = ruleset->quiet_permission;
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
return no_free_ptr(new_dom);

View File

@ -130,10 +130,15 @@ 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_permission: Per-member quiet bitmasks for permission types in
* this layer.
*/
struct permission_masks quiet_permission;
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
};
@ -217,7 +222,7 @@ struct landlock_domain {
* @work_free: Enables to free a domain within a lockless
* section. This is only used by landlock_put_domain_deferred()
* when @usage reaches zero. The fields @usage, @num_layers and
* @handled_masks are then unused.
* @layers are then unused.
*/
struct work_struct work_free;
struct {
@ -233,15 +238,14 @@ struct landlock_domain {
*/
u32 num_layers;
/**
* @handled_masks: Contains the subset of filesystem and
* network actions that are restricted by a domain. 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 domain.
* @layers: Per-layer access configuration. 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 domain.
*
* Not annotated with __counted_by(num_layers): clang
* miscomputes the counter address for a FAM nested in
@ -249,7 +253,7 @@ struct landlock_domain {
* FORTIFY:
* https://github.com/llvm/llvm-project/pull/228309
*/
struct access_masks handled_masks[];
struct layer_config layers[];
};
};
};
@ -259,7 +263,7 @@ landlock_get_fs_access_mask(const struct landlock_domain *const domain,
const u16 layer_level)
{
/* Handles all initially denied by default access rights. */
return domain->handled_masks[layer_level].fs |
return domain->layers[layer_level].handled.fs |
_LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
}
@ -267,14 +271,14 @@ static inline access_mask_t
landlock_get_net_access_mask(const struct landlock_domain *const domain,
const u16 layer_level)
{
return domain->handled_masks[layer_level].net;
return domain->layers[layer_level].handled.net;
}
static inline access_mask_t
landlock_get_scope_mask(const struct landlock_domain *const domain,
const u16 layer_level)
{
return domain->handled_masks[layer_level].scope;
return domain->layers[layer_level].handled.scope;
}
/**
@ -293,7 +297,7 @@ landlock_union_access_masks(const struct landlock_domain *const domain)
for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
union access_masks_all layer = {
.masks = domain->handled_masks[layer_level],
.masks = domain->layers[layer_level].handled,
};
matches.all |= layer.all;
@ -329,4 +333,63 @@ static inline void landlock_get_domain(struct landlock_domain *const domain)
refcount_inc(&domain->usage);
}
/**
* landlock_permission_is_denied - Check if a permission request is denied
*
* @domain: The enforced domain.
* @permission_bit: The LANDLOCK_PERMISSION_* flag to check. Must have
* exactly one bit set.
* @request_value: Compact bitmask to look for (e.g. the result of
* landlock_ns_type_to_bit()). Must have exactly one bit set.
*
* Iterate from the youngest layer to the oldest. For each layer that handles
* @permission_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_permission_is_denied(const struct landlock_domain *const domain,
const access_mask_t permission_bit,
const u64 request_value)
{
ssize_t layer;
BUILD_BUG_ON(sizeof(permission_bit) > sizeof(u32));
if (WARN_ON_ONCE(hweight32(permission_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.permissions &
permission_bit))
continue;
/*
* Current callers pass only permission types with an explicit
* case below. The default catches a new caller missing its
* member mask.
*/
switch (permission_bit) {
case LANDLOCK_PERMISSION_NAMESPACE_USE:
allowed = domain->layers[layer].allowed.ns_types;
break;
case LANDLOCK_PERMISSION_CAPABILITY_USE:
allowed = domain->layers[layer].allowed.caps;
break;
default:
WARN_ONCE(1, "Unknown permission %u\n",
(unsigned int)permission_bit);
return layer + 1;
}
if (!(allowed & request_value))
return layer + 1;
}
return 0;
}
#endif /* _SECURITY_LANDLOCK_DOMAIN_H */

View File

@ -61,7 +61,7 @@ static void release_inode(struct landlock_object *const object)
__releases(object->lock)
{
struct inode *const inode = object->underobj;
struct super_block *sb;
atomic_long_t *inode_refs;
if (!inode) {
spin_unlock(&object->lock);
@ -77,8 +77,8 @@ static void release_inode(struct landlock_object *const object)
* Makes sure that if the filesystem is concurrently unmounted,
* hook_sb_delete() will wait for us to finish iput().
*/
sb = inode->i_sb;
atomic_long_inc(&landlock_superblock(sb)->inode_refs);
inode_refs = &landlock_superblock(inode->i_sb)->inode_refs;
atomic_long_inc(inode_refs);
spin_unlock(&object->lock);
/*
* Because object->underobj was not NULL, hook_sb_delete() and
@ -92,8 +92,8 @@ static void release_inode(struct landlock_object *const object)
*/
iput(inode);
if (atomic_long_dec_and_test(&landlock_superblock(sb)->inode_refs))
wake_up_var(&landlock_superblock(sb)->inode_refs);
if (atomic_long_dec_and_test(inode_refs))
wake_up_var(inode_refs);
}
static const struct landlock_object_underops landlock_fs_underops = {
@ -342,7 +342,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
/* Transforms relative access rights to absolute ones. */
access_rights |= LANDLOCK_MASK_ACCESS_FS &
~(ruleset->handled_masks.fs |
~(ruleset->layer.handled.fs |
_LANDLOCK_ACCESS_FS_INITIALLY_DENIED);
id.key.object = get_inode_object(d_backing_inode(path->dentry));
if (IS_ERR(id.key.object))

View File

@ -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_PERMISSION LANDLOCK_PERMISSION_CAPABILITY_USE
#define LANDLOCK_MASK_PERMISSION ((LANDLOCK_LAST_PERMISSION << 1) - 1)
#define LANDLOCK_NUM_PERMISSION __const_hweight64(LANDLOCK_MASK_PERMISSION)
#define LANDLOCK_NUM_NAMESPACE_TYPE __const_hweight64((u64)CLONE_NS_ALL)
#define LANDLOCK_NUM_CAPABILITY (CAP_LAST_CAP + 1)
#define LANDLOCK_NUM_ACCESS_MAX \
MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE)

View File

@ -12,11 +12,13 @@
#include "access.h"
#include "audit.h"
#include "cap.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "limits.h"
#include "log.h"
#include "ns.h"
#include "ruleset.h"
#include "trace.h"
@ -382,6 +384,37 @@ static bool is_valid_request(const struct landlock_request *const request)
if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
return false;
if (WARN_ON_ONCE(request->access && request->permission))
return false;
switch (request->type) {
case LANDLOCK_REQUEST_NAMESPACE:
if (WARN_ON_ONCE(request->permission !=
LANDLOCK_PERMISSION_NAMESPACE_USE) ||
WARN_ON_ONCE(request->audit.type != LSM_AUDIT_DATA_NS))
return false;
break;
case LANDLOCK_REQUEST_CAPABILITY:
if (WARN_ON_ONCE(request->permission !=
LANDLOCK_PERMISSION_CAPABILITY_USE) ||
WARN_ON_ONCE(request->audit.type != LSM_AUDIT_DATA_CAP))
return false;
break;
case LANDLOCK_REQUEST_PTRACE:
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
case LANDLOCK_REQUEST_FS_ACCESS:
case LANDLOCK_REQUEST_NET_ACCESS:
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
if (WARN_ON_ONCE(request->permission))
return false;
break;
default:
WARN_ONCE(1, "Unknown Landlock request type %d\n",
request->type);
return false;
}
if (request->access) {
if (WARN_ON_ONCE(!(!!request->layer_masks ^
!!request->all_existing_optional_access)))
@ -436,22 +469,28 @@ is_denial_quieted(const struct landlock_request *const request,
if (object_quiet_flag) {
const access_mask_t quiet_mask =
pick_access_mask_for_request_type(
request->type, youngest_denied->quiet_masks);
request->type, youngest_denied->quiet_access);
return (quiet_mask & missing) == missing;
}
/*
* Either the object is not quiet, or this is a scope request. We check
* request->type to distinguish between the two cases.
* Per-object quieting did not apply. Check request->type for scope and
* permission quieting; ptrace and topology requests are never quiet.
*/
switch (request->type) {
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
return !!(youngest_denied->quiet_masks.scope &
return !!(youngest_denied->quiet_access.scope &
LANDLOCK_SCOPE_SIGNAL);
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
return !!(youngest_denied->quiet_masks.scope &
return !!(youngest_denied->quiet_access.scope &
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
case LANDLOCK_REQUEST_NAMESPACE:
return !!(youngest_denied->quiet_permission.ns_types &
landlock_ns_type_to_bit(request->audit.u.ns.ns_type));
case LANDLOCK_REQUEST_CAPABILITY:
return !!(youngest_denied->quiet_permission.caps &
landlock_cap_to_bit(request->audit.u.cap));
/*
* Leave LANDLOCK_REQUEST_PTRACE and LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY
* unhandled for now - they are never quiet.
@ -508,8 +547,8 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
if (!is_valid_request(request))
return;
missing = request->access;
if (missing) {
missing = request->access ? request->access : request->permission;
if (request->access) {
/* Gets the nearest domain that denies the request. */
if (request->layer_masks) {
youngest_layer = get_denied_layer(subject->domain,

View File

@ -25,9 +25,12 @@ enum landlock_request_type {
LANDLOCK_REQUEST_NET_ACCESS,
LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
LANDLOCK_REQUEST_SCOPE_SIGNAL,
LANDLOCK_REQUEST_NAMESPACE,
LANDLOCK_REQUEST_CAPABILITY,
};
struct landlock_blockers {
/* Blocking access rights or permissions, as selected by @type. */
access_mask_t access;
enum landlock_request_type type;
};
@ -58,8 +61,13 @@ struct landlock_signal_trace {
* CONFIG_SECURITY_LANDLOCK_LOG is not set.
*/
struct landlock_request {
/* Mandatory fields. */
/* Mandatory request type. */
enum landlock_request_type type;
/* Required field for permission requests. */
access_mask_t permission;
/* Mandatory audit context. */
struct common_audit_data audit;
/**

View File

@ -35,7 +35,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
/* Transforms relative access rights to absolute ones. */
access_rights |= LANDLOCK_MASK_ACCESS_NET & ~ruleset->handled_masks.net;
access_rights |= LANDLOCK_MASK_ACCESS_NET & ~ruleset->layer.handled.net;
mutex_lock(&ruleset->lock);
err = landlock_insert_rule(ruleset, id, access_rights, flags);

241
security/landlock/ns.c Normal file
View File

@ -0,0 +1,241 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Landlock - Namespace hooks
*
* Copyright © 2026 Cloudflare, Inc.
*/
#include <linux/bitops.h>
#include <linux/bug.h>
#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 "cred.h"
#include "domain.h"
#include "limits.h"
#include "log.h"
#include "ns.h"
#include "ruleset.h"
#include "setup.h"
#define _LANDLOCK_NS_TYPE(type, flag) flag,
static const u32 landlock_namespace_types[] = {
/* clang-format off */
FOR_EACH_NS_TYPE(_LANDLOCK_NS_TYPE)
/* clang-format on */
};
#undef _LANDLOCK_NS_TYPE
static_assert(ARRAY_SIZE(landlock_namespace_types) ==
LANDLOCK_NUM_NAMESPACE_TYPE);
/* 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));
/**
* landlock_ns_type_to_bit - Convert a namespace type to a compact bitmask
*
* @ns_type: Namespace type (``CLONE_NEW*``).
*
* Return: The compact bit for @ns_type, or 0 if @ns_type is invalid (with a
* warning).
*/
u64 landlock_ns_type_to_bit(const u32 ns_type)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) {
if (landlock_namespace_types[i] == ns_type)
return BIT_ULL(i);
}
WARN_ONCE(1, "Unknown namespace type 0x%x\n", ns_type);
return 0;
}
/**
* landlock_ns_types_to_bits - Convert namespace types to a compact bitmask
*
* @ns_types: Bitmask of namespace types (``CLONE_NEW*``).
*
* Return: The compact bits for all known @ns_types. Warns if unknown bits are
* present (callers must pre-mask user input).
*/
u64 landlock_ns_types_to_bits(const u64 ns_types)
{
u64 bits = 0;
size_t i;
/* Callers pre-mask (CLONE_NS_ALL); the WARN guards future callers. */
WARN_ON_ONCE(ns_types & ~(u64)CLONE_NS_ALL);
for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) {
if (ns_types & landlock_namespace_types[i])
bits |= BIT_ULL(i);
}
return bits;
}
static const struct access_masks ns_permission = {
.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE,
};
/**
* check_ns_type - Check namespace use permission
*
* @ns: The namespace being allocated or installed.
*
* Shared check for namespace_init (creation via clone(2), unshare(2),
* open_tree(2), or fsmount(2)) and namespace_install (use 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_permission,
NULL);
if (!subject)
return 0;
denied_layer = landlock_permission_is_denied(
subject->domain, LANDLOCK_PERMISSION_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,
.permission =
LANDLOCK_PERMISSION_NAMESPACE_USE,
.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>
static void test_ns_type_to_bit(struct kunit *const test)
{
u64 seen = 0;
size_t i;
for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) {
const u64 bit =
landlock_ns_type_to_bit(landlock_namespace_types[i]);
KUNIT_EXPECT_NE(test, 0ULL, bit);
KUNIT_EXPECT_EQ(test, 0ULL, seen & bit);
seen |= bit;
}
KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_NAMESPACE_TYPE - 1, 0),
seen);
}
static void test_ns_type_to_bit_unknown(struct kunit *const test)
{
if (!IS_ENABLED(CONFIG_BUG))
kunit_skip(test, "requires CONFIG_BUG");
/* clang-format off */
kunit_warning_suppress(test) {
/* clang-format on */
KUNIT_EXPECT_EQ(test, 0ULL,
landlock_ns_type_to_bit(CLONE_THREAD));
KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
}
}
static void test_ns_types_to_bits_all(struct kunit *const test)
{
KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_NAMESPACE_TYPE - 1, 0),
landlock_ns_types_to_bits(CLONE_NS_ALL));
}
static void test_ns_types_to_bits_single(struct kunit *const test)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++)
KUNIT_EXPECT_EQ(
test,
landlock_ns_type_to_bit(landlock_namespace_types[i]),
landlock_ns_types_to_bits(landlock_namespace_types[i]));
}
static void test_ns_types_to_bits_unknown(struct kunit *const test)
{
if (!IS_ENABLED(CONFIG_BUG))
kunit_skip(test, "requires CONFIG_BUG");
/* clang-format off */
kunit_warning_suppress(test) {
/* clang-format on */
KUNIT_EXPECT_EQ(test, 0ULL,
landlock_ns_types_to_bits(CLONE_THREAD));
KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
}
}
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_unknown),
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 */

18
security/landlock/ns.h Normal file
View File

@ -0,0 +1,18 @@
/* 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/types.h>
u64 landlock_ns_type_to_bit(u32 ns_type);
u64 landlock_ns_types_to_bits(u64 ns_types);
__init void landlock_add_ns_hooks(void);
#endif /* _SECURITY_LANDLOCK_NS_H */

View File

@ -31,15 +31,15 @@
#include <trace/events/landlock.h>
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 permission_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 &&
!permission_mask)
return ERR_PTR(-ENOMSG);
new_ruleset = kzalloc_obj(*new_ruleset, GFP_KERNEL_ACCOUNT);
@ -64,21 +64,32 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,
LANDLOCK_MASK_ACCESS_FS;
WARN_ON_ONCE(fs_access_mask != mask);
new_ruleset->handled_masks.fs |= mask;
new_ruleset->layer.handled.fs |= mask;
}
if (net_access_mask) {
const access_mask_t mask = net_access_mask &
LANDLOCK_MASK_ACCESS_NET;
WARN_ON_ONCE(net_access_mask != mask);
new_ruleset->handled_masks.net |= mask;
new_ruleset->layer.handled.net |= mask;
}
if (scope_mask) {
const access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
WARN_ON_ONCE(scope_mask != mask);
new_ruleset->handled_masks.scope |= mask;
new_ruleset->layer.handled.scope |= mask;
}
if (permission_mask) {
const access_mask_t mask = permission_mask &
LANDLOCK_MASK_PERMISSION;
WARN_ON_ONCE(permission_mask != mask);
new_ruleset->layer.handled.permissions |= mask;
}
return new_ruleset;
}

View File

@ -182,22 +182,29 @@ struct landlock_ruleset {
#endif /* CONFIG_TRACEPOINTS */
/**
* @quiet_masks: Stores the quiet flags for an unmerged ruleset. For a
* @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.
* landlock_hierarchy instead. Its permissions member is unused because
* permission quieting is per member rather than per permission.
*/
struct access_masks quiet_masks;
struct access_masks quiet_access;
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @handled_masks: Contains the subset of filesystem and network actions
* that are handled by this ruleset.
* @quiet_permission: Per-member quiet bitmasks for permission types in
* this ruleset. A denied member whose bit is set here is not submitted
* to audit when this layer denies it.
*/
struct access_masks handled_masks;
struct permission_masks quiet_permission;
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/**
* @layer: Access configuration for this ruleset's single mutable layer.
*/
struct layer_config layer;
};
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 permission_mask);
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
@ -248,4 +255,10 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
refcount_inc(&ruleset->usage);
}
static inline access_mask_t
landlock_get_permission_mask(const struct landlock_ruleset *const ruleset)
{
return ruleset->layer.handled.permissions;
}
#endif /* _SECURITY_LANDLOCK_RULESET_H */

View File

@ -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");

View File

@ -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/sched/signal.h>
@ -30,11 +31,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"
@ -98,7 +101,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
@ -111,8 +117,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_permissions);
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);
@ -123,6 +130,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.permissions);
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.permissions);
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 */
@ -172,7 +191,7 @@ static const struct file_operations ruleset_fops = {
* If the change involves a fix that requires userspace awareness, also update
* the errata documentation in Documentation/userspace-api/landlock.rst .
*/
const int landlock_abi_version = 11;
const int landlock_abi_version = 12;
/**
* sys_landlock_create_ruleset - Create a new ruleset
@ -197,14 +216,13 @@ const int landlock_abi_version = 11;
* returned errors are:
*
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
* - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small
* @size;
* - %EINVAL: unknown @flags, access, scope, or permission, or too small @size;
* - %EINVAL: quiet_access_fs, quiet_access_net, or quiet_scoped is not a
* subset of the corresponding handled_access_fs, handled_access_net, or
* scoped;
* - %E2BIG: @attr or @size inconsistencies;
* - %EFAULT: @attr or @size inconsistencies;
* - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
* - %ENOMSG: all handled access, scope, and permission fields are empty.
*
* .. kernel-doc:: include/uapi/linux/landlock.h
* :identifiers: landlock_create_ruleset_flags
@ -273,16 +291,22 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
ruleset_attr.scoped)
return -EINVAL;
/* Checks permission content (and 32-bits cast). */
if ((ruleset_attr.handled_permissions | LANDLOCK_MASK_PERMISSION) !=
LANDLOCK_MASK_PERMISSION)
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_permissions);
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;
/*
* Emits before anon_inode_getfd() installs the file descriptor, while
@ -377,12 +401,12 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
mask = ruleset->handled_masks.fs;
mask = ruleset->layer.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. */
@ -418,12 +442,12 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
mask = ruleset->handled_masks.net;
mask = ruleset->layer.handled.net;
if ((net_port_attr.allowed_access | mask) != mask)
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. */
@ -435,13 +459,164 @@ 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;
access_mask_t mask;
u64 allowed_types, quiet_types;
int ret;
/*
* 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. */
ret = copy_from_user(&ns_attr, rule_attr, sizeof(ns_attr));
if (ret)
return -EFAULT;
/* Informs about useless rule: empty permissions. */
if (!ns_attr.permissions)
return -ENOMSG;
/*
* The permissions selector must match
* LANDLOCK_PERMISSION_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.permissions != LANDLOCK_PERMISSION_NAMESPACE_USE)
return -EINVAL;
/*
* Checks that permissions match the ruleset constraints. This also
* makes quieting require the category to be handled.
*/
mask = landlock_get_permission_mask(ruleset);
if (!(mask & LANDLOCK_PERMISSION_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().
*/
allowed_types = ns_attr.allowed_namespace_types & CLONE_NS_ALL;
quiet_types = ns_attr.quiet_namespace_types & CLONE_NS_ALL;
mutex_lock(&ruleset->lock);
ruleset->layer.allowed.ns_types |=
landlock_ns_types_to_bits(allowed_types);
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
ruleset->quiet_permission.ns_types |=
landlock_ns_types_to_bits(quiet_types);
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
#ifdef CONFIG_TRACEPOINTS
ruleset->version++;
#endif /* CONFIG_TRACEPOINTS */
trace_landlock_add_rule_namespace(ruleset, flags, ns_attr.permissions,
allowed_types, quiet_types);
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;
access_mask_t mask;
u64 allowed_caps, quiet_caps;
int ret;
/*
* 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. */
ret = copy_from_user(&cap_attr, rule_attr, sizeof(cap_attr));
if (ret)
return -EFAULT;
/* Informs about useless rule: empty permissions. */
if (!cap_attr.permissions)
return -ENOMSG;
/*
* The permissions selector must match
* LANDLOCK_PERMISSION_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.permissions != LANDLOCK_PERMISSION_CAPABILITY_USE)
return -EINVAL;
/*
* Checks that permissions match the ruleset constraints. This also
* makes quieting require the category to be handled.
*/
mask = landlock_get_permission_mask(ruleset);
if (!(mask & LANDLOCK_PERMISSION_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().
*/
allowed_caps = cap_attr.allowed_capabilities & CAP_VALID_MASK;
quiet_caps = cap_attr.quiet_capabilities & CAP_VALID_MASK;
mutex_lock(&ruleset->lock);
ruleset->layer.allowed.caps |= landlock_caps_to_bits(allowed_caps);
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
ruleset->quiet_permission.caps |= landlock_caps_to_bits(quiet_caps);
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
#ifdef CONFIG_TRACEPOINTS
ruleset->version++;
#endif /* CONFIG_TRACEPOINTS */
trace_landlock_add_rule_capability(ruleset, flags, cap_attr.permissions,
allowed_caps, quiet_caps);
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.
*
@ -458,11 +633,21 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* &landlock_path_beneath_attr.allowed_access or
* &landlock_net_port_attr.allowed_access is not a subset of the ruleset
* handled accesses)
* - %EINVAL: A nonzero &landlock_namespace_attr.permissions is not
* %LANDLOCK_PERMISSION_NAMESPACE_USE or is not handled by the ruleset;
* - %EINVAL: A nonzero &landlock_capability_attr.permissions is not
* %LANDLOCK_PERMISSION_CAPABILITY_USE or is not handled by the ruleset;
* - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
* - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no
* quiet access bits set for the corresponding rule type.
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is
* 0) and no flags;
* - %ENOMSG: &landlock_namespace_attr.permissions is 0, or both
* &landlock_namespace_attr.allowed_namespace_types and
* &landlock_namespace_attr.quiet_namespace_types are 0;
* - %ENOMSG: &landlock_capability_attr.permissions is 0, or both
* &landlock_capability_attr.allowed_capabilities and
* &landlock_capability_attr.quiet_capabilities are 0;
* - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
* member of @rule_attr is not a file descriptor as expected;
* - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
@ -495,6 +680,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;
}

View File

@ -62,7 +62,7 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy
*
* @request: Detail of the user space request.
* @youngest_denied: The youngest hierarchy node that denied the access.
* @missing: The final missing access subset, when applicable.
* @missing: The final missing access or permission subset, when applicable.
* @same_exec: Whether the policy subject is the same executable that called
* landlock_restrict_self() for the denying domain, as computed
* by landlock_log_denial().
@ -81,6 +81,31 @@ void landlock_trace_denial(
const access_mask_t missing, const bool same_exec, const bool logged)
{
switch (request->type) {
case LANDLOCK_REQUEST_NAMESPACE:
if (trace_landlock_deny_permission_namespace_enabled()) {
const struct landlock_blockers blockers = {
.access = missing,
.type = request->type,
};
trace_landlock_deny_permission_namespace(
youngest_denied, same_exec, logged, &blockers,
request->audit.u.ns.ns_type,
request->audit.u.ns.ns_id);
}
break;
case LANDLOCK_REQUEST_CAPABILITY:
if (trace_landlock_deny_permission_capability_enabled()) {
const struct landlock_blockers blockers = {
.access = missing,
.type = request->type,
};
trace_landlock_deny_permission_capability(
youngest_denied, same_exec, logged, &blockers,
request->audit.u.cap);
}
break;
case LANDLOCK_REQUEST_FS_ACCESS:
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
if (trace_landlock_deny_access_fs_enabled()) {

View File

@ -31,7 +31,7 @@ static int matches_log_signal(struct __test_metadata *const _metadata,
log_match_len =
snprintf(log_match, sizeof(log_match), log_template, opid);
if (log_match_len > sizeof(log_match))
if (log_match_len >= sizeof(log_match))
return -E2BIG;
return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,

View File

@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
ASSERT_EQ(11, landlock_create_ruleset(NULL, 0,
ASSERT_EQ(12, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
@ -142,6 +142,25 @@ TEST(errata)
ASSERT_EQ(EINVAL, errno);
}
#define PERMISSION_LAST LANDLOCK_PERMISSION_CAPABILITY_USE
TEST(ruleset_with_unknown_permission)
{
__u64 permission_mask;
for (permission_mask = 1ULL << 63; permission_mask != PERMISSION_LAST;
permission_mask >>= 1) {
struct landlock_ruleset_attr ruleset_attr = {
.handled_permissions = permission_mask,
};
/* Unknown handled_permissions 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)
{

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,12 @@
#include <arpa/inet.h>
#include <errno.h>
#include <linux/nsfs.h>
#include <linux/securebits.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
@ -130,6 +132,17 @@ 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. */
static bool __maybe_unused is_in_init_user_ns(void)
{
struct stat user_ns;
if (stat("/proc/self/ns/user", &user_ns))
return false;
return user_ns.st_ino == USER_NS_INIT_INO;
}
/* Receives an FD from a UNIX socket. Returns the received FD, or -errno. */
static int __maybe_unused recv_fd(int usock)
{

View File

@ -5,6 +5,7 @@ CONFIG_CGROUP_SCHED=y
CONFIG_ENABLE_DEFAULT_TRACERS=y
CONFIG_FTRACE=y
CONFIG_INET=y
CONFIG_IPC_NS=y
CONFIG_IPV6=y
CONFIG_KEYS=y
CONFIG_MPTCP=y
@ -12,10 +13,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

View File

@ -57,13 +57,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)
{
@ -2628,9 +2621,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);

View File

@ -2878,7 +2878,7 @@ static int matches_auditlog(const int audit_fd, const char *const blockers,
log_match_len = snprintf(log_match, sizeof(log_match),
log_with_addrport_tmpl, blockers,
dir_addr, addr, dir_port, port);
if (log_match_len > sizeof(log_match))
if (log_match_len >= sizeof(log_match))
return -E2BIG;
return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
@ -3173,7 +3173,7 @@ static int matches_log_connect_bound(int audit_fd, const char *const blockers,
log_match_len = snprintf(log_match, sizeof(log_match), log_template,
blockers, addr, lport, addr, dport);
if (log_match_len > sizeof(log_match))
if (log_match_len >= sizeof(log_match))
return -E2BIG;
return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,

File diff suppressed because it is too large Load Diff

View File

@ -305,7 +305,7 @@ static int matches_log_ptrace(struct __test_metadata *const _metadata,
log_match_len =
snprintf(log_match, sizeof(log_match), log_template, opid);
if (log_match_len > sizeof(log_match))
if (log_match_len >= sizeof(log_match))
return -E2BIG;
return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,

View File

@ -31,6 +31,10 @@
TRACEFS_LANDLOCK_DIR "/landlock_add_rule_path_beneath/enable"
#define TRACEFS_ADD_RULE_NET_PORT_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net_port/enable"
#define TRACEFS_ADD_RULE_NAMESPACE_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_add_rule_namespace/enable"
#define TRACEFS_ADD_RULE_CAPABILITY_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_add_rule_capability/enable"
#define TRACEFS_CHECK_RULE_FS_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_check_rule_inode/enable"
#define TRACEFS_CHECK_RULE_NET_ENABLE \
@ -39,6 +43,10 @@
TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable"
#define TRACEFS_DENY_ACCESS_NET_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_deny_access_net/enable"
#define TRACEFS_DENY_PERMISSION_NAMESPACE_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_deny_permission_namespace/enable"
#define TRACEFS_DENY_PERMISSION_CAPABILITY_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_deny_permission_capability/enable"
#define TRACEFS_DENY_PTRACE_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_deny_ptrace/enable"
#define TRACEFS_DENY_SCOPE_SIGNAL_ENABLE \
@ -95,13 +103,36 @@
"access_rights=[a-z_|]* " \
"port=[0-9]\\+$"
#define REGEX_ADD_RULE_NAMESPACE_VERSION(task, version) \
TRACE_PREFIX(task) \
"landlock_add_rule_namespace: " \
"ruleset=[0-9a-f]\\+\\." version " " \
"permissions=[a-z._|]* " \
"allowed_namespace_types=0x[0-9a-f]\\+ " \
"quiet_namespace_types=0x[0-9a-f]\\+$"
#define REGEX_ADD_RULE_NAMESPACE(task) \
REGEX_ADD_RULE_NAMESPACE_VERSION(task, "[0-9]\\+")
#define REGEX_ADD_RULE_CAPABILITY_VERSION(task, version) \
TRACE_PREFIX(task) \
"landlock_add_rule_capability: " \
"ruleset=[0-9a-f]\\+\\." version " " \
"permissions=[a-z._|]* " \
"allowed_capabilities=0x[0-9a-f]\\+ " \
"quiet_capabilities=0x[0-9a-f]\\+$"
#define REGEX_ADD_RULE_CAPABILITY(task) \
REGEX_ADD_RULE_CAPABILITY_VERSION(task, "[0-9]\\+")
#define REGEX_CREATE_RULESET(task) \
TRACE_PREFIX(task) \
"landlock_create_ruleset: " \
"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
"handled_fs=[a-z_|]* " \
"handled_net=[a-z_|]* " \
"scoped=[a-z_|]*$"
"scoped=[a-z_|]* " \
"handled_permissions=[a-z._|]*$"
#define REGEX_CREATE_DOMAIN(task) \
TRACE_PREFIX(task) \
@ -147,6 +178,25 @@
"blockers=[a-z_|]* " \
"port=-\\?[0-9]\\+$"
#define REGEX_DENY_PERMISSION_NAMESPACE(task) \
TRACE_PREFIX(task) \
"landlock_deny_permission_namespace: " \
"domain=[0-9a-f]\\+ " \
"same_exec=[01] " \
"logged=[01] " \
"blockers=[a-z_|]* " \
"namespace_type=0x[0-9a-f]\\+ " \
"namespace_id=[0-9]\\+$"
#define REGEX_DENY_PERMISSION_CAPABILITY(task) \
TRACE_PREFIX(task) \
"landlock_deny_permission_capability: " \
"domain=[0-9a-f]\\+ " \
"same_exec=[01] " \
"logged=[01] " \
"blockers=[a-z_|]* " \
"capability=[0-9]\\+$"
#define REGEX_DENY_PTRACE(task) \
TRACE_PREFIX(task) \
"landlock_deny_ptrace: " \

View File

@ -148,7 +148,7 @@ TEST_F(trace, no_trace_when_disabled)
/*
* Verifies that landlock_create_ruleset emits a trace event with the correct
* handled access masks.
* handled access and permission masks.
*/
TEST_F(trace, create_ruleset)
{
@ -186,6 +186,12 @@ TEST_F(trace, create_ruleset)
"handled_net", field, sizeof(field)));
EXPECT_STREQ("bind_tcp", field);
/* Verify that no permission is handled. */
EXPECT_EQ(0, tracefs_extract_field(
buf, REGEX_CREATE_RULESET(TRACE_TASK),
"handled_permissions", field, sizeof(field)));
EXPECT_STREQ("", field);
/* Verify version is 0 at creation (no rules added yet). */
EXPECT_EQ(0,
tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),

View File

@ -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);
}