mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
landlock: Add documentation for capability and namespace restrictions
Document the two new Landlock permission categories, introduced with ABI 12, in the userspace API guide, admin guide, trace-event guide, UAPI comments, and kernel security documentation. The kernel security documentation generalizes the existing per-object access-rights framing into three restriction models (handled_access_*, handled_permissions, scoped) and states criteria for choosing among them, so future permission or scope additions have a documented place to land. Cc: Christian Brauner <brauner@kernel.org> Cc: Günther Noack <gnoack@google.com> Cc: Paul Moore <paul@paul-moore.com> Cc: Serge E. Hallyn <serge@hallyn.com> Link: https://patch.msgid.link/20261014082718.1435842-9-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
parent
b3dafbe7ed
commit
5d2c5cd87a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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' > \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -278,11 +278,12 @@ 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 shared fields as domain, same_exec,
|
||||
* 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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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,16 +66,17 @@ 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;
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user