mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
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:
commit
3815f7d55a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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
159
security/landlock/cap.c
Normal 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
48
security/landlock/cap.h
Normal 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 */
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
241
security/landlock/ns.c
Normal 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
18
security/landlock/ns.h
Normal 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 */
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
1370
tools/testing/selftests/landlock/cap_test.c
Normal file
1370
tools/testing/selftests/landlock/cap_test.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
2647
tools/testing/selftests/landlock/ns_test.c
Normal file
2647
tools/testing/selftests/landlock/ns_test.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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: " \
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user