mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 01:32:21 +02:00
Landlock update for v7.2-rc1
-----BEGIN PGP SIGNATURE----- iIYEABYKAC4WIQSVyBthFV4iTW/VU1/l49DojIL20gUCajT1cxAcbWljQGRpZ2lr b2QubmV0AAoJEOXj0OiMgvbSAWcBAKtN0xWVNJM6jXj5q2EHO5vOFTvoLD9KNLsK 9FJoWN3dAQCWsuMI1OBovh6JguJ/jvwB2chwwa5NGxanhQ1LP9NyCg== =4cLz -----END PGP SIGNATURE----- Merge tag 'landlock-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux Pull landlock updates from Mickaël Salaün: "This adds new Landlock access rights to control UDP bind and connect/send operations, and a new "quiet" feature to mute specific specific audit logs (and other future observability events). A few commits also fix Landlock issues" * tag 'landlock-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux: (24 commits) selftests/landlock: Add tests for invalid use of quiet flag selftests/landlock: Add tests for quiet flag with scope selftests/landlock: Add tests for quiet flag with net rules selftests/landlock: Add tests for quiet flag with fs rules selftests/landlock: Replace hard-coded 16 with a constant samples/landlock: Add quiet flag support to sandboxer landlock: Suppress logging when quiet flag is present landlock: Add API support and docs for the quiet flags landlock: Add a place for flags to layer rules landlock: Add documentation for UDP support samples/landlock: Add sandboxer UDP access control selftests/landlock: Add tests for UDP send selftests/landlock: Add tests for UDP bind/connect landlock: Add UDP send+connect access control landlock: Add UDP bind() access control landlock: Fix unmarked concurrent access to socket family selftests/landlock: Explicitly disable audit in teardowns selftests/landlock: Test SCOPE_SIGNAL on the SIGIO/fowner pgid path landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path landlock: Demonstrate best-effort allowed_access filtering ...
This commit is contained in:
commit
5e2e14749c
|
|
@ -6,7 +6,7 @@ Landlock: system-wide management
|
|||
================================
|
||||
|
||||
:Author: Mickaël Salaün
|
||||
:Date: January 2026
|
||||
:Date: June 2026
|
||||
|
||||
Landlock can leverage the audit framework to log events.
|
||||
|
||||
|
|
@ -19,8 +19,10 @@ 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). Landlock logs can also be masked
|
||||
thanks to audit rules. Landlock can generate 2 audit record types.
|
||||
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.
|
||||
|
||||
Record types
|
||||
------------
|
||||
|
|
@ -54,6 +56,8 @@ AUDIT_LANDLOCK_ACCESS
|
|||
**net.*** - Network access rights (ABI 4+):
|
||||
- net.bind_tcp - TCP port binding was denied
|
||||
- net.connect_tcp - TCP connection was denied
|
||||
- net.bind_udp - UDP port binding was denied
|
||||
- net.connect_send_udp - UDP connection and send was denied
|
||||
|
||||
**scope.*** - IPC scoping restrictions (ABI 6+):
|
||||
- scope.abstract_unix_socket - Abstract UNIX socket connection denied
|
||||
|
|
@ -172,7 +176,8 @@ 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 if we can fix the sandboxed
|
||||
- with sys_landlock_restrict_self()'s flags, or
|
||||
``LANDLOCK_ADD_RULE_QUIET`` (ABI 10+) if we can fix the sandboxed
|
||||
programs,
|
||||
- or with audit rules (see :manpage:`auditctl(8)`).
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Landlock: unprivileged access control
|
|||
=====================================
|
||||
|
||||
:Author: Mickaël Salaün
|
||||
:Date: March 2026
|
||||
:Date: June 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
|
||||
|
|
@ -40,8 +40,8 @@ Filesystem rules
|
|||
and the related filesystem actions are defined with
|
||||
`filesystem access rights`.
|
||||
|
||||
Network rules (since ABI v4)
|
||||
For these rules, the object is a TCP port,
|
||||
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`.
|
||||
|
||||
Defining and enforcing a security policy
|
||||
|
|
@ -49,11 +49,11 @@ Defining and enforcing a security policy
|
|||
|
||||
We first need to define the ruleset that will contain our rules.
|
||||
|
||||
For this example, the ruleset will contain rules that only allow filesystem
|
||||
read actions and establish a specific TCP connection. Filesystem write
|
||||
actions and other TCP actions will be denied.
|
||||
For this example, the ruleset will contain rules that only allow some
|
||||
filesystem read actions and some specific UDP and TCP actions. Filesystem
|
||||
write actions and other TCP/UDP actions will be denied.
|
||||
|
||||
The ruleset then needs to handle both these kinds of actions. This is
|
||||
The ruleset then needs to handle all these kinds of actions. This is
|
||||
required for backward and forward compatibility (i.e. the kernel and user
|
||||
space may not know each other's supported restrictions), hence the need
|
||||
to be explicit about the denied-by-default access rights.
|
||||
|
|
@ -81,7 +81,9 @@ to be explicit about the denied-by-default access rights.
|
|||
LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
|
||||
.handled_access_net =
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP |
|
||||
LANDLOCK_ACCESS_NET_BIND_UDP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
|
||||
.scoped =
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
||||
LANDLOCK_SCOPE_SIGNAL,
|
||||
|
|
@ -132,6 +134,12 @@ version, and only use the available subset of access rights:
|
|||
case 6 ... 8:
|
||||
/* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
|
||||
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
|
||||
__attribute__((fallthrough));
|
||||
case 9:
|
||||
/* Removes LANDLOCK_ACCESS_NET_*_UDP for ABI < 10 */
|
||||
ruleset_attr.handled_access_net &=
|
||||
~(LANDLOCK_ACCESS_NET_BIND_UDP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
|
||||
}
|
||||
|
||||
This enables the creation of an inclusive ruleset that will contain our rules.
|
||||
|
|
@ -155,7 +163,7 @@ this file descriptor.
|
|||
|
||||
.. code-block:: c
|
||||
|
||||
int err;
|
||||
int err = 0;
|
||||
struct landlock_path_beneath_attr path_beneath = {
|
||||
.allowed_access =
|
||||
LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
|
|
@ -163,38 +171,76 @@ this file descriptor.
|
|||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
};
|
||||
|
||||
path_beneath.parent_fd = open("/usr", O_PATH | O_CLOEXEC);
|
||||
if (path_beneath.parent_fd < 0) {
|
||||
perror("Failed to open file");
|
||||
close(ruleset_fd);
|
||||
return 1;
|
||||
}
|
||||
err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
|
||||
&path_beneath, 0);
|
||||
close(path_beneath.parent_fd);
|
||||
if (err) {
|
||||
perror("Failed to update ruleset");
|
||||
close(ruleset_fd);
|
||||
return 1;
|
||||
path_beneath.allowed_access &= ruleset_attr.handled_access_fs;
|
||||
if (path_beneath.allowed_access) {
|
||||
path_beneath.parent_fd = open("/usr", O_PATH | O_CLOEXEC);
|
||||
if (path_beneath.parent_fd < 0) {
|
||||
perror("Failed to open file");
|
||||
close(ruleset_fd);
|
||||
return 1;
|
||||
}
|
||||
err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
|
||||
&path_beneath, 0);
|
||||
close(path_beneath.parent_fd);
|
||||
if (err) {
|
||||
perror("Failed to update ruleset");
|
||||
close(ruleset_fd);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
It may also be required to create rules following the same logic as explained
|
||||
for the ruleset creation, by filtering access rights according to the Landlock
|
||||
ABI version. In this example, this is not required because all of the requested
|
||||
``allowed_access`` rights are already available in ABI 1.
|
||||
As shown above, masking the rule's ``allowed_access`` against the ruleset's
|
||||
``handled_access_*`` is the recommended best-effort pattern: rights the running
|
||||
kernel does not support are dropped (the compatibility switch above already
|
||||
cleared them in ``handled_access_*``), and the rule is skipped if no supported
|
||||
right remains.
|
||||
|
||||
For network access-control, we can add a set of rules that allow to use a port
|
||||
number for a specific action: HTTPS connections.
|
||||
For network access-control, we will add a set of rules to allow DNS
|
||||
queries, which requires both UDP and TCP. For TCP, we need to allow
|
||||
outbound connections to port 53, which can be handled and granted starting
|
||||
with ABI 4:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct landlock_net_port_attr net_port = {
|
||||
struct landlock_net_port_attr tcp_conn = {
|
||||
.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
.port = 443,
|
||||
.port = 53,
|
||||
};
|
||||
|
||||
err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&net_port, 0);
|
||||
tcp_conn.allowed_access &= ruleset_attr.handled_access_net;
|
||||
if (tcp_conn.allowed_access)
|
||||
err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&tcp_conn, 0);
|
||||
|
||||
We also need to be able to send UDP datagrams to port 53, which requires
|
||||
granting ``LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP``. Since our DNS client will
|
||||
emit datagrams without explicitly binding to a specific source port, its UDP
|
||||
socket will automatically bind an ephemeral port. To allow this behaviour,
|
||||
we also need to grant ``LANDLOCK_ACCESS_NET_BIND_UDP`` on port 0, as if
|
||||
the program explicitly called :manpage:`bind(2)` on port 0.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct landlock_net_port_attr udp_send = {
|
||||
.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
|
||||
.port = 53,
|
||||
};
|
||||
|
||||
udp_send.allowed_access &= ruleset_attr.handled_access_net;
|
||||
if (udp_send.allowed_access)
|
||||
err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&udp_send, 0);
|
||||
[...]
|
||||
|
||||
struct landlock_net_port_attr udp_bind = {
|
||||
.allowed_access = LANDLOCK_ACCESS_NET_BIND_UDP,
|
||||
.port = 0,
|
||||
};
|
||||
|
||||
udp_bind.allowed_access &= ruleset_attr.handled_access_net;
|
||||
if (udp_bind.allowed_access)
|
||||
err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&udp_bind, 0);
|
||||
|
||||
When passing a non-zero ``flags`` argument to ``landlock_restrict_self()``, a
|
||||
similar backwards compatibility check is needed for the restrict flags
|
||||
|
|
@ -228,7 +274,7 @@ similar backwards compatibility check is needed for the restrict flags
|
|||
The next step is to restrict the current thread from gaining more privileges
|
||||
(e.g. through a SUID binary). We now have a ruleset with the first rule
|
||||
allowing read and execute access to ``/usr`` while denying all other handled
|
||||
accesses for the filesystem, and a second rule allowing HTTPS connections.
|
||||
accesses for the filesystem, and two more rules allowing DNS queries.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
|
|
@ -716,6 +762,33 @@ Starting with the Landlock ABI version 9, it is possible to restrict
|
|||
connections to pathname UNIX domain sockets (:manpage:`unix(7)`) using
|
||||
the new ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` right.
|
||||
|
||||
UDP bind, connect and send* (ABI < 10)
|
||||
--------------------------------------
|
||||
|
||||
Starting with the Landlock ABI version 10, it is possible to restrict
|
||||
setting the local port of UDP sockets with the
|
||||
``LANDLOCK_ACCESS_NET_BIND_UDP`` right. This includes restricting the
|
||||
ability to trigger autobind of an ephemeral port by the kernel by e.g.
|
||||
sending a first datagram or setting the remote peer of a socket.
|
||||
The ``LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP`` right controls setting the
|
||||
remote port of UDP sockets (via :manpage:`connect(2)`), and sending
|
||||
datagrams to an explicit remote port (ignoring any destination set on
|
||||
UDP sockets, via e.g. :manpage:`sendto(2)`).
|
||||
|
||||
Quiet rule flag (ABI < 10)
|
||||
--------------------------
|
||||
|
||||
Starting with the Landlock ABI version 10, it is possible to selectively
|
||||
suppress logs for specific denied accesses on a per-object basis with
|
||||
the ``LANDLOCK_ADD_RULE_QUIET`` flag of sys_landlock_add_rule(), in
|
||||
combination with the ``quiet_access_fs`` and ``quiet_access_net`` fields
|
||||
of struct landlock_ruleset_attr. It is also now possible to suppress
|
||||
logs for scope accesses via the ``quiet_scoped`` field of struct
|
||||
landlock_ruleset_attr. The object is marked as quiet within a ruleset
|
||||
when at least one sys_landlock_add_rule() call is made for it with the
|
||||
``LANDLOCK_ADD_RULE_QUIET`` flag, additional add-rule calls for the same
|
||||
object without this flag do not clear it.
|
||||
|
||||
.. _kernel_support:
|
||||
|
||||
Kernel support
|
||||
|
|
@ -778,10 +851,10 @@ the boot loader.
|
|||
Network support
|
||||
---------------
|
||||
|
||||
To be able to explicitly allow TCP operations (e.g., adding a network rule with
|
||||
``LANDLOCK_ACCESS_NET_BIND_TCP``), the kernel must support TCP
|
||||
To be able to explicitly allow TCP or UDP operations (e.g., adding a network rule with
|
||||
``LANDLOCK_ACCESS_NET_BIND_TCP``), the kernel must support the TCP/IP protocol suite
|
||||
(``CONFIG_INET=y``). Otherwise, sys_landlock_add_rule() returns an
|
||||
``EAFNOSUPPORT`` error, which can safely be ignored because this kind of TCP
|
||||
``EAFNOSUPPORT`` error, which can safely be ignored because this kind of TCP or UDP
|
||||
operation is already not possible.
|
||||
|
||||
Questions and answers
|
||||
|
|
|
|||
|
|
@ -32,6 +32,19 @@
|
|||
* *handle* a wide range or all access rights that they know about at build time
|
||||
* (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
|
||||
* 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.
|
||||
*
|
||||
* @quiet_access_fs, @quiet_access_net and @quiet_scoped must be a subset of
|
||||
* @handled_access_fs, @handled_access_net and @scoped respectively.
|
||||
*
|
||||
* This structure can grow in future Landlock versions.
|
||||
*/
|
||||
struct landlock_ruleset_attr {
|
||||
|
|
@ -51,6 +64,20 @@ struct landlock_ruleset_attr {
|
|||
* resources (e.g. IPCs).
|
||||
*/
|
||||
__u64 scoped;
|
||||
/**
|
||||
* @quiet_access_fs: Bitmask of filesystem actions which should not be
|
||||
* logged if 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.
|
||||
*/
|
||||
__u64 quiet_access_net;
|
||||
/**
|
||||
* @quiet_scoped: Bitmask of scoped actions which should not be logged.
|
||||
*/
|
||||
__u64 quiet_scoped;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -69,6 +96,39 @@ struct landlock_ruleset_attr {
|
|||
#define LANDLOCK_CREATE_RULESET_ERRATA (1U << 1)
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
* DOC: landlock_add_rule_flags
|
||||
*
|
||||
* **Flags**
|
||||
*
|
||||
* %LANDLOCK_ADD_RULE_QUIET
|
||||
* Together with the quiet_* fields in struct landlock_ruleset_attr,
|
||||
* this flag controls whether Landlock will log audit messages when
|
||||
* access to the objects covered by this rule is denied by this layer.
|
||||
*
|
||||
* If logging is enabled, when Landlock denies an access, it will
|
||||
* suppress the log if all of the following are true:
|
||||
*
|
||||
* - this layer is the innermost layer that denied the access;
|
||||
* - all accesses denied by this layer are part of the quiet_* fields
|
||||
* in the related struct landlock_ruleset_attr;
|
||||
* - the object (or one of its parents, for filesystem rules) is
|
||||
* marked as "quiet" via %LANDLOCK_ADD_RULE_QUIET.
|
||||
*
|
||||
* Because logging is only suppressed by a layer if the layer denies
|
||||
* access, a sandboxed program cannot use this flag to "hide" access
|
||||
* denials, without denying itself the access in the first place.
|
||||
*
|
||||
* The effect of this flag does not depend on the value of
|
||||
* allowed_access in the passed in rule_attr. When this flag is
|
||||
* present, the caller is also allowed to pass in an empty
|
||||
* allowed_access.
|
||||
*/
|
||||
|
||||
/* clang-format off */
|
||||
#define LANDLOCK_ADD_RULE_QUIET (1U << 0)
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
* DOC: landlock_restrict_self_flags
|
||||
*
|
||||
|
|
@ -200,10 +260,10 @@ struct landlock_net_port_attr {
|
|||
* (also used for IPv6), and within that range, on a per-socket basis
|
||||
* with ``setsockopt(IP_LOCAL_PORT_RANGE)``.
|
||||
*
|
||||
* A Landlock rule with port 0 and the %LANDLOCK_ACCESS_NET_BIND_TCP
|
||||
* right means that requesting to bind on port 0 is allowed and it will
|
||||
* automatically translate to binding on a kernel-assigned ephemeral
|
||||
* port.
|
||||
* A Landlock rule with port 0 and the %LANDLOCK_ACCESS_NET_BIND_TCP or
|
||||
* %LANDLOCK_ACCESS_NET_BIND_UDP right means that requesting to bind on
|
||||
* port 0 is allowed and it will automatically translate to binding on a
|
||||
* kernel-assigned ephemeral port.
|
||||
*/
|
||||
__u64 port;
|
||||
};
|
||||
|
|
@ -373,10 +433,39 @@ struct landlock_net_port_attr {
|
|||
* port. Support added in Landlock ABI version 4.
|
||||
* - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect TCP sockets to the given
|
||||
* remote port. Support added in Landlock ABI version 4.
|
||||
*
|
||||
* And similarly for UDP port numbers:
|
||||
*
|
||||
* - %LANDLOCK_ACCESS_NET_BIND_UDP: Bind UDP sockets to the given local
|
||||
* port. Support added in Landlock ABI version 10.
|
||||
* - %LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP: Set the remote port of UDP
|
||||
* sockets to the given port, or send datagrams to the given remote port
|
||||
* ignoring any destination pre-set on a socket. Support added in
|
||||
* Landlock ABI version 10.
|
||||
*
|
||||
* .. note:: Setting a remote address or sending a first datagram
|
||||
* auto-binds UDP sockets to an ephemeral local source port if not
|
||||
* already bound. To allow this if both %LANDLOCK_ACCESS_NET_BIND_UDP
|
||||
* and %LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP are handled, you need to
|
||||
* either:
|
||||
*
|
||||
* - use a socket already bound to a port before the ruleset started
|
||||
* being enforced;
|
||||
* - or grant %LANDLOCK_ACCESS_NET_BIND_UDP on port 0, meaning "any
|
||||
* port in the ephemeral port range";
|
||||
* - or grant %LANDLOCK_ACCESS_NET_BIND_UDP on a specific port, and
|
||||
* call :manpage:`bind(2)` on that port before trying to
|
||||
* :manpage:`connect(2)` or send datagrams.
|
||||
*
|
||||
* .. note:: Sending datagrams to an ``AF_UNSPEC`` destination address
|
||||
* family is not supported for IPv6 UDP sockets: you will need to use a
|
||||
* ``NULL`` address instead.
|
||||
*/
|
||||
/* clang-format off */
|
||||
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
|
||||
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
|
||||
#define LANDLOCK_ACCESS_NET_BIND_UDP (1ULL << 2)
|
||||
#define LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP (1ULL << 3)
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -58,10 +58,15 @@ static inline int landlock_restrict_self(const int ruleset_fd,
|
|||
|
||||
#define ENV_FS_RO_NAME "LL_FS_RO"
|
||||
#define ENV_FS_RW_NAME "LL_FS_RW"
|
||||
#define ENV_FS_QUIET_NAME "LL_FS_QUIET"
|
||||
#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_SCOPED_NAME "LL_SCOPED"
|
||||
#define ENV_QUIET_ACCESS_NAME "LL_QUIET_ACCESS"
|
||||
#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
|
||||
#define ENV_UDP_BIND_NAME "LL_UDP_BIND"
|
||||
#define ENV_UDP_CONNECT_SEND_NAME "LL_UDP_CONNECT_SEND"
|
||||
#define ENV_DELIMITER ":"
|
||||
|
||||
static int str2num(const char *numstr, __u64 *num_dst)
|
||||
|
|
@ -117,7 +122,7 @@ static int parse_path(char *env_path, const char ***const path_list)
|
|||
/* clang-format on */
|
||||
|
||||
static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
|
||||
const __u64 allowed_access)
|
||||
const __u64 allowed_access, __u32 flags)
|
||||
{
|
||||
int num_paths, i, ret = 1;
|
||||
char *env_path_name;
|
||||
|
|
@ -167,7 +172,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
|
|||
if (!S_ISDIR(statbuf.st_mode))
|
||||
path_beneath.allowed_access &= ACCESS_FILE;
|
||||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
|
||||
&path_beneath, 0)) {
|
||||
&path_beneath, flags)) {
|
||||
fprintf(stderr,
|
||||
"Failed to update the ruleset with \"%s\": %s\n",
|
||||
path_list[i], strerror(errno));
|
||||
|
|
@ -185,7 +190,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
|
|||
}
|
||||
|
||||
static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
|
||||
const __u64 allowed_access)
|
||||
const __u64 allowed_access, __u32 flags)
|
||||
{
|
||||
int ret = 1;
|
||||
char *env_port_name, *env_port_name_next, *strport;
|
||||
|
|
@ -213,7 +218,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
|
|||
}
|
||||
net_port.port = port;
|
||||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&net_port, 0)) {
|
||||
&net_port, flags)) {
|
||||
fprintf(stderr,
|
||||
"Failed to update the ruleset with port \"%llu\": %s\n",
|
||||
net_port.port, strerror(errno));
|
||||
|
|
@ -301,7 +306,70 @@ static bool check_ruleset_scope(const char *const env_var,
|
|||
|
||||
/* clang-format on */
|
||||
|
||||
#define LANDLOCK_ABI_LAST 9
|
||||
/*
|
||||
* Parses ENV_QUIET_ACCESS_NAME and sets the quiet_access_fs, quiet_access_net
|
||||
* and quiet_scoped masks of @ruleset_attr accordingly.
|
||||
*/
|
||||
static int add_quiet_access(const char *const env_var,
|
||||
struct landlock_ruleset_attr *const ruleset_attr)
|
||||
{
|
||||
char *env_quiet_access, *env_quiet_access_next, *str_access;
|
||||
|
||||
env_quiet_access = getenv(env_var);
|
||||
if (!env_quiet_access)
|
||||
return 0;
|
||||
|
||||
env_quiet_access = strdup(env_quiet_access);
|
||||
env_quiet_access_next = env_quiet_access;
|
||||
unsetenv(env_var);
|
||||
|
||||
while ((str_access = strsep(&env_quiet_access_next, ENV_DELIMITER))) {
|
||||
if (strcmp(str_access, "") == 0)
|
||||
continue;
|
||||
else if (strcmp(str_access, "all") == 0) {
|
||||
ruleset_attr->quiet_access_fs =
|
||||
ruleset_attr->handled_access_fs;
|
||||
ruleset_attr->quiet_access_net =
|
||||
ruleset_attr->handled_access_net;
|
||||
ruleset_attr->quiet_scoped = ruleset_attr->scoped;
|
||||
} else if (strcmp(str_access, "read") == 0)
|
||||
ruleset_attr->quiet_access_fs |= ACCESS_FS_ROUGHLY_READ;
|
||||
else if (strcmp(str_access, "write") == 0)
|
||||
ruleset_attr->quiet_access_fs |=
|
||||
ACCESS_FS_ROUGHLY_WRITE;
|
||||
else if (strcmp(str_access, "tcp_bind") == 0)
|
||||
ruleset_attr->quiet_access_net |=
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP;
|
||||
else if (strcmp(str_access, "tcp_connect") == 0)
|
||||
ruleset_attr->quiet_access_net |=
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP;
|
||||
else if (strcmp(str_access, "udp_bind") == 0)
|
||||
ruleset_attr->quiet_access_net |=
|
||||
LANDLOCK_ACCESS_NET_BIND_UDP;
|
||||
else if (strcmp(str_access, "udp_connect") == 0)
|
||||
ruleset_attr->quiet_access_net |=
|
||||
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
|
||||
else if (strcmp(str_access, "abstract_unix_socket") == 0)
|
||||
ruleset_attr->quiet_scoped |=
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
|
||||
else if (strcmp(str_access, "signal") == 0)
|
||||
ruleset_attr->quiet_scoped |= LANDLOCK_SCOPE_SIGNAL;
|
||||
else {
|
||||
fprintf(stderr, "Unknown quiet access \"%s\"\n",
|
||||
str_access);
|
||||
free(env_quiet_access);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
free(env_quiet_access);
|
||||
ruleset_attr->quiet_access_fs &= ruleset_attr->handled_access_fs;
|
||||
ruleset_attr->quiet_access_net &= ruleset_attr->handled_access_net;
|
||||
ruleset_attr->quiet_scoped &= ruleset_attr->scoped;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define LANDLOCK_ABI_LAST 10
|
||||
|
||||
#define XSTR(s) #s
|
||||
#define STR(s) XSTR(s)
|
||||
|
|
@ -324,18 +392,37 @@ static const char help[] =
|
|||
"means an empty list):\n"
|
||||
"* " ENV_TCP_BIND_NAME ": ports allowed to bind (server)\n"
|
||||
"* " ENV_TCP_CONNECT_NAME ": ports allowed to connect (client)\n"
|
||||
"* " ENV_UDP_BIND_NAME ": local UDP ports allowed to bind (server: "
|
||||
"prepare to receive on port / client: set as source port)\n"
|
||||
"* " 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_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"
|
||||
"\n"
|
||||
"A sandboxer should not log denied access requests to avoid spamming logs, "
|
||||
"but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
|
||||
ENV_FS_QUIET_NAME " and " ENV_NET_QUIET_NAME ", both optional, can then be used "
|
||||
"to make access to some denied paths or network ports not trigger audit logging.\n"
|
||||
ENV_QUIET_ACCESS_NAME " can be used to specify which accesses should be quieted "
|
||||
"(required when " ENV_FS_QUIET_NAME " or " ENV_NET_QUIET_NAME " is set):\n"
|
||||
" - \"all\" to quiet all of the accesses below\n"
|
||||
" - \"read\" to quiet all file/dir read accesses\n"
|
||||
" - \"write\" to quiet all file/dir write accesses\n"
|
||||
" - \"tcp_bind\" to quiet tcp bind denials\n"
|
||||
" - \"tcp_connect\" to quiet tcp connect denials\n"
|
||||
" - \"udp_bind\" to quiet udp bind denials\n"
|
||||
" - \"udp_connect\" to quiet udp connect / send denials\n"
|
||||
" - \"abstract_unix_socket\" to quiet abstract unix socket denials\n"
|
||||
" - \"signal\" to quiet signal denials\n"
|
||||
"\n"
|
||||
"Example:\n"
|
||||
ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
|
||||
ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
|
||||
ENV_TCP_BIND_NAME "=\"9418\" "
|
||||
ENV_TCP_CONNECT_NAME "=\"80:443\" "
|
||||
ENV_UDP_CONNECT_SEND_NAME "=\"53\" "
|
||||
ENV_SCOPED_NAME "=\"a:s\" "
|
||||
"%1$s bash -i\n"
|
||||
"\n"
|
||||
|
|
@ -356,10 +443,16 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = access_fs_rw,
|
||||
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP |
|
||||
LANDLOCK_ACCESS_NET_BIND_UDP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
||||
LANDLOCK_SCOPE_SIGNAL,
|
||||
.quiet_access_fs = 0,
|
||||
.quiet_access_net = 0,
|
||||
.quiet_scoped = 0,
|
||||
};
|
||||
bool quiet_supported = true;
|
||||
int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
|
||||
int set_restrict_flags = 0;
|
||||
|
||||
|
|
@ -444,6 +537,15 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
/* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
|
||||
ruleset_attr.handled_access_fs &=
|
||||
~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
|
||||
__attribute__((fallthrough));
|
||||
case 9:
|
||||
/* Removes UDP support for ABI < 10 */
|
||||
ruleset_attr.handled_access_net &=
|
||||
~(LANDLOCK_ACCESS_NET_BIND_UDP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
|
||||
/* Removes quiet flags for ABI < 10 later on. */
|
||||
quiet_supported = false;
|
||||
|
||||
/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
|
||||
fprintf(stderr,
|
||||
"Hint: You should update the running kernel "
|
||||
|
|
@ -475,6 +577,18 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
ruleset_attr.handled_access_net &=
|
||||
~LANDLOCK_ACCESS_NET_CONNECT_TCP;
|
||||
}
|
||||
/* Removes UDP bind access control if not supported by a user. */
|
||||
env_port_name = getenv(ENV_UDP_BIND_NAME);
|
||||
if (!env_port_name) {
|
||||
ruleset_attr.handled_access_net &=
|
||||
~LANDLOCK_ACCESS_NET_BIND_UDP;
|
||||
}
|
||||
/* Removes UDP connect/send access control if not supported by a user. */
|
||||
env_port_name = getenv(ENV_UDP_CONNECT_SEND_NAME);
|
||||
if (!env_port_name) {
|
||||
ruleset_attr.handled_access_net &=
|
||||
~LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
|
||||
}
|
||||
|
||||
if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
|
||||
return 1;
|
||||
|
|
@ -497,6 +611,25 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
unsetenv(ENV_FORCE_LOG_NAME);
|
||||
}
|
||||
|
||||
/* Set the quiet access masks. */
|
||||
if (quiet_supported) {
|
||||
if ((getenv(ENV_FS_QUIET_NAME) || getenv(ENV_NET_QUIET_NAME)) &&
|
||||
!getenv(ENV_QUIET_ACCESS_NAME)) {
|
||||
fprintf(stderr,
|
||||
"%s must be set (e.g. to \"all\") when %s or %s is used\n",
|
||||
ENV_QUIET_ACCESS_NAME, ENV_FS_QUIET_NAME,
|
||||
ENV_NET_QUIET_NAME);
|
||||
return 1;
|
||||
}
|
||||
if (add_quiet_access(ENV_QUIET_ACCESS_NAME, &ruleset_attr))
|
||||
return 1;
|
||||
} else if (getenv(ENV_FS_QUIET_NAME) || getenv(ENV_NET_QUIET_NAME) ||
|
||||
getenv(ENV_QUIET_ACCESS_NAME)) {
|
||||
fprintf(stderr,
|
||||
"Quiet flags not supported by current kernel\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ruleset_fd =
|
||||
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0) {
|
||||
|
|
@ -504,21 +637,41 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
|
||||
if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro, 0))
|
||||
goto err_close_ruleset;
|
||||
}
|
||||
if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
|
||||
if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw, 0))
|
||||
goto err_close_ruleset;
|
||||
|
||||
/* Don't require this env to be present. */
|
||||
if (quiet_supported && getenv(ENV_FS_QUIET_NAME)) {
|
||||
if (populate_ruleset_fs(ENV_FS_QUIET_NAME, ruleset_fd, 0,
|
||||
LANDLOCK_ADD_RULE_QUIET))
|
||||
goto err_close_ruleset;
|
||||
}
|
||||
|
||||
if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP)) {
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP, 0)) {
|
||||
goto err_close_ruleset;
|
||||
}
|
||||
if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP, 0)) {
|
||||
goto err_close_ruleset;
|
||||
}
|
||||
if (populate_ruleset_net(ENV_UDP_BIND_NAME, ruleset_fd,
|
||||
LANDLOCK_ACCESS_NET_BIND_UDP, 0)) {
|
||||
goto err_close_ruleset;
|
||||
}
|
||||
if (populate_ruleset_net(ENV_UDP_CONNECT_SEND_NAME, ruleset_fd,
|
||||
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, 0)) {
|
||||
goto err_close_ruleset;
|
||||
}
|
||||
|
||||
if (quiet_supported) {
|
||||
if (populate_ruleset_net(ENV_NET_QUIET_NAME, ruleset_fd, 0,
|
||||
LANDLOCK_ADD_RULE_QUIET)) {
|
||||
goto err_close_ruleset;
|
||||
}
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
|
||||
perror("Failed to restrict privileges");
|
||||
|
|
|
|||
|
|
@ -62,18 +62,41 @@ static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
|
|||
sizeof(typeof_member(union access_masks_all, all)));
|
||||
|
||||
/**
|
||||
* struct layer_access_masks - A boolean matrix of layers and access rights
|
||||
* struct layer_mask - The access rights and rule flags for a layer.
|
||||
*
|
||||
* This has a bit for each combination of layer numbers and access rights.
|
||||
* During access checks, it is used to represent the access rights for each
|
||||
* layer which still need to be fulfilled. When all bits are 0, the access
|
||||
* request is considered to be fulfilled.
|
||||
* This has a bit for each access rights and rule flags. During access checks,
|
||||
* it is used to represent the access rights for each layer which still need to
|
||||
* be fulfilled. When all bits are 0, the access request is considered to be
|
||||
* fulfilled.
|
||||
*/
|
||||
struct layer_access_masks {
|
||||
struct layer_mask {
|
||||
/**
|
||||
* @access: The unfulfilled access rights for each layer.
|
||||
* @access: The unfulfilled access rights for this layer.
|
||||
*/
|
||||
access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
|
||||
access_mask_t access : LANDLOCK_NUM_ACCESS_MAX;
|
||||
#ifdef CONFIG_AUDIT
|
||||
/**
|
||||
* @quiet: Whether we have encountered a rule with the quiet flag for
|
||||
* this layer. Used to control logging.
|
||||
*/
|
||||
access_mask_t quiet : 1;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
} __packed __aligned(sizeof(access_mask_t));
|
||||
|
||||
/*
|
||||
* Make sure that we don't increase the size of struct layer_mask when storing
|
||||
* rule flags.
|
||||
*/
|
||||
static_assert(sizeof(struct layer_mask) == sizeof(access_mask_t));
|
||||
|
||||
/**
|
||||
* struct layer_masks - An array of struct layer_mask, one per layer.
|
||||
*/
|
||||
struct layer_masks {
|
||||
/**
|
||||
* @layers: The unfulfilled access rights for each layer.
|
||||
*/
|
||||
struct layer_mask layers[LANDLOCK_MAX_NUM_LAYERS];
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -120,4 +143,9 @@ static inline bool access_mask_subset(access_mask_t subset,
|
|||
return (subset | superset) == superset;
|
||||
}
|
||||
|
||||
/* A bitmask that is large enough to hold set of optional accesses. */
|
||||
typedef u8 optional_access_t;
|
||||
static_assert(BITS_PER_TYPE(optional_access_t) >=
|
||||
HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
|
||||
|
||||
#endif /* _SECURITY_LANDLOCK_ACCESS_H */
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
|
|||
static const char *const net_access_strings[] = {
|
||||
[BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp",
|
||||
[BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp",
|
||||
[BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp",
|
||||
[BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)] =
|
||||
"net.connect_send_udp",
|
||||
};
|
||||
|
||||
static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
|
||||
|
|
@ -184,11 +187,11 @@ static void test_get_hierarchy(struct kunit *const test)
|
|||
/* Get the youngest layer that denied the access_request. */
|
||||
static size_t get_denied_layer(const struct landlock_ruleset *const domain,
|
||||
access_mask_t *const access_request,
|
||||
const struct layer_access_masks *masks)
|
||||
const struct layer_masks *masks)
|
||||
{
|
||||
for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
|
||||
if (masks->access[i] & *access_request) {
|
||||
*access_request &= masks->access[i];
|
||||
for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
|
||||
if (masks->layers[i].access & *access_request) {
|
||||
*access_request &= masks->layers[i].access;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
|
@ -205,12 +208,12 @@ static void test_get_denied_layer(struct kunit *const test)
|
|||
const struct landlock_ruleset dom = {
|
||||
.num_layers = 5,
|
||||
};
|
||||
const struct layer_access_masks masks = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.access[1] = LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.access[2] = LANDLOCK_ACCESS_FS_REMOVE_DIR,
|
||||
const struct layer_masks masks = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
|
||||
};
|
||||
access_mask_t access;
|
||||
|
||||
|
|
@ -246,7 +249,9 @@ static void test_get_denied_layer(struct kunit *const test)
|
|||
static size_t
|
||||
get_layer_from_deny_masks(access_mask_t *const access_request,
|
||||
const access_mask_t all_existing_optional_access,
|
||||
const deny_masks_t deny_masks)
|
||||
const deny_masks_t deny_masks,
|
||||
optional_access_t quiet_optional_accesses,
|
||||
bool *quiet)
|
||||
{
|
||||
const unsigned long access_opt = all_existing_optional_access;
|
||||
const unsigned long access_req = *access_request;
|
||||
|
|
@ -254,6 +259,7 @@ get_layer_from_deny_masks(access_mask_t *const access_request,
|
|||
size_t youngest_layer = 0;
|
||||
size_t access_index = 0;
|
||||
unsigned long access_bit;
|
||||
bool should_quiet = false;
|
||||
|
||||
/* This will require change with new object types. */
|
||||
WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
|
||||
|
|
@ -262,20 +268,34 @@ get_layer_from_deny_masks(access_mask_t *const access_request,
|
|||
BITS_PER_TYPE(access_mask_t)) {
|
||||
if (access_req & BIT(access_bit)) {
|
||||
const size_t layer =
|
||||
(deny_masks >> (access_index * 4)) &
|
||||
(deny_masks >>
|
||||
(access_index *
|
||||
HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
|
||||
(LANDLOCK_MAX_NUM_LAYERS - 1);
|
||||
const bool layer_has_quiet =
|
||||
!!(quiet_optional_accesses & BIT(access_index));
|
||||
|
||||
if (layer > youngest_layer) {
|
||||
youngest_layer = layer;
|
||||
missing = BIT(access_bit);
|
||||
should_quiet = layer_has_quiet;
|
||||
} else if (layer == youngest_layer) {
|
||||
missing |= BIT(access_bit);
|
||||
/*
|
||||
* Whether the layer has rules with quiet flag
|
||||
* covering the file accessed does not depend on
|
||||
* the access, and so the following
|
||||
* WARN_ON_ONCE() should not fail.
|
||||
*/
|
||||
WARN_ON_ONCE(should_quiet && !layer_has_quiet);
|
||||
should_quiet = layer_has_quiet;
|
||||
}
|
||||
}
|
||||
access_index++;
|
||||
}
|
||||
|
||||
*access_request = missing;
|
||||
*quiet = should_quiet;
|
||||
return youngest_layer;
|
||||
}
|
||||
|
||||
|
|
@ -285,42 +305,188 @@ static void test_get_layer_from_deny_masks(struct kunit *const test)
|
|||
{
|
||||
deny_masks_t deny_mask;
|
||||
access_mask_t access;
|
||||
optional_access_t quiet_optional_accesses;
|
||||
bool quiet;
|
||||
|
||||
/* truncate:0 ioctl_dev:2 */
|
||||
deny_mask = 0x20;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(&access,
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask));
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(&access,
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask));
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* layer denying truncate: quiet, ioctl: not quiet */
|
||||
quiet_optional_accesses = 0b01;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* Reverse order - truncate:2 ioctl_dev:0 */
|
||||
deny_mask = 0x02;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* layer denying truncate: quiet, ioctl: not quiet */
|
||||
quiet_optional_accesses = 0b01;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
/* layer denying truncate: not quiet, ioctl: quiet */
|
||||
quiet_optional_accesses = 0b10;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* truncate:15 ioctl_dev:15 */
|
||||
deny_mask = 0xff;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(&access,
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask));
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(&access,
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask));
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* Both quiet (same layer so quietness must be the same) */
|
||||
quiet_optional_accesses = 0b11;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
|
@ -346,11 +512,34 @@ static bool is_valid_request(const struct landlock_request *const request)
|
|||
if (request->deny_masks) {
|
||||
if (WARN_ON_ONCE(!request->all_existing_optional_access))
|
||||
return false;
|
||||
static_assert(sizeof(request->all_existing_optional_access) ==
|
||||
sizeof(u32));
|
||||
if (WARN_ON_ONCE(
|
||||
request->quiet_optional_accesses >=
|
||||
BIT(hweight32(
|
||||
request->all_existing_optional_access))))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static access_mask_t
|
||||
pick_access_mask_for_request_type(const enum landlock_request_type type,
|
||||
const struct access_masks access_masks)
|
||||
{
|
||||
switch (type) {
|
||||
case LANDLOCK_REQUEST_FS_ACCESS:
|
||||
return access_masks.fs;
|
||||
case LANDLOCK_REQUEST_NET_ACCESS:
|
||||
return access_masks.net;
|
||||
default:
|
||||
WARN_ONCE(1, "Invalid request type %d passed to %s", type,
|
||||
__func__);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* landlock_log_denial - Create audit records related to a denial
|
||||
*
|
||||
|
|
@ -364,6 +553,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
|
|||
struct landlock_hierarchy *youngest_denied;
|
||||
size_t youngest_layer;
|
||||
access_mask_t missing;
|
||||
bool object_quiet_flag = false, quiet_applicable_to_access = false;
|
||||
|
||||
if (WARN_ON_ONCE(!subject || !subject->domain ||
|
||||
!subject->domain->hierarchy || !request))
|
||||
|
|
@ -379,10 +569,15 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
|
|||
youngest_layer = get_denied_layer(subject->domain,
|
||||
&missing,
|
||||
request->layer_masks);
|
||||
object_quiet_flag =
|
||||
request->layer_masks->layers[youngest_layer]
|
||||
.quiet;
|
||||
} else {
|
||||
youngest_layer = get_layer_from_deny_masks(
|
||||
&missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
request->deny_masks);
|
||||
request->deny_masks,
|
||||
request->quiet_optional_accesses,
|
||||
&object_quiet_flag);
|
||||
}
|
||||
youngest_denied =
|
||||
get_hierarchy(subject->domain, youngest_layer);
|
||||
|
|
@ -417,6 +612,53 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the object is marked quiet by the layer that denied the
|
||||
* request. If it's a different layer that marked it as quiet, but that
|
||||
* layer is not the one that denied the request, we should still audit
|
||||
* log the denial.
|
||||
*/
|
||||
if (object_quiet_flag) {
|
||||
/*
|
||||
* We now check if the denied requests are all covered by the
|
||||
* layer's quiet access bits.
|
||||
*/
|
||||
const access_mask_t quiet_mask =
|
||||
pick_access_mask_for_request_type(
|
||||
request->type, youngest_denied->quiet_masks);
|
||||
|
||||
quiet_applicable_to_access = (quiet_mask & missing) == missing;
|
||||
} else {
|
||||
/*
|
||||
* Either the object is not quiet, or this is a scope request.
|
||||
* We check request->type to distinguish between the two cases.
|
||||
*/
|
||||
const access_mask_t quiet_mask =
|
||||
youngest_denied->quiet_masks.scope;
|
||||
|
||||
switch (request->type) {
|
||||
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
|
||||
quiet_applicable_to_access =
|
||||
!!(quiet_mask & LANDLOCK_SCOPE_SIGNAL);
|
||||
break;
|
||||
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
|
||||
quiet_applicable_to_access =
|
||||
!!(quiet_mask &
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||||
break;
|
||||
/*
|
||||
* Leave LANDLOCK_REQUEST_PTRACE and
|
||||
* LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they
|
||||
* are never quiet.
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (quiet_applicable_to_access)
|
||||
return;
|
||||
|
||||
/* Uses consistent allocation flags wrt common_lsm_audit(). */
|
||||
ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
|
||||
AUDIT_LANDLOCK_ACCESS);
|
||||
|
|
|
|||
|
|
@ -43,11 +43,12 @@ struct landlock_request {
|
|||
access_mask_t access;
|
||||
|
||||
/* Required fields for requests with layer masks. */
|
||||
const struct layer_access_masks *layer_masks;
|
||||
const struct layer_masks *layer_masks;
|
||||
|
||||
/* Required fields for requests with deny masks. */
|
||||
const access_mask_t all_existing_optional_access;
|
||||
deny_masks_t deny_masks;
|
||||
optional_access_t quiet_optional_accesses;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
|
|
|
|||
|
|
@ -90,11 +90,12 @@ static struct landlock_details *get_current_details(void)
|
|||
return ERR_CAST(buffer);
|
||||
|
||||
/*
|
||||
* Create the new details according to the path's length. Do not
|
||||
* allocate with GFP_KERNEL_ACCOUNT because it is independent from the
|
||||
* caller.
|
||||
* Create the new details according to the path's length. Account to
|
||||
* the calling task's memcg, like the other Landlock per-domain
|
||||
* allocations, even if it may not control the related size.
|
||||
*/
|
||||
details = kzalloc_flex(*details, exe_path, path_size);
|
||||
details =
|
||||
kzalloc_flex(*details, exe_path, path_size, GFP_KERNEL_ACCOUNT);
|
||||
if (!details)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
|
|
@ -156,6 +157,44 @@ get_layer_deny_mask(const access_mask_t all_existing_optional_access,
|
|||
<< ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* landlock_get_quiet_optional_accesses - Get optional accesses which are
|
||||
* covered by quiet rule flags.
|
||||
*
|
||||
* @all_existing_optional_access: Bitmask of valid optional accesses.
|
||||
* @deny_masks: Domain layer levels that denied each optional access (the
|
||||
* deny_masks field on struct landlock_file_security).
|
||||
* @masks: The struct layer_masks collected during the path walk.
|
||||
*
|
||||
* Return: a bitmask of which optional accesses are denied by layers for which
|
||||
* the quiet flag was collected during the path walk.
|
||||
*/
|
||||
optional_access_t landlock_get_quiet_optional_accesses(
|
||||
const access_mask_t all_existing_optional_access,
|
||||
const deny_masks_t deny_masks, const struct layer_masks *const masks)
|
||||
{
|
||||
const unsigned long access_opt = all_existing_optional_access;
|
||||
size_t access_index = 0;
|
||||
unsigned long access_bit;
|
||||
optional_access_t quiet_optional_accesses = 0;
|
||||
|
||||
/* This will require change with new object types. */
|
||||
WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
|
||||
|
||||
for_each_set_bit(access_bit, &access_opt,
|
||||
BITS_PER_TYPE(access_mask_t)) {
|
||||
const u8 layer =
|
||||
(deny_masks >> (access_index *
|
||||
HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
|
||||
(LANDLOCK_MAX_NUM_LAYERS - 1);
|
||||
|
||||
if (masks->layers[layer].quiet)
|
||||
quiet_optional_accesses |= BIT(access_index);
|
||||
access_index++;
|
||||
}
|
||||
return quiet_optional_accesses;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static void test_get_layer_deny_mask(struct kunit *const test)
|
||||
|
|
@ -183,7 +222,7 @@ static void test_get_layer_deny_mask(struct kunit *const test)
|
|||
deny_masks_t
|
||||
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
||||
const access_mask_t optional_access,
|
||||
const struct layer_access_masks *const masks)
|
||||
const struct layer_masks *const masks)
|
||||
{
|
||||
const unsigned long access_opt = optional_access;
|
||||
unsigned long access_bit;
|
||||
|
|
@ -200,8 +239,9 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
|||
if (WARN_ON_ONCE(!access_opt))
|
||||
return 0;
|
||||
|
||||
for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
|
||||
const access_mask_t denied = masks->access[i] & optional_access;
|
||||
for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
|
||||
const access_mask_t denied = masks->layers[i].access &
|
||||
optional_access;
|
||||
const unsigned long newly_denied = denied & ~all_denied;
|
||||
|
||||
if (!newly_denied)
|
||||
|
|
@ -221,12 +261,12 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
|||
|
||||
static void test_landlock_get_deny_masks(struct kunit *const test)
|
||||
{
|
||||
const struct layer_access_masks layers1 = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.access[1] = LANDLOCK_ACCESS_FS_TRUNCATE,
|
||||
.access[2] = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.access[9] = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
const struct layer_masks layers1 = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_TRUNCATE,
|
||||
.layers[2].access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.layers[9].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
};
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0x1,
|
||||
|
|
|
|||
|
|
@ -33,10 +33,7 @@ enum landlock_log_status {
|
|||
* Rarely accessed, mainly when logging the first domain's denial.
|
||||
*
|
||||
* The contained pointers are initialized at the domain creation time and never
|
||||
* changed again. Contrary to most other Landlock object types, this one is
|
||||
* not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the
|
||||
* caller's control (e.g. unknown exe_path) and the data is not explicitly
|
||||
* requested nor used by tasks.
|
||||
* changed again.
|
||||
*/
|
||||
struct landlock_details {
|
||||
/**
|
||||
|
|
@ -114,6 +111,11 @@ struct landlock_hierarchy {
|
|||
* %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON. Set to false by default.
|
||||
*/
|
||||
log_new_exec : 1;
|
||||
/**
|
||||
* @quiet_masks: Bitmasks of access that should be quieted (i.e. not
|
||||
* logged) if the related object is marked as quiet.
|
||||
*/
|
||||
struct access_masks quiet_masks;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
};
|
||||
|
||||
|
|
@ -122,7 +124,11 @@ struct landlock_hierarchy {
|
|||
deny_masks_t
|
||||
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
||||
const access_mask_t optional_access,
|
||||
const struct layer_access_masks *const masks);
|
||||
const struct layer_masks *const masks);
|
||||
|
||||
optional_access_t landlock_get_quiet_optional_accesses(
|
||||
const access_mask_t all_existing_optional_access,
|
||||
const deny_masks_t deny_masks, const struct layer_masks *const masks);
|
||||
|
||||
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
|
||||
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
|
|||
*/
|
||||
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
|
||||
const struct path *const path,
|
||||
access_mask_t access_rights)
|
||||
access_mask_t access_rights, const u32 flags)
|
||||
{
|
||||
int err;
|
||||
struct landlock_id id = {
|
||||
|
|
@ -346,7 +346,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
|
|||
if (IS_ERR(id.key.object))
|
||||
return PTR_ERR(id.key.object);
|
||||
mutex_lock(&ruleset->lock);
|
||||
err = landlock_insert_rule(ruleset, id, access_rights);
|
||||
err = landlock_insert_rule(ruleset, id, access_rights, flags);
|
||||
mutex_unlock(&ruleset->lock);
|
||||
/*
|
||||
* No need to check for an error because landlock_insert_rule()
|
||||
|
|
@ -406,15 +406,15 @@ static const struct access_masks any_fs = {
|
|||
* src_parent would result in having the same or fewer access rights if it were
|
||||
* moved under new_parent.
|
||||
*/
|
||||
static bool may_refer(const struct layer_access_masks *const src_parent,
|
||||
const struct layer_access_masks *const src_child,
|
||||
const struct layer_access_masks *const new_parent,
|
||||
static bool may_refer(const struct layer_masks *const src_parent,
|
||||
const struct layer_masks *const src_child,
|
||||
const struct layer_masks *const new_parent,
|
||||
const bool child_is_dir)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(new_parent->access); i++) {
|
||||
access_mask_t child_access = src_parent->access[i] &
|
||||
src_child->access[i];
|
||||
access_mask_t parent_access = new_parent->access[i];
|
||||
for (size_t i = 0; i < ARRAY_SIZE(new_parent->layers); i++) {
|
||||
access_mask_t child_access = src_parent->layers[i].access &
|
||||
src_child->layers[i].access;
|
||||
access_mask_t parent_access = new_parent->layers[i].access;
|
||||
|
||||
if (!child_is_dir) {
|
||||
child_access &= ACCESS_FILE;
|
||||
|
|
@ -436,11 +436,11 @@ static bool may_refer(const struct layer_access_masks *const src_parent,
|
|||
* that child2 may be used from parent2 to parent1 without increasing its access
|
||||
* rights), false otherwise.
|
||||
*/
|
||||
static bool no_more_access(const struct layer_access_masks *const parent1,
|
||||
const struct layer_access_masks *const child1,
|
||||
static bool no_more_access(const struct layer_masks *const parent1,
|
||||
const struct layer_masks *const child1,
|
||||
const bool child1_is_dir,
|
||||
const struct layer_access_masks *const parent2,
|
||||
const struct layer_access_masks *const child2,
|
||||
const struct layer_masks *const parent2,
|
||||
const struct layer_masks *const child2,
|
||||
const bool child2_is_dir)
|
||||
{
|
||||
if (!may_refer(parent1, child1, parent2, child1_is_dir))
|
||||
|
|
@ -459,25 +459,25 @@ static bool no_more_access(const struct layer_access_masks *const parent1,
|
|||
|
||||
static void test_no_more_access(struct kunit *const test)
|
||||
{
|
||||
const struct layer_access_masks rx0 = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
const struct layer_masks rx0 = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
};
|
||||
const struct layer_access_masks mx0 = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
const struct layer_masks mx0 = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
};
|
||||
const struct layer_access_masks x0 = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
const struct layer_masks x0 = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
};
|
||||
const struct layer_access_masks x1 = {
|
||||
.access[1] = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
const struct layer_masks x1 = {
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
};
|
||||
const struct layer_access_masks x01 = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.access[1] = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
const struct layer_masks x01 = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
};
|
||||
const struct layer_access_masks allows_all = {};
|
||||
const struct layer_masks allows_all = {};
|
||||
|
||||
/* Checks without restriction. */
|
||||
NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false);
|
||||
|
|
@ -565,9 +565,13 @@ static void test_no_more_access(struct kunit *const test)
|
|||
#undef NMA_TRUE
|
||||
#undef NMA_FALSE
|
||||
|
||||
static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
|
||||
static bool is_layer_masks_allowed(const struct layer_masks *masks)
|
||||
{
|
||||
return mem_is_zero(&masks->access, sizeof(masks->access));
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||
if (masks->layers[i].access)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -576,16 +580,16 @@ static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
|
|||
* Returns true if the request is allowed, false otherwise.
|
||||
*/
|
||||
static bool scope_to_request(const access_mask_t access_request,
|
||||
struct layer_access_masks *masks)
|
||||
struct layer_masks *masks)
|
||||
{
|
||||
bool saw_unfulfilled_access = false;
|
||||
|
||||
if (WARN_ON_ONCE(!masks))
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
|
||||
masks->access[i] &= access_request;
|
||||
if (masks->access[i])
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||
masks->layers[i].access &= access_request;
|
||||
if (masks->layers[i].access)
|
||||
saw_unfulfilled_access = true;
|
||||
}
|
||||
return !saw_unfulfilled_access;
|
||||
|
|
@ -596,41 +600,46 @@ static bool scope_to_request(const access_mask_t access_request,
|
|||
static void test_scope_to_request_with_exec_none(struct kunit *const test)
|
||||
{
|
||||
/* Allows everything. */
|
||||
struct layer_access_masks masks = {};
|
||||
struct layer_masks masks = {};
|
||||
|
||||
/* Checks and scopes with execute. */
|
||||
KUNIT_EXPECT_TRUE(test,
|
||||
scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, &masks));
|
||||
KUNIT_EXPECT_EQ(test, 0, masks.access[0]);
|
||||
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access);
|
||||
}
|
||||
|
||||
static void test_scope_to_request_with_exec_some(struct kunit *const test)
|
||||
{
|
||||
/* Denies execute and write. */
|
||||
struct layer_access_masks masks = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
struct layer_masks masks = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
};
|
||||
|
||||
/* Checks and scopes with execute. */
|
||||
KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
&masks));
|
||||
KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE, masks.access[0]);
|
||||
KUNIT_EXPECT_EQ(test, 0, masks.access[1]);
|
||||
/*
|
||||
* These casts to access_mask_t are needed because typeof(), used in
|
||||
* KUNIT_EXPECT_EQ(), does not work on bitfields.
|
||||
*/
|
||||
KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
(access_mask_t)masks.layers[0].access);
|
||||
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access);
|
||||
}
|
||||
|
||||
static void test_scope_to_request_without_access(struct kunit *const test)
|
||||
{
|
||||
/* Denies execute and write. */
|
||||
struct layer_access_masks masks = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
struct layer_masks masks = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
};
|
||||
|
||||
/* Checks and scopes without access request. */
|
||||
KUNIT_EXPECT_TRUE(test, scope_to_request(0, &masks));
|
||||
KUNIT_EXPECT_EQ(test, 0, masks.access[0]);
|
||||
KUNIT_EXPECT_EQ(test, 0, masks.access[1]);
|
||||
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access);
|
||||
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
|
@ -639,15 +648,15 @@ static void test_scope_to_request_without_access(struct kunit *const test)
|
|||
* Returns true if there is at least one access right different than
|
||||
* LANDLOCK_ACCESS_FS_REFER.
|
||||
*/
|
||||
static bool is_eacces(const struct layer_access_masks *masks,
|
||||
static bool is_eacces(const struct layer_masks *masks,
|
||||
const access_mask_t access_request)
|
||||
{
|
||||
if (!masks)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||
/* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */
|
||||
if (masks->access[i] & access_request &
|
||||
if (masks->layers[i].access & access_request &
|
||||
~LANDLOCK_ACCESS_FS_REFER)
|
||||
return true;
|
||||
}
|
||||
|
|
@ -661,7 +670,7 @@ static bool is_eacces(const struct layer_access_masks *masks,
|
|||
|
||||
static void test_is_eacces_with_none(struct kunit *const test)
|
||||
{
|
||||
const struct layer_access_masks masks = {};
|
||||
const struct layer_masks masks = {};
|
||||
|
||||
IE_FALSE(&masks, 0);
|
||||
IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER);
|
||||
|
|
@ -671,8 +680,8 @@ static void test_is_eacces_with_none(struct kunit *const test)
|
|||
|
||||
static void test_is_eacces_with_refer(struct kunit *const test)
|
||||
{
|
||||
const struct layer_access_masks masks = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_REFER,
|
||||
const struct layer_masks masks = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_REFER,
|
||||
};
|
||||
|
||||
IE_FALSE(&masks, 0);
|
||||
|
|
@ -683,8 +692,8 @@ static void test_is_eacces_with_refer(struct kunit *const test)
|
|||
|
||||
static void test_is_eacces_with_write(struct kunit *const test)
|
||||
{
|
||||
const struct layer_access_masks masks = {
|
||||
.access[0] = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
const struct layer_masks masks = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
};
|
||||
|
||||
IE_FALSE(&masks, 0);
|
||||
|
|
@ -743,11 +752,11 @@ static bool
|
|||
is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
|
||||
const struct path *const path,
|
||||
const access_mask_t access_request_parent1,
|
||||
struct layer_access_masks *layer_masks_parent1,
|
||||
struct layer_masks *layer_masks_parent1,
|
||||
struct landlock_request *const log_request_parent1,
|
||||
struct dentry *const dentry_child1,
|
||||
const access_mask_t access_request_parent2,
|
||||
struct layer_access_masks *layer_masks_parent2,
|
||||
struct layer_masks *layer_masks_parent2,
|
||||
struct landlock_request *const log_request_parent2,
|
||||
struct dentry *const dentry_child2)
|
||||
{
|
||||
|
|
@ -755,9 +764,9 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
|
|||
child1_is_directory = true, child2_is_directory = true;
|
||||
struct path walker_path;
|
||||
access_mask_t access_masked_parent1, access_masked_parent2;
|
||||
struct layer_access_masks _layer_masks_child1, _layer_masks_child2;
|
||||
struct layer_access_masks *layer_masks_child1 = NULL,
|
||||
*layer_masks_child2 = NULL;
|
||||
struct layer_masks _layer_masks_child1, _layer_masks_child2;
|
||||
struct layer_masks *layer_masks_child1 = NULL,
|
||||
*layer_masks_child2 = NULL;
|
||||
|
||||
if (!access_request_parent1 && !access_request_parent2)
|
||||
return true;
|
||||
|
|
@ -797,6 +806,10 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
|
|||
}
|
||||
|
||||
if (unlikely(dentry_child1)) {
|
||||
/*
|
||||
* Get the layer masks for the child dentries for use by domain
|
||||
* check later.
|
||||
*/
|
||||
if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
|
||||
&_layer_masks_child1,
|
||||
LANDLOCK_KEY_INODE))
|
||||
|
|
@ -952,7 +965,7 @@ static int current_check_access_path(const struct path *const path,
|
|||
};
|
||||
const struct landlock_cred_security *const subject =
|
||||
landlock_get_applicable_subject(current_cred(), masks, NULL);
|
||||
struct layer_access_masks layer_masks;
|
||||
struct layer_masks layer_masks;
|
||||
struct landlock_request request = {};
|
||||
|
||||
if (!subject)
|
||||
|
|
@ -1029,7 +1042,7 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
|
|||
static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
|
||||
const struct dentry *const mnt_root,
|
||||
struct dentry *dir,
|
||||
struct layer_access_masks *layer_masks_dom)
|
||||
struct layer_masks *layer_masks_dom)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
|
|
@ -1135,8 +1148,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
|
|||
access_mask_t access_request_parent1, access_request_parent2;
|
||||
struct path mnt_dir;
|
||||
struct dentry *old_parent;
|
||||
struct layer_access_masks layer_masks_parent1 = {},
|
||||
layer_masks_parent2 = {};
|
||||
struct layer_masks layer_masks_parent1 = {}, layer_masks_parent2 = {};
|
||||
struct landlock_request request1 = {}, request2 = {};
|
||||
|
||||
if (!subject)
|
||||
|
|
@ -1202,7 +1214,6 @@ static int current_check_refer_path(struct dentry *const old_dentry,
|
|||
allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
|
||||
new_dir->dentry,
|
||||
&layer_masks_parent2);
|
||||
|
||||
if (allow_parent1 && allow_parent2)
|
||||
return 0;
|
||||
|
||||
|
|
@ -1580,7 +1591,7 @@ static int hook_path_truncate(const struct path *const path)
|
|||
*/
|
||||
static void unmask_scoped_access(const struct landlock_ruleset *const client,
|
||||
const struct landlock_ruleset *const server,
|
||||
struct layer_access_masks *const masks,
|
||||
struct layer_masks *const masks,
|
||||
const access_mask_t access)
|
||||
{
|
||||
int client_layer, server_layer;
|
||||
|
|
@ -1621,9 +1632,9 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client,
|
|||
server_walker = server_walker->parent;
|
||||
|
||||
for (; client_layer >= 0; client_layer--) {
|
||||
if (masks->access[client_layer] & access &&
|
||||
if (masks->layers[client_layer].access & access &&
|
||||
client_walker == server_walker)
|
||||
masks->access[client_layer] &= ~access;
|
||||
masks->layers[client_layer].access &= ~access;
|
||||
|
||||
client_walker = client_walker->parent;
|
||||
server_walker = server_walker->parent;
|
||||
|
|
@ -1635,7 +1646,7 @@ static int hook_unix_find(const struct path *const path, struct sock *other,
|
|||
{
|
||||
const struct landlock_ruleset *dom_other;
|
||||
const struct landlock_cred_security *subject;
|
||||
struct layer_access_masks layer_masks;
|
||||
struct layer_masks layer_masks;
|
||||
struct landlock_request request = {};
|
||||
static const struct access_masks fs_resolve_unix = {
|
||||
.fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
|
||||
|
|
@ -1739,7 +1750,7 @@ static bool is_device(const struct file *const file)
|
|||
|
||||
static int hook_file_open(struct file *const file)
|
||||
{
|
||||
struct layer_access_masks layer_masks = {};
|
||||
struct layer_masks layer_masks = {};
|
||||
access_mask_t open_access_request, full_access_request, allowed_access,
|
||||
optional_access;
|
||||
const struct landlock_cred_security *const subject =
|
||||
|
|
@ -1780,8 +1791,8 @@ static int hook_file_open(struct file *const file)
|
|||
* are still unfulfilled in any of the layers.
|
||||
*/
|
||||
allowed_access = full_access_request;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(layer_masks.access); i++)
|
||||
allowed_access &= ~layer_masks.access[i];
|
||||
for (size_t i = 0; i < ARRAY_SIZE(layer_masks.layers); i++)
|
||||
allowed_access &= ~layer_masks.layers[i].access;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1794,6 +1805,10 @@ static int hook_file_open(struct file *const file)
|
|||
#ifdef CONFIG_AUDIT
|
||||
landlock_file(file)->deny_masks = landlock_get_deny_masks(
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks);
|
||||
landlock_file(file)->quiet_optional_accesses =
|
||||
landlock_get_quiet_optional_accesses(
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
landlock_file(file)->deny_masks, &layer_masks);
|
||||
#endif /* CONFIG_AUDIT */
|
||||
|
||||
if (access_mask_subset(open_access_request, allowed_access))
|
||||
|
|
@ -1830,6 +1845,7 @@ static int hook_file_truncate(struct file *const file)
|
|||
.access = LANDLOCK_ACCESS_FS_TRUNCATE,
|
||||
#ifdef CONFIG_AUDIT
|
||||
.deny_masks = landlock_file(file)->deny_masks,
|
||||
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
|
||||
#endif /* CONFIG_AUDIT */
|
||||
});
|
||||
return -EACCES;
|
||||
|
|
@ -1869,6 +1885,7 @@ static int hook_file_ioctl_common(const struct file *const file,
|
|||
.access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
#ifdef CONFIG_AUDIT
|
||||
.deny_masks = landlock_file(file)->deny_masks,
|
||||
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
|
||||
#endif /* CONFIG_AUDIT */
|
||||
});
|
||||
return -EACCES;
|
||||
|
|
@ -1900,6 +1917,14 @@ static bool control_current_fowner(struct fown_struct *const fown)
|
|||
*/
|
||||
lockdep_assert_held(&fown->lock);
|
||||
|
||||
/*
|
||||
* A process-group or session owner (PIDTYPE_PGID/PIDTYPE_SID) fans the
|
||||
* signal out to every member at delivery time, so record the domain and
|
||||
* let hook_file_send_sigiotask() check the live scope per recipient.
|
||||
*/
|
||||
if (fown->pid_type != PIDTYPE_PID && fown->pid_type != PIDTYPE_TGID)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side
|
||||
* critical section.
|
||||
|
|
@ -1916,6 +1941,7 @@ static void hook_file_set_fowner(struct file *file)
|
|||
{
|
||||
struct landlock_ruleset *prev_dom;
|
||||
struct landlock_cred_security fown_subject = {};
|
||||
struct pid *prev_tg, *fown_tg = NULL;
|
||||
size_t fown_layer = 0;
|
||||
|
||||
if (control_current_fowner(file_f_owner(file))) {
|
||||
|
|
@ -1928,21 +1954,26 @@ static void hook_file_set_fowner(struct file *file)
|
|||
if (new_subject) {
|
||||
landlock_get_ruleset(new_subject->domain);
|
||||
fown_subject = *new_subject;
|
||||
fown_tg = get_pid(task_tgid(current));
|
||||
}
|
||||
}
|
||||
|
||||
prev_dom = landlock_file(file)->fown_subject.domain;
|
||||
prev_tg = landlock_file(file)->fown_tg;
|
||||
landlock_file(file)->fown_subject = fown_subject;
|
||||
landlock_file(file)->fown_tg = fown_tg;
|
||||
#ifdef CONFIG_AUDIT
|
||||
landlock_file(file)->fown_layer = fown_layer;
|
||||
#endif /* CONFIG_AUDIT*/
|
||||
|
||||
/* May be called in an RCU read-side critical section. */
|
||||
landlock_put_ruleset_deferred(prev_dom);
|
||||
put_pid(prev_tg);
|
||||
}
|
||||
|
||||
static void hook_file_free_security(struct file *file)
|
||||
{
|
||||
put_pid(landlock_file(file)->fown_tg);
|
||||
landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,13 @@ struct landlock_file_security {
|
|||
* _LANDLOCK_ACCESS_FS_OPTIONAL).
|
||||
*/
|
||||
deny_masks_t deny_masks;
|
||||
/**
|
||||
* @quiet_optional_accesses: Stores which optional accesses are covered
|
||||
* by quiet rules within the layer referred to in deny_masks, one access
|
||||
* per bit. Does not take into account whether the quiet access bits
|
||||
* are actually set in the layer's corresponding landlock_hierarchy.
|
||||
*/
|
||||
optional_access_t quiet_optional_accesses;
|
||||
/**
|
||||
* @fown_layer: Layer level of @fown_subject->domain with
|
||||
* LANDLOCK_SCOPE_SIGNAL.
|
||||
|
|
@ -78,6 +85,16 @@ struct landlock_file_security {
|
|||
* euid.
|
||||
*/
|
||||
struct landlock_cred_security fown_subject;
|
||||
/**
|
||||
* @fown_tg: Thread group of the task that set the file owner, pinned
|
||||
* while @fown_subject holds a domain. It lets
|
||||
* hook_file_send_sigiotask() always allow a SIGIO delivered to the
|
||||
* owner's own process -- e.g. the thread-group leader reached through a
|
||||
* process-group owner -- matching the same-process exemption of
|
||||
* hook_task_kill(). NULL when no domain is recorded. Protected by
|
||||
* file->f_owner->lock, like @fown_subject.
|
||||
*/
|
||||
struct pid *fown_tg;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
|
|
@ -86,7 +103,15 @@ struct landlock_file_security {
|
|||
/* clang-format off */
|
||||
static_assert((typeof_member(struct landlock_file_security, fown_layer))~0 >=
|
||||
LANDLOCK_MAX_NUM_LAYERS);
|
||||
/* clang-format off */
|
||||
/* clang-format on */
|
||||
|
||||
/*
|
||||
* Make sure quiet_optional_accesses has enough bits to cover all optional
|
||||
* accesses.
|
||||
*/
|
||||
static_assert(BITS_PER_TYPE(typeof_member(struct landlock_file_security,
|
||||
quiet_optional_accesses)) >=
|
||||
HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
|
||||
|
||||
#endif /* CONFIG_AUDIT */
|
||||
|
||||
|
|
@ -126,6 +151,6 @@ __init void landlock_add_fs_hooks(void);
|
|||
|
||||
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
|
||||
const struct path *const path,
|
||||
access_mask_t access_hierarchy);
|
||||
access_mask_t access_hierarchy, const u32 flags);
|
||||
|
||||
#endif /* _SECURITY_LANDLOCK_FS_H */
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
|
||||
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
|
||||
|
||||
#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
|
||||
#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP
|
||||
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
|
||||
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
|
||||
|
||||
|
|
@ -31,6 +31,9 @@
|
|||
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
|
||||
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
|
||||
|
||||
#define LANDLOCK_NUM_ACCESS_MAX \
|
||||
MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE)
|
||||
|
||||
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
|
||||
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@
|
|||
#include "ruleset.h"
|
||||
|
||||
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
||||
const u16 port, access_mask_t access_rights)
|
||||
const u16 port, access_mask_t access_rights,
|
||||
const u32 flags)
|
||||
{
|
||||
int err;
|
||||
const struct landlock_id id = {
|
||||
|
|
@ -35,7 +36,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
|||
~landlock_get_net_access_mask(ruleset, 0);
|
||||
|
||||
mutex_lock(&ruleset->lock);
|
||||
err = landlock_insert_rule(ruleset, id, access_rights);
|
||||
err = landlock_insert_rule(ruleset, id, access_rights, flags);
|
||||
mutex_unlock(&ruleset->lock);
|
||||
|
||||
return err;
|
||||
|
|
@ -44,10 +45,12 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
|||
static int current_check_access_socket(struct socket *const sock,
|
||||
struct sockaddr *const address,
|
||||
const int addrlen,
|
||||
access_mask_t access_request)
|
||||
access_mask_t access_request,
|
||||
bool connecting)
|
||||
{
|
||||
unsigned short sock_family;
|
||||
__be16 port;
|
||||
struct layer_access_masks layer_masks = {};
|
||||
struct layer_masks layer_masks = {};
|
||||
const struct landlock_rule *rule;
|
||||
struct landlock_id id = {
|
||||
.type = LANDLOCK_KEY_NET_PORT,
|
||||
|
|
@ -66,30 +69,69 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
if (addrlen < offsetofend(typeof(*address), sa_family))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The socket is not locked, so sk_family can change concurrently due to
|
||||
* e.g. setsockopt(IPV6_ADDRFORM).
|
||||
*/
|
||||
sock_family = READ_ONCE(sock->sk->sk_family);
|
||||
|
||||
switch (address->sa_family) {
|
||||
case AF_UNSPEC:
|
||||
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
|
||||
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
|
||||
(access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP &&
|
||||
connecting)) {
|
||||
/*
|
||||
* Connecting to an address with AF_UNSPEC dissolves
|
||||
* the TCP association, which have the same effect as
|
||||
* closing the connection while retaining the socket
|
||||
* object (i.e., the file descriptor). As for dropping
|
||||
* privileges, closing connections is always allowed.
|
||||
*
|
||||
* For a TCP access control system, this request is
|
||||
* legitimate. Let the network stack handle potential
|
||||
* inconsistencies and return -EINVAL if needed.
|
||||
* Connecting to an address with AF_UNSPEC dissolves the
|
||||
* remote association while retaining the socket object
|
||||
* (i.e., the file descriptor). For TCP, it has the same
|
||||
* effect as closing the connection. For UDP, it removes
|
||||
* any preset remote address. As for dropping
|
||||
* privileges, these actions are always allowed. Let
|
||||
* the network stack handle potential inconsistencies
|
||||
* and return -EINVAL if needed.
|
||||
*/
|
||||
return 0;
|
||||
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
|
||||
} else if (access_request ==
|
||||
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
|
||||
if (sock_family == AF_INET6) {
|
||||
/*
|
||||
* We cannot allow sending UDP datagrams to an
|
||||
* explicit AF_UNSPEC address on IPv6 sockets,
|
||||
* even if AF_UNSPEC is treated as "no address"
|
||||
* on such sockets (so it should always be
|
||||
* allowed). That's because the socket's family
|
||||
* can change under our feet (if another thread
|
||||
* calls setsockopt(IPV6_ADDRFORM)) to IPv4,
|
||||
* which would then treat AF_UNSPEC as AF_INET.
|
||||
*/
|
||||
audit_net.family = AF_UNSPEC;
|
||||
audit_net.sk = sock->sk;
|
||||
landlock_init_layer_masks(
|
||||
subject->domain, access_request,
|
||||
&layer_masks, LANDLOCK_KEY_NET_PORT);
|
||||
landlock_log_denial(
|
||||
subject,
|
||||
&(struct landlock_request){
|
||||
.type = LANDLOCK_REQUEST_NET_ACCESS,
|
||||
.audit.type =
|
||||
LSM_AUDIT_DATA_NET,
|
||||
.audit.u.net = &audit_net,
|
||||
.access = access_request,
|
||||
.layer_masks = &layer_masks,
|
||||
});
|
||||
return -EACCES;
|
||||
}
|
||||
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
|
||||
access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
|
||||
/*
|
||||
* Binding to an AF_UNSPEC address is treated
|
||||
* differently by IPv4 and IPv6 sockets. The socket's
|
||||
* family may change under our feet due to
|
||||
* setsockopt(IPV6_ADDRFORM), but that's ok: we either
|
||||
* reject entirely or require
|
||||
* %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so
|
||||
* it cannot be used to bypass the policy.
|
||||
* reject entirely for IPv6 or require
|
||||
* %LANDLOCK_ACCESS_NET_BIND_TCP or
|
||||
* %LANDLOCK_ACCESS_NET_BIND_UDP for IPv4, so it cannot
|
||||
* be used to bypass the policy.
|
||||
*
|
||||
* IPv4 sockets map AF_UNSPEC to AF_INET for
|
||||
* retrocompatibility for bind accesses, only if the
|
||||
|
|
@ -102,7 +144,7 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
* these checks, but it is safer to return a proper
|
||||
* error and test consistency thanks to kselftest.
|
||||
*/
|
||||
if (sock->sk->__sk_common.skc_family == AF_INET) {
|
||||
if (sock_family == AF_INET) {
|
||||
const struct sockaddr_in *const sockaddr =
|
||||
(struct sockaddr_in *)address;
|
||||
|
||||
|
|
@ -121,7 +163,11 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
} else {
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
/* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */
|
||||
/*
|
||||
* AF_UNSPEC is treated as AF_INET only in
|
||||
* bind(AF_UNSPEC+INADDR_ANY) on IPv4 sockets and when sending
|
||||
* to AF_UNSPEC addresses on IPv4 sockets.
|
||||
*/
|
||||
fallthrough;
|
||||
case AF_INET: {
|
||||
const struct sockaddr_in *addr4;
|
||||
|
|
@ -132,10 +178,12 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
addr4 = (struct sockaddr_in *)address;
|
||||
port = addr4->sin_port;
|
||||
|
||||
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
|
||||
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
|
||||
access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
|
||||
audit_net.dport = port;
|
||||
audit_net.v4info.daddr = addr4->sin_addr.s_addr;
|
||||
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
|
||||
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
|
||||
access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
|
||||
audit_net.sport = port;
|
||||
audit_net.v4info.saddr = addr4->sin_addr.s_addr;
|
||||
} else {
|
||||
|
|
@ -154,10 +202,12 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
addr6 = (struct sockaddr_in6 *)address;
|
||||
port = addr6->sin6_port;
|
||||
|
||||
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
|
||||
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
|
||||
access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
|
||||
audit_net.dport = port;
|
||||
audit_net.v6info.daddr = addr6->sin6_addr;
|
||||
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
|
||||
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
|
||||
access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
|
||||
audit_net.sport = port;
|
||||
audit_net.v6info.saddr = addr6->sin6_addr;
|
||||
} else {
|
||||
|
|
@ -180,7 +230,7 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
* check, but it is safer to return a proper error and test
|
||||
* consistency thanks to kselftest.
|
||||
*/
|
||||
if (address->sa_family != sock->sk->__sk_common.skc_family &&
|
||||
if (address->sa_family != sock_family &&
|
||||
address->sa_family != AF_UNSPEC)
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -198,6 +248,7 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
return 0;
|
||||
|
||||
audit_net.family = address->sa_family;
|
||||
audit_net.sk = sock->sk;
|
||||
landlock_log_denial(subject,
|
||||
&(struct landlock_request){
|
||||
.type = LANDLOCK_REQUEST_NET_ACCESS,
|
||||
|
|
@ -209,6 +260,44 @@ static int current_check_access_socket(struct socket *const sock,
|
|||
return -EACCES;
|
||||
}
|
||||
|
||||
static int current_check_autobind_udp_socket(struct socket *const sock)
|
||||
{
|
||||
const struct access_masks bind_udp = {
|
||||
.net = LANDLOCK_ACCESS_NET_BIND_UDP,
|
||||
};
|
||||
struct sockaddr_storage port0 = {};
|
||||
unsigned short num;
|
||||
bool slow;
|
||||
|
||||
/* Quick return for non-Landlocked tasks. */
|
||||
if (!landlock_get_applicable_subject(current_cred(), bind_udp, NULL))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* On UDP sockets, if a local port has not already been bound, calling
|
||||
* connect() or sending a first datagram has the side effect of
|
||||
* autobinding an ephemeral port: we also have to check that the process
|
||||
* would have had the right to bind(0) explicitly. Hold the socket lock
|
||||
* around the inet_num read to exclude udp_lib_get_port()'s transient
|
||||
* inet_num = snum write that is reverted to 0 on a failing reuseport
|
||||
* bind.
|
||||
*/
|
||||
slow = lock_sock_fast(sock->sk);
|
||||
num = inet_sk(sock->sk)->inet_num;
|
||||
unlock_sock_fast(sock->sk, slow);
|
||||
if (num != 0)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Construct a struct sockaddr* with port 0 to pretend the process tried
|
||||
* to bind() on that address.
|
||||
*/
|
||||
port0.ss_family = READ_ONCE(sock->sk->sk_family);
|
||||
|
||||
return current_check_access_socket(sock, (struct sockaddr *)&port0,
|
||||
sizeof(port0), bind_udp.net, false);
|
||||
}
|
||||
|
||||
static int hook_socket_bind(struct socket *const sock,
|
||||
struct sockaddr *const address, const int addrlen)
|
||||
{
|
||||
|
|
@ -216,11 +305,13 @@ static int hook_socket_bind(struct socket *const sock,
|
|||
|
||||
if (sk_is_tcp(sock->sk))
|
||||
access_request = LANDLOCK_ACCESS_NET_BIND_TCP;
|
||||
else if (sk_is_udp(sock->sk))
|
||||
access_request = LANDLOCK_ACCESS_NET_BIND_UDP;
|
||||
else
|
||||
return 0;
|
||||
|
||||
return current_check_access_socket(sock, address, addrlen,
|
||||
access_request);
|
||||
access_request, false);
|
||||
}
|
||||
|
||||
static int hook_socket_connect(struct socket *const sock,
|
||||
|
|
@ -228,19 +319,57 @@ static int hook_socket_connect(struct socket *const sock,
|
|||
const int addrlen)
|
||||
{
|
||||
access_mask_t access_request;
|
||||
int ret = 0;
|
||||
|
||||
if (sk_is_tcp(sock->sk))
|
||||
access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP;
|
||||
else if (sk_is_udp(sock->sk))
|
||||
access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
|
||||
else
|
||||
return 0;
|
||||
|
||||
return current_check_access_socket(sock, address, addrlen,
|
||||
access_request);
|
||||
ret = current_check_access_socket(sock, address, addrlen,
|
||||
access_request, true);
|
||||
|
||||
/*
|
||||
* connect()ing to an AF_UNSPEC address does not trigger an autobind and
|
||||
* should never be restricted.
|
||||
*/
|
||||
if (ret == 0 && sk_is_udp(sock->sk) &&
|
||||
addrlen >= offsetofend(typeof(*address), sa_family) &&
|
||||
address->sa_family != AF_UNSPEC)
|
||||
ret = current_check_autobind_udp_socket(sock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hook_socket_sendmsg(struct socket *const sock,
|
||||
struct msghdr *const msg, const int size)
|
||||
{
|
||||
struct sockaddr *const address = msg->msg_name;
|
||||
const int addrlen = msg->msg_namelen;
|
||||
access_mask_t access_request;
|
||||
int ret = 0;
|
||||
|
||||
if (sk_is_udp(sock->sk))
|
||||
access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
|
||||
else
|
||||
return 0;
|
||||
|
||||
if (address != NULL)
|
||||
ret = current_check_access_socket(sock, address, addrlen,
|
||||
access_request, false);
|
||||
|
||||
if (ret == 0)
|
||||
ret = current_check_autobind_udp_socket(sock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct security_hook_list landlock_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(socket_bind, hook_socket_bind),
|
||||
LSM_HOOK_INIT(socket_connect, hook_socket_connect),
|
||||
LSM_HOOK_INIT(socket_sendmsg, hook_socket_sendmsg),
|
||||
};
|
||||
|
||||
__init void landlock_add_net_hooks(void)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
__init void landlock_add_net_hooks(void);
|
||||
|
||||
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
||||
const u16 port, access_mask_t access_rights);
|
||||
const u16 port, access_mask_t access_rights,
|
||||
const u32 flags);
|
||||
#else /* IS_ENABLED(CONFIG_INET) */
|
||||
static inline void landlock_add_net_hooks(void)
|
||||
{
|
||||
|
|
@ -24,7 +25,7 @@ static inline void landlock_add_net_hooks(void)
|
|||
|
||||
static inline int
|
||||
landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port,
|
||||
access_mask_t access_rights)
|
||||
access_mask_t access_rights, const u32 flags)
|
||||
{
|
||||
return -EAFNOSUPPORT;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <uapi/linux/landlock.h>
|
||||
|
||||
#include "access.h"
|
||||
#include "domain.h"
|
||||
|
|
@ -255,6 +256,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
|
|||
if (WARN_ON_ONCE(this->layers[0].level != 0))
|
||||
return -EINVAL;
|
||||
this->layers[0].access |= (*layers)[0].access;
|
||||
this->layers[0].flags.quiet |= (*layers)[0].flags.quiet;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -305,12 +307,15 @@ static void build_check_layer(void)
|
|||
/* @ruleset must be locked by the caller. */
|
||||
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
|
||||
const struct landlock_id id,
|
||||
const access_mask_t access)
|
||||
const access_mask_t access, const u32 flags)
|
||||
{
|
||||
struct landlock_layer layers[] = { {
|
||||
.access = access,
|
||||
/* When @level is zero, insert_rule() extends @ruleset. */
|
||||
.level = 0,
|
||||
.flags = {
|
||||
.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
|
||||
},
|
||||
} };
|
||||
|
||||
build_check_layer();
|
||||
|
|
@ -351,6 +356,7 @@ static int merge_tree(struct landlock_ruleset *const dst,
|
|||
return -EINVAL;
|
||||
|
||||
layers[0].access = walker_rule->layers[0].access;
|
||||
layers[0].flags = walker_rule->layers[0].flags;
|
||||
|
||||
err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
|
||||
if (err)
|
||||
|
|
@ -581,6 +587,10 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
|
|||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
|
||||
return no_free_ptr(new_dom);
|
||||
}
|
||||
|
||||
|
|
@ -628,7 +638,7 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
|
|||
* remaining unfulfilled access rights and masks has no leftover set bits).
|
||||
*/
|
||||
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
||||
struct layer_access_masks *masks)
|
||||
struct layer_masks *masks)
|
||||
{
|
||||
if (!masks)
|
||||
return true;
|
||||
|
|
@ -649,11 +659,17 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
|||
const struct landlock_layer *const layer = &rule->layers[i];
|
||||
|
||||
/* Clear the bits where the layer in the rule grants access. */
|
||||
masks->access[layer->level - 1] &= ~layer->access;
|
||||
masks->layers[layer->level - 1].access &= ~layer->access;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
/* Collect rule flags for each layer. */
|
||||
if (layer->flags.quiet)
|
||||
masks->layers[layer->level - 1].quiet = true;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
|
||||
if (masks->access[i])
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||
if (masks->layers[i].access)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -666,8 +682,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
|
|||
/**
|
||||
* landlock_init_layer_masks - Initialize layer masks from an access request
|
||||
*
|
||||
* Populates @masks such that for each access right in @access_request,
|
||||
* the bits for all the layers are set where this access right is handled.
|
||||
* Populates @masks such that for each access right in @access_request, the bits
|
||||
* for all the layers are set where this access right is handled. Rule flags
|
||||
* are also zeroed.
|
||||
*
|
||||
* @domain: The domain that defines the current restrictions.
|
||||
* @access_request: The requested access rights to check.
|
||||
|
|
@ -680,7 +697,7 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
|
|||
access_mask_t
|
||||
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
||||
const access_mask_t access_request,
|
||||
struct layer_access_masks *const masks,
|
||||
struct layer_masks *const masks,
|
||||
const enum landlock_key_type key_type)
|
||||
{
|
||||
access_mask_t handled_accesses = 0;
|
||||
|
|
@ -709,11 +726,19 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
|||
for (size_t i = 0; i < domain->num_layers; i++) {
|
||||
const access_mask_t handled = get_access_mask(domain, i);
|
||||
|
||||
masks->access[i] = access_request & handled;
|
||||
handled_accesses |= masks->access[i];
|
||||
masks->layers[i].access = access_request & handled;
|
||||
handled_accesses |= masks->layers[i].access;
|
||||
#ifdef CONFIG_AUDIT
|
||||
masks->layers[i].quiet = false;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
}
|
||||
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
|
||||
i++) {
|
||||
masks->layers[i].access = 0;
|
||||
#ifdef CONFIG_AUDIT
|
||||
masks->layers[i].quiet = false;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
}
|
||||
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->access); i++)
|
||||
masks->access[i] = 0;
|
||||
|
||||
return handled_accesses;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,18 @@ struct landlock_layer {
|
|||
/**
|
||||
* @level: Position of this layer in the layer stack. Starts from 1.
|
||||
*/
|
||||
u16 level;
|
||||
u8 level;
|
||||
/**
|
||||
* @flags: Bitfield for special flags attached to this rule.
|
||||
*/
|
||||
struct {
|
||||
/**
|
||||
* @quiet: Suppresses denial logs for the object covered by this
|
||||
* rule in this domain. For filesystem rules, this inherits
|
||||
* down the file hierarchy.
|
||||
*/
|
||||
u8 quiet : 1;
|
||||
} flags;
|
||||
/**
|
||||
* @access: Bitfield of allowed actions on the kernel object. They are
|
||||
* relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
|
||||
|
|
@ -145,8 +156,8 @@ struct landlock_ruleset {
|
|||
* @work_free: Enables to free a ruleset within a lockless
|
||||
* section. This is only used by
|
||||
* landlock_put_ruleset_deferred() when @usage reaches zero.
|
||||
* The fields @lock, @usage, @num_rules, @num_layers and
|
||||
* @access_masks are then unused.
|
||||
* The fields @lock, @usage, @num_rules, @num_layers,
|
||||
* @quiet_masks and @access_masks are then unused.
|
||||
*/
|
||||
struct work_struct work_free;
|
||||
struct {
|
||||
|
|
@ -172,6 +183,12 @@ struct landlock_ruleset {
|
|||
* non-merged ruleset (i.e. not a domain).
|
||||
*/
|
||||
u32 num_layers;
|
||||
/**
|
||||
* @quiet_masks: Stores the quiet flags for an unmerged
|
||||
* ruleset. For a merged domain, this is stored in each
|
||||
* layer's struct landlock_hierarchy instead.
|
||||
*/
|
||||
struct access_masks quiet_masks;
|
||||
/**
|
||||
* @access_masks: Contains the subset of filesystem and
|
||||
* network actions that are restricted by a ruleset.
|
||||
|
|
@ -202,7 +219,7 @@ DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *,
|
|||
|
||||
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
|
||||
const struct landlock_id id,
|
||||
const access_mask_t access);
|
||||
const access_mask_t access, const u32 flags);
|
||||
|
||||
struct landlock_ruleset *
|
||||
landlock_merge_ruleset(struct landlock_ruleset *const parent,
|
||||
|
|
@ -302,12 +319,12 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
|
|||
}
|
||||
|
||||
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
||||
struct layer_access_masks *masks);
|
||||
struct layer_masks *masks);
|
||||
|
||||
access_mask_t
|
||||
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
||||
const access_mask_t access_request,
|
||||
struct layer_access_masks *masks,
|
||||
struct layer_masks *masks,
|
||||
const enum landlock_key_type key_type);
|
||||
|
||||
#endif /* _SECURITY_LANDLOCK_RULESET_H */
|
||||
|
|
|
|||
|
|
@ -105,8 +105,11 @@ static void build_check_abi(void)
|
|||
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
|
||||
ruleset_size += sizeof(ruleset_attr.handled_access_net);
|
||||
ruleset_size += sizeof(ruleset_attr.scoped);
|
||||
ruleset_size += sizeof(ruleset_attr.quiet_access_fs);
|
||||
ruleset_size += sizeof(ruleset_attr.quiet_access_net);
|
||||
ruleset_size += sizeof(ruleset_attr.quiet_scoped);
|
||||
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
|
||||
BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
|
||||
BUILD_BUG_ON(sizeof(ruleset_attr) != 48);
|
||||
|
||||
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
|
||||
path_beneath_size += sizeof(path_beneath_attr.parent_fd);
|
||||
|
|
@ -166,7 +169,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 = 9;
|
||||
const int landlock_abi_version = 10;
|
||||
|
||||
/**
|
||||
* sys_landlock_create_ruleset - Create a new ruleset
|
||||
|
|
@ -193,6 +196,9 @@ const int landlock_abi_version = 9;
|
|||
* - %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: 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.
|
||||
|
|
@ -249,6 +255,21 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
|
|||
if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Check that quiet masks are subsets of the respective handled masks.
|
||||
* Because of the checks above this is sufficient to also ensure that
|
||||
* the quiet masks are valid access masks.
|
||||
*/
|
||||
if ((ruleset_attr.quiet_access_fs | ruleset_attr.handled_access_fs) !=
|
||||
ruleset_attr.handled_access_fs)
|
||||
return -EINVAL;
|
||||
if ((ruleset_attr.quiet_access_net | ruleset_attr.handled_access_net) !=
|
||||
ruleset_attr.handled_access_net)
|
||||
return -EINVAL;
|
||||
if ((ruleset_attr.quiet_scoped | ruleset_attr.scoped) !=
|
||||
ruleset_attr.scoped)
|
||||
return -EINVAL;
|
||||
|
||||
/* Checks arguments and transforms to kernel struct. */
|
||||
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
|
||||
ruleset_attr.handled_access_net,
|
||||
|
|
@ -256,6 +277,10 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
|
|||
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;
|
||||
|
||||
/* Creates anonymous FD referring to the ruleset. */
|
||||
ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops,
|
||||
ruleset, O_RDWR | O_CLOEXEC);
|
||||
|
|
@ -320,7 +345,7 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
|
|||
}
|
||||
|
||||
static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
|
||||
const void __user *const rule_attr)
|
||||
const void __user *const rule_attr, u32 flags)
|
||||
{
|
||||
struct landlock_path_beneath_attr path_beneath_attr;
|
||||
struct path path;
|
||||
|
|
@ -335,9 +360,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
|
|||
|
||||
/*
|
||||
* Informs about useless rule: empty allowed_access (i.e. deny rules)
|
||||
* are ignored in path walks.
|
||||
* are ignored in path walks. However, the rule is not useless if it is
|
||||
* there to hold a quiet flag.
|
||||
*/
|
||||
if (!path_beneath_attr.allowed_access)
|
||||
if (!flags && !path_beneath_attr.allowed_access)
|
||||
return -ENOMSG;
|
||||
|
||||
/* Checks that allowed_access matches the @ruleset constraints. */
|
||||
|
|
@ -345,6 +371,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
|
|||
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)
|
||||
return -EINVAL;
|
||||
|
||||
/* Gets and checks the new rule. */
|
||||
err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
|
||||
if (err)
|
||||
|
|
@ -352,13 +382,13 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
|
|||
|
||||
/* Imports the new rule. */
|
||||
err = landlock_append_fs_rule(ruleset, &path,
|
||||
path_beneath_attr.allowed_access);
|
||||
path_beneath_attr.allowed_access, flags);
|
||||
path_put(&path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int add_rule_net_port(struct landlock_ruleset *ruleset,
|
||||
const void __user *const rule_attr)
|
||||
const void __user *const rule_attr, u32 flags)
|
||||
{
|
||||
struct landlock_net_port_attr net_port_attr;
|
||||
int res;
|
||||
|
|
@ -371,9 +401,10 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
|
|||
|
||||
/*
|
||||
* Informs about useless rule: empty allowed_access (i.e. deny rules)
|
||||
* are ignored by network actions.
|
||||
* are ignored by network actions. However, the rule is not useless if
|
||||
* it is there to hold a quiet flag.
|
||||
*/
|
||||
if (!net_port_attr.allowed_access)
|
||||
if (!flags && !net_port_attr.allowed_access)
|
||||
return -ENOMSG;
|
||||
|
||||
/* Checks that allowed_access matches the @ruleset constraints. */
|
||||
|
|
@ -381,13 +412,17 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
|
|||
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)
|
||||
return -EINVAL;
|
||||
|
||||
/* Denies inserting a rule with port greater than 65535. */
|
||||
if (net_port_attr.port > U16_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
/* Imports the new rule. */
|
||||
return landlock_append_net_rule(ruleset, net_port_attr.port,
|
||||
net_port_attr.allowed_access);
|
||||
net_port_attr.allowed_access, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -398,7 +433,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
|
|||
* @rule_type: Identify the structure type pointed to by @rule_attr:
|
||||
* %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
|
||||
* @rule_attr: Pointer to a rule (matching the @rule_type).
|
||||
* @flags: Must be 0.
|
||||
* @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET.
|
||||
*
|
||||
* This system call enables to define a new rule and add it to an existing
|
||||
* ruleset.
|
||||
|
|
@ -408,20 +443,25 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
|
|||
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
|
||||
* - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
|
||||
* supported by the running kernel;
|
||||
* - %EINVAL: @flags is not 0;
|
||||
* - %EINVAL: @flags is not valid;
|
||||
* - %EINVAL: The rule accesses are inconsistent (i.e.
|
||||
* &landlock_path_beneath_attr.allowed_access or
|
||||
* &landlock_net_port_attr.allowed_access is not a subset of the ruleset
|
||||
* handled accesses)
|
||||
* - %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);
|
||||
* 0) and no flags;
|
||||
* - %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
|
||||
* @rule_attr is not the expected file descriptor type;
|
||||
* - %EPERM: @ruleset_fd has no write access to the underlying ruleset;
|
||||
* - %EFAULT: @rule_attr was not a valid address.
|
||||
*
|
||||
* .. kernel-doc:: include/uapi/linux/landlock.h
|
||||
* :identifiers: landlock_add_rule_flags
|
||||
*/
|
||||
SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
|
||||
const enum landlock_rule_type, rule_type,
|
||||
|
|
@ -432,8 +472,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
|
|||
if (!is_initialized())
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* No flag for now. */
|
||||
if (flags)
|
||||
if (flags && flags != LANDLOCK_ADD_RULE_QUIET)
|
||||
return -EINVAL;
|
||||
|
||||
/* Gets and checks the ruleset. */
|
||||
|
|
@ -443,9 +482,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
|
|||
|
||||
switch (rule_type) {
|
||||
case LANDLOCK_RULE_PATH_BENEATH:
|
||||
return add_rule_path_beneath(ruleset, rule_attr);
|
||||
return add_rule_path_beneath(ruleset, rule_attr, flags);
|
||||
case LANDLOCK_RULE_NET_PORT:
|
||||
return add_rule_net_port(ruleset, rule_attr);
|
||||
return add_rule_net_port(ruleset, rule_attr, flags);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,6 +411,17 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
|
|||
if (!subject->domain)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Always allow delivery to the file owner's own process, including a
|
||||
* thread-group leader reached through a process-group owner. This
|
||||
* mirrors hook_task_kill()'s same-process exemption and preserves the
|
||||
* guarantee of commit 18eb75f3af40 ("landlock: Always allow signals
|
||||
* between threads of the same process"), which the registration-time
|
||||
* check cannot honor for a process-group target.
|
||||
*/
|
||||
if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg)
|
||||
return 0;
|
||||
|
||||
scoped_guard(rcu)
|
||||
{
|
||||
is_scoped = domain_is_scoped(subject->domain,
|
||||
|
|
|
|||
|
|
@ -45,17 +45,25 @@ struct audit_message {
|
|||
};
|
||||
};
|
||||
|
||||
static const struct timeval audit_tv_dom_drop = {
|
||||
static const struct timeval audit_tv_default = {
|
||||
/*
|
||||
* Because domain deallocation is tied to asynchronous credential
|
||||
* freeing, receiving such event may take some time. In practice,
|
||||
* on a small VM, it should not exceed 100k usec, but let's wait up
|
||||
* to 1 second to be safe.
|
||||
* Default socket timeout for audit_match_record() callers that expect a
|
||||
* record to arrive. Asynchronous kauditd delivery can exceed 1 usec
|
||||
* under heavy debug configs (KASAN, lockdep), where kauditd_thread
|
||||
* scheduling between audit_log_end() and netlink_unicast() takes longer
|
||||
* than the previous 1 usec timeout. 1 second is a generous ceiling: on
|
||||
* the happy path, kauditd delivers within dozens of usec.
|
||||
*/
|
||||
.tv_sec = 1,
|
||||
};
|
||||
|
||||
static const struct timeval audit_tv_default = {
|
||||
static const struct timeval audit_tv_fast = {
|
||||
/*
|
||||
* Fast timeout for paths that expect no record (audit_init() drain,
|
||||
* audit_count_records(), probes). Causes audit_recv() to return
|
||||
* -EAGAIN once the socket buffer is empty, naturally terminating the
|
||||
* read loop.
|
||||
*/
|
||||
.tv_usec = 1,
|
||||
};
|
||||
|
||||
|
|
@ -334,8 +342,13 @@ static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid,
|
|||
* Matches a domain deallocation record. When expected_domain_id is non-zero,
|
||||
* the pattern includes the specific domain ID so that stale deallocation
|
||||
* records from a previous test (with a different domain ID) are skipped by
|
||||
* audit_match_record(), and the socket timeout is temporarily increased to
|
||||
* audit_tv_dom_drop to wait for the asynchronous kworker deallocation.
|
||||
* audit_match_record(), waiting for the asynchronous kworker deallocation with
|
||||
* the default patient timeout.
|
||||
*
|
||||
* When expected_domain_id is zero, the caller is probing for any dealloc record
|
||||
* that may or may not arrive. Temporarily lowers the socket timeout to
|
||||
* audit_tv_fast for this probe so it returns promptly when no record is
|
||||
* pending; restores audit_tv_default after.
|
||||
*/
|
||||
static int __maybe_unused
|
||||
matches_log_domain_deallocated(int audit_fd, unsigned int num_denials,
|
||||
|
|
@ -361,16 +374,21 @@ matches_log_domain_deallocated(int audit_fd, unsigned int num_denials,
|
|||
if (log_match_len >= sizeof(log_match))
|
||||
return -E2BIG;
|
||||
|
||||
if (expected_domain_id)
|
||||
setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO,
|
||||
&audit_tv_dom_drop, sizeof(audit_tv_dom_drop));
|
||||
if (!expected_domain_id) {
|
||||
if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO,
|
||||
&audit_tv_fast, sizeof(audit_tv_fast)))
|
||||
return -errno;
|
||||
}
|
||||
|
||||
err = audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match,
|
||||
domain_id);
|
||||
|
||||
if (expected_domain_id)
|
||||
setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
|
||||
sizeof(audit_tv_default));
|
||||
if (!expected_domain_id) {
|
||||
if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO,
|
||||
&audit_tv_default, sizeof(audit_tv_default)) &&
|
||||
!err)
|
||||
err = -errno;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
@ -381,30 +399,46 @@ struct audit_records {
|
|||
};
|
||||
|
||||
/*
|
||||
* WARNING: Do not assert records.domain == 0 without a preceding
|
||||
* audit_match_record() call. Domain deallocation records are emitted
|
||||
* asynchronously from kworker threads and can arrive after the drain in
|
||||
* audit_init(), corrupting the domain count. A preceding audit_match_record()
|
||||
* call consumes stale records while scanning, making the assertion safe in
|
||||
* practice because stale deallocation records arrive before the expected access
|
||||
* records.
|
||||
* Counts remaining audit records by type, skipping domain deallocation records.
|
||||
* Deallocation records are emitted asynchronously from kworker threads after a
|
||||
* previous test's child has exited, so they can arrive after the drain in
|
||||
* audit_init() and after the preceding audit_match_record() call. Allocation
|
||||
* records are emitted synchronously during landlock_log_denial() in the current
|
||||
* test's syscall context, so only those are counted in records->domain.
|
||||
*
|
||||
* Temporarily lowers SO_RCVTIMEO to audit_tv_fast for the read loop: this is a
|
||||
* "no record expected" path that should terminate on the first -EAGAIN. The
|
||||
* default patient timeout is restored on exit for subsequent
|
||||
* audit_match_record() callers.
|
||||
*/
|
||||
static int audit_count_records(int audit_fd, struct audit_records *records)
|
||||
{
|
||||
static const char dealloc_pattern[] = REGEX_LANDLOCK_PREFIX
|
||||
" status=deallocated ";
|
||||
struct audit_message msg;
|
||||
int err;
|
||||
regex_t dealloc_re;
|
||||
int ret, err = 0;
|
||||
|
||||
ret = regcomp(&dealloc_re, dealloc_pattern, 0);
|
||||
if (ret)
|
||||
return -ENOMEM;
|
||||
|
||||
records->access = 0;
|
||||
records->domain = 0;
|
||||
|
||||
if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_fast,
|
||||
sizeof(audit_tv_fast))) {
|
||||
err = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
do {
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
err = audit_recv(audit_fd, &msg);
|
||||
if (err) {
|
||||
if (err == -EAGAIN)
|
||||
return 0;
|
||||
else
|
||||
return err;
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (msg.header.nlmsg_type) {
|
||||
|
|
@ -412,12 +446,24 @@ static int audit_count_records(int audit_fd, struct audit_records *records)
|
|||
records->access++;
|
||||
break;
|
||||
case AUDIT_LANDLOCK_DOMAIN:
|
||||
records->domain++;
|
||||
ret = regexec(&dealloc_re, msg.data, 0, NULL, 0);
|
||||
if (ret == REG_NOMATCH) {
|
||||
records->domain++;
|
||||
} else if (ret != 0) {
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
|
||||
sizeof(audit_tv_default)) &&
|
||||
!err)
|
||||
err = -errno;
|
||||
regfree(&dealloc_re);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int audit_init(void)
|
||||
|
|
@ -436,9 +482,9 @@ static int audit_init(void)
|
|||
if (err)
|
||||
goto err_close;
|
||||
|
||||
/* Sets a timeout for negative tests. */
|
||||
err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
|
||||
sizeof(audit_tv_default));
|
||||
/* Uses the fast timeout to drain stale records below. */
|
||||
err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_fast,
|
||||
sizeof(audit_tv_fast));
|
||||
if (err) {
|
||||
err = -errno;
|
||||
goto err_close;
|
||||
|
|
@ -454,6 +500,19 @@ static int audit_init(void)
|
|||
while (audit_recv(fd, NULL) == 0)
|
||||
;
|
||||
|
||||
/*
|
||||
* Restores the default timeout for audit_match_record() callers that
|
||||
* expect a record to arrive. Paths that expect no record restore the
|
||||
* fast timeout locally (audit_count_records(), the expected_domain_id
|
||||
* == 0 probe in matches_log_domain_deallocated()).
|
||||
*/
|
||||
err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
|
||||
sizeof(audit_tv_default));
|
||||
if (err) {
|
||||
err = -errno;
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
return fd;
|
||||
|
||||
err_close:
|
||||
|
|
@ -494,10 +553,9 @@ static int audit_init_filter_exe(struct audit_filter *filter, const char *path)
|
|||
static int audit_cleanup(int audit_fd, struct audit_filter *filter)
|
||||
{
|
||||
struct audit_filter new_filter;
|
||||
int err = 0;
|
||||
|
||||
if (audit_fd < 0 || !filter) {
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Simulates audit_init_with_exe_filter() when called from
|
||||
* FIXTURE_TEARDOWN_PARENT().
|
||||
|
|
@ -508,23 +566,19 @@ static int audit_cleanup(int audit_fd, struct audit_filter *filter)
|
|||
|
||||
filter = &new_filter;
|
||||
err = audit_init_filter_exe(filter, NULL);
|
||||
if (err) {
|
||||
close(audit_fd);
|
||||
return err;
|
||||
}
|
||||
if (err)
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
/* Filters might not be in place. */
|
||||
audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE);
|
||||
audit_filter_drop(audit_fd, AUDIT_DEL_RULE);
|
||||
|
||||
/*
|
||||
* Because audit_cleanup() might not be called by the test auditd
|
||||
* process, it might not be possible to explicitly set it. Anyway,
|
||||
* AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd
|
||||
* process will exit.
|
||||
*/
|
||||
return close(audit_fd);
|
||||
err = audit_set_status(audit_fd, AUDIT_STATUS_ENABLED, 0);
|
||||
|
||||
err_close:
|
||||
close(audit_fd);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int audit_init_with_exe_filter(struct audit_filter *filter)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ TEST_F(audit, layers)
|
|||
.scoped = LANDLOCK_SCOPE_SIGNAL,
|
||||
};
|
||||
int status, ruleset_fd, i;
|
||||
__u64(*domain_stack)[16];
|
||||
__u64(*domain_stack)[LANDLOCK_MAX_NUM_LAYERS];
|
||||
__u64 prev_dom = 3;
|
||||
pid_t child;
|
||||
|
||||
|
|
@ -607,30 +607,42 @@ FIXTURE(audit_flags)
|
|||
FIXTURE_VARIANT(audit_flags)
|
||||
{
|
||||
const int restrict_flags;
|
||||
const __u64 quiet_scoped;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(audit_flags, default) {
|
||||
/* clang-format on */
|
||||
.restrict_flags = 0,
|
||||
.quiet_scoped = 0,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(audit_flags, same_exec_off) {
|
||||
/* clang-format on */
|
||||
.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
|
||||
.quiet_scoped = 0,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(audit_flags, subdomains_off) {
|
||||
/* clang-format on */
|
||||
.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
|
||||
.quiet_scoped = 0,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(audit_flags, cross_exec_on) {
|
||||
/* clang-format on */
|
||||
.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
|
||||
.quiet_scoped = 0,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(audit_flags, signal_quieted) {
|
||||
/* clang-format on */
|
||||
.restrict_flags = 0,
|
||||
.quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(audit_flags)
|
||||
|
|
@ -674,12 +686,16 @@ TEST_F(audit_flags, signal)
|
|||
pid_t child;
|
||||
struct audit_records records;
|
||||
__u64 deallocated_dom = 2;
|
||||
bool expect_audit = !(variant->restrict_flags &
|
||||
LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) &&
|
||||
!(variant->quiet_scoped & LANDLOCK_SCOPE_SIGNAL);
|
||||
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
if (child == 0) {
|
||||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.scoped = LANDLOCK_SCOPE_SIGNAL,
|
||||
.quiet_scoped = variant->quiet_scoped,
|
||||
};
|
||||
int ruleset_fd;
|
||||
|
||||
|
|
@ -696,8 +712,7 @@ TEST_F(audit_flags, signal)
|
|||
EXPECT_EQ(-1, kill(getppid(), 0));
|
||||
EXPECT_EQ(EPERM, errno);
|
||||
|
||||
if (variant->restrict_flags &
|
||||
LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
|
||||
if (!expect_audit) {
|
||||
EXPECT_EQ(-EAGAIN, matches_log_signal(
|
||||
_metadata, self->audit_fd,
|
||||
getppid(), self->domain_id));
|
||||
|
|
@ -724,12 +739,12 @@ TEST_F(audit_flags, signal)
|
|||
|
||||
/* Makes sure there is no superfluous logged records. */
|
||||
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
|
||||
if (variant->restrict_flags &
|
||||
LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
|
||||
if (!expect_audit) {
|
||||
EXPECT_EQ(0, records.access);
|
||||
} else {
|
||||
EXPECT_EQ(1, records.access);
|
||||
}
|
||||
EXPECT_EQ(0, records.domain);
|
||||
|
||||
/* Updates filter rules to match the drop record. */
|
||||
set_cap(_metadata, CAP_AUDIT_CONTROL);
|
||||
|
|
@ -748,8 +763,7 @@ TEST_F(audit_flags, signal)
|
|||
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||||
_metadata->exit_code = KSFT_FAIL;
|
||||
|
||||
if (variant->restrict_flags &
|
||||
LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
|
||||
if (!expect_audit) {
|
||||
/*
|
||||
* No deallocation record: denials=0 never matches a real
|
||||
* record.
|
||||
|
|
@ -849,10 +863,8 @@ FIXTURE_SETUP(audit_exec)
|
|||
FIXTURE_TEARDOWN(audit_exec)
|
||||
{
|
||||
set_cap(_metadata, CAP_AUDIT_CONTROL);
|
||||
EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter,
|
||||
AUDIT_DEL_RULE));
|
||||
EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter));
|
||||
clear_cap(_metadata, CAP_AUDIT_CONTROL);
|
||||
EXPECT_EQ(0, close(self->audit_fd));
|
||||
}
|
||||
|
||||
TEST_F(audit_exec, signal_and_open)
|
||||
|
|
@ -917,6 +929,7 @@ TEST_F(audit_exec, signal_and_open)
|
|||
/* Tests that there was no denial until now. */
|
||||
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
|
||||
EXPECT_EQ(0, records.access);
|
||||
EXPECT_EQ(0, records.domain);
|
||||
|
||||
/*
|
||||
* Wait for the child to do a first denied action by layer1 and
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ TEST(abi_version)
|
|||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
};
|
||||
ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
|
||||
LANDLOCK_CREATE_RULESET_VERSION));
|
||||
ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
|
||||
LANDLOCK_CREATE_RULESET_VERSION));
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
|
||||
LANDLOCK_CREATE_RULESET_VERSION));
|
||||
|
|
@ -201,7 +201,7 @@ TEST(add_rule_checks_ordering)
|
|||
ASSERT_LE(0, ruleset_fd);
|
||||
|
||||
/* Checks invalid flags. */
|
||||
ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
|
||||
ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 100));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
/* Checks invalid ruleset FD. */
|
||||
|
|
@ -526,4 +526,120 @@ TEST(cred_transfer)
|
|||
EXPECT_EQ(EACCES, errno);
|
||||
}
|
||||
|
||||
TEST(useless_quiet_rule_fs)
|
||||
{
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.quiet_access_fs = 0,
|
||||
};
|
||||
struct landlock_path_beneath_attr path_beneath_attr = {
|
||||
.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
};
|
||||
int ruleset_fd, root_fd;
|
||||
|
||||
drop_caps(_metadata);
|
||||
ruleset_fd =
|
||||
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
|
||||
root_fd = open("/", O_PATH | O_CLOEXEC);
|
||||
ASSERT_LE(0, root_fd);
|
||||
path_beneath_attr.parent_fd = root_fd;
|
||||
ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
|
||||
&path_beneath_attr,
|
||||
LANDLOCK_ADD_RULE_QUIET));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
/* Check that the rule had not been added. */
|
||||
ASSERT_EQ(0, close(root_fd));
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
ASSERT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
}
|
||||
|
||||
TEST(useless_quiet_rule_net)
|
||||
{
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
.quiet_access_net = 0,
|
||||
};
|
||||
struct landlock_net_port_attr net_port_attr = {
|
||||
.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
.port = 1024,
|
||||
};
|
||||
int ruleset_fd;
|
||||
|
||||
drop_caps(_metadata);
|
||||
ruleset_fd =
|
||||
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
|
||||
ASSERT_EQ(-1,
|
||||
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&net_port_attr, LANDLOCK_ADD_RULE_QUIET));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
}
|
||||
|
||||
TEST(invalid_quiet_bits_1)
|
||||
{
|
||||
const struct landlock_ruleset_attr ruleset_attr_fs = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.quiet_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
};
|
||||
const struct landlock_ruleset_attr ruleset_attr_net = {
|
||||
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
.quiet_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
};
|
||||
const struct landlock_ruleset_attr ruleset_attr_scoped = {
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
.quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
|
||||
};
|
||||
|
||||
/* Quiet bit set but not part of the handled mask. */
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_fs,
|
||||
sizeof(ruleset_attr_fs), 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_net,
|
||||
sizeof(ruleset_attr_net), 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_scoped,
|
||||
sizeof(ruleset_attr_scoped), 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
}
|
||||
|
||||
TEST(invalid_quiet_bits_2)
|
||||
{
|
||||
const struct landlock_ruleset_attr ruleset_attr_fs = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.quiet_access_fs = 1ULL << 63,
|
||||
};
|
||||
const struct landlock_ruleset_attr ruleset_attr_net = {
|
||||
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
.quiet_access_net = 1ULL << 63,
|
||||
};
|
||||
const struct landlock_ruleset_attr ruleset_attr_scoped = {
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
.quiet_scoped = 1ULL << 63,
|
||||
};
|
||||
|
||||
/* Quiet bit outside of the valid access range. */
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_fs,
|
||||
sizeof(ruleset_attr_fs), 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_net,
|
||||
sizeof(ruleset_attr_net), 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr_scoped,
|
||||
sizeof(ruleset_attr_scoped), 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
/* TEST_F_FORK() should not be used for new tests. */
|
||||
#define TEST_F_FORK(fixture_name, test_name) TEST_F(fixture_name, test_name)
|
||||
|
||||
#define LANDLOCK_MAX_NUM_LAYERS 16
|
||||
|
||||
static const char bin_sandbox_and_launch[] = "./sandbox-and-launch";
|
||||
static const char bin_wait_pipe[] = "./wait-pipe";
|
||||
static const char bin_wait_pipe_sandbox[] = "./wait-pipe-sandbox";
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -342,6 +342,7 @@ TEST_F(audit, trace)
|
|||
/* Makes sure there is no superfluous logged records. */
|
||||
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
|
||||
EXPECT_EQ(0, records.access);
|
||||
EXPECT_EQ(0, records.domain);
|
||||
|
||||
yama_ptrace_scope = get_yama_ptrace_scope();
|
||||
ASSERT_LE(0, yama_ptrace_scope);
|
||||
|
|
|
|||
|
|
@ -293,6 +293,45 @@ FIXTURE_TEARDOWN_PARENT(scoped_audit)
|
|||
EXPECT_EQ(0, audit_cleanup(-1, NULL));
|
||||
}
|
||||
|
||||
FIXTURE_VARIANT(scoped_audit)
|
||||
{
|
||||
const __u64 scoped;
|
||||
const __u64 quiet_scoped;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(scoped_audit, no_quiet)
|
||||
{
|
||||
/* clang-format on */
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
.quiet_scoped = 0,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(scoped_audit, quiet_abstract_socket)
|
||||
{
|
||||
/* clang-format on */
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
.quiet_scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(scoped_audit, quiet_abstract_socket_2)
|
||||
{
|
||||
/* clang-format on */
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL,
|
||||
.quiet_scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
||||
LANDLOCK_SCOPE_SIGNAL,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(scoped_audit, quiet_unrelated)
|
||||
{
|
||||
/* clang-format on */
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL,
|
||||
.quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
|
||||
};
|
||||
|
||||
/* python -c 'print(b"\0selftests-landlock-abstract-unix-".hex().upper())' */
|
||||
#define ABSTRACT_SOCKET_PATH_PREFIX \
|
||||
"0073656C6674657374732D6C616E646C6F636B2D61627374726163742D756E69782D"
|
||||
|
|
@ -308,10 +347,18 @@ TEST_F(scoped_audit, connect_to_child)
|
|||
char buf;
|
||||
int dgram_client;
|
||||
struct audit_records records;
|
||||
int ruleset_fd;
|
||||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.scoped = variant->scoped,
|
||||
.quiet_scoped = variant->quiet_scoped,
|
||||
};
|
||||
bool should_audit =
|
||||
!(variant->quiet_scoped & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||||
|
||||
/* Makes sure there is no superfluous logged records. */
|
||||
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
|
||||
EXPECT_EQ(0, records.access);
|
||||
EXPECT_EQ(0, records.domain);
|
||||
|
||||
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
|
||||
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||||
|
|
@ -344,7 +391,14 @@ TEST_F(scoped_audit, connect_to_child)
|
|||
EXPECT_EQ(0, close(pipe_child[1]));
|
||||
EXPECT_EQ(0, close(pipe_parent[0]));
|
||||
|
||||
create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||||
ruleset_fd =
|
||||
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd)
|
||||
{
|
||||
TH_LOG("Failed to create a ruleset: %s", strerror(errno));
|
||||
}
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
EXPECT_EQ(0, close(ruleset_fd));
|
||||
|
||||
/* Signals that the parent is in a domain, if any. */
|
||||
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||||
|
|
@ -359,14 +413,20 @@ TEST_F(scoped_audit, connect_to_child)
|
|||
EXPECT_EQ(-1, err_dgram);
|
||||
EXPECT_EQ(EPERM, errno);
|
||||
|
||||
EXPECT_EQ(
|
||||
0,
|
||||
audit_match_record(
|
||||
self->audit_fd, AUDIT_LANDLOCK_ACCESS,
|
||||
REGEX_LANDLOCK_PREFIX
|
||||
" blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
|
||||
"[0-9A-F]\\+$",
|
||||
NULL));
|
||||
if (should_audit) {
|
||||
EXPECT_EQ(
|
||||
0,
|
||||
audit_match_record(
|
||||
self->audit_fd, AUDIT_LANDLOCK_ACCESS,
|
||||
REGEX_LANDLOCK_PREFIX
|
||||
" blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
|
||||
"[0-9A-F]\\+$",
|
||||
NULL));
|
||||
}
|
||||
|
||||
/* No other logs */
|
||||
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
|
||||
EXPECT_EQ(0, records.access);
|
||||
|
||||
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||||
EXPECT_EQ(0, close(dgram_client));
|
||||
|
|
|
|||
|
|
@ -559,4 +559,186 @@ TEST_F(fown, sigurg_socket)
|
|||
_metadata->exit_code = KSFT_FAIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks that LANDLOCK_SCOPE_SIGNAL is enforced on the asynchronous SIGIO
|
||||
* delivery path (fcntl(F_SETOWN)) when the file owner is a process group.
|
||||
*
|
||||
* A sandboxed process sitting at the head of its process group's PID hlist (the
|
||||
* default position right after fork()) used to escape the fcntl(F_SETOWN,
|
||||
* -pgrp) domain recording: pid_task(pgrp, PIDTYPE_PGID) resolved to the process
|
||||
* itself, so the same-thread-group exemption skipped recording its Landlock
|
||||
* domain. At SIGIO time that domain was then unset and the signal fanned out
|
||||
* to every group member, including non-sandboxed processes outside the domain.
|
||||
*/
|
||||
TEST(sigio_to_pgid_members)
|
||||
{
|
||||
int trigger[2], sync_child[2];
|
||||
char buf;
|
||||
pid_t child;
|
||||
int status, i;
|
||||
|
||||
drop_caps(_metadata);
|
||||
|
||||
/*
|
||||
* Isolates the test in its own process group so the SIGIO fan-out stays
|
||||
* bounded to this parent and the child forked below.
|
||||
*/
|
||||
ASSERT_EQ(0, setpgid(0, 0));
|
||||
|
||||
/* The non-sandboxed parent is the protected (out-of-domain) target. */
|
||||
ASSERT_EQ(0, setup_signal_handler(SIGURG));
|
||||
signal_received = 0;
|
||||
|
||||
ASSERT_EQ(0, pipe2(trigger, O_CLOEXEC));
|
||||
ASSERT_EQ(0, pipe2(sync_child, O_CLOEXEC));
|
||||
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
if (child == 0) {
|
||||
/*
|
||||
* The child inherits the parent's new process group and, just
|
||||
* attached with hlist_add_head_rcu(), is now the head of the
|
||||
* pgid hlist: this is the case that used to skip the recording.
|
||||
*/
|
||||
EXPECT_EQ(0, close(sync_child[0]));
|
||||
|
||||
/* In-domain positive control: the child must be signaled. */
|
||||
ASSERT_EQ(0, setup_signal_handler(SIGURG));
|
||||
signal_received = 0;
|
||||
|
||||
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
|
||||
|
||||
/* Owns the SIGIO source for the whole process group. */
|
||||
ASSERT_EQ(0, fcntl(trigger[0], F_SETSIG, SIGURG));
|
||||
ASSERT_EQ(0, fcntl(trigger[0], F_SETOWN, -getpgrp()));
|
||||
ASSERT_EQ(0, fcntl(trigger[0], F_SETFL, O_ASYNC));
|
||||
|
||||
/* Fans SIGURG out to every member of the process group. */
|
||||
ASSERT_EQ(1, write(trigger[1], ".", 1));
|
||||
|
||||
/*
|
||||
* The sandboxed child is in its own domain and must always be
|
||||
* signaled: this proves the SIGIO actually fired.
|
||||
*/
|
||||
for (i = 0; i < 1000 && !signal_received; i++)
|
||||
usleep(1000);
|
||||
EXPECT_EQ(1, signal_received);
|
||||
|
||||
ASSERT_EQ(1, write(sync_child[1], ".", 1));
|
||||
EXPECT_EQ(0, close(sync_child[1]));
|
||||
|
||||
_exit(_metadata->exit_code);
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(0, close(sync_child[1]));
|
||||
EXPECT_EQ(0, close(trigger[0]));
|
||||
EXPECT_EQ(0, close(trigger[1]));
|
||||
|
||||
/* Waits for the child to generate the SIGIO. */
|
||||
ASSERT_EQ(1, read(sync_child[0], &buf, 1));
|
||||
EXPECT_EQ(0, close(sync_child[0]));
|
||||
|
||||
/* Lets a delivered-but-pending signal run our handler, if any. */
|
||||
for (i = 0; i < 100 && !signal_received; i++)
|
||||
usleep(1000);
|
||||
|
||||
/*
|
||||
* SCOPE_SIGNAL must block the fan-out to this non-sandboxed parent,
|
||||
* which is outside the child's Landlock domain. Before the fix the
|
||||
* parent was signaled here.
|
||||
*/
|
||||
EXPECT_EQ(0, signal_received);
|
||||
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||||
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||||
_metadata->exit_code = KSFT_FAIL;
|
||||
}
|
||||
|
||||
static void *thread_setown_scoped(void *arg)
|
||||
{
|
||||
const int fd = *(int *)arg;
|
||||
int ruleset_fd;
|
||||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.scoped = LANDLOCK_SCOPE_SIGNAL,
|
||||
};
|
||||
|
||||
/* Sandboxes only this non-leader thread (no thread syncing). */
|
||||
ruleset_fd =
|
||||
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0)
|
||||
return (void *)THREAD_ERROR;
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
|
||||
landlock_restrict_self(ruleset_fd, 0)) {
|
||||
close(ruleset_fd);
|
||||
return (void *)THREAD_ERROR;
|
||||
}
|
||||
close(ruleset_fd);
|
||||
|
||||
/* Makes this process group own the SIGIO source. */
|
||||
if (fcntl(fd, F_SETSIG, SIGURG) || fcntl(fd, F_SETOWN, -getpgrp()) ||
|
||||
fcntl(fd, F_SETFL, O_ASYNC))
|
||||
return (void *)THREAD_ERROR;
|
||||
|
||||
return (void *)THREAD_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks that the SIGIO fan-out is still delivered to the file owner's own
|
||||
* process when fcntl(F_SETOWN, -pgrp) was issued from a sandboxed non-leader
|
||||
* thread.
|
||||
*
|
||||
* The Landlock domain is recorded for a process-group owner (so out-of-domain
|
||||
* members stay blocked, see sigio_to_pgid_members), but the kernel signals a
|
||||
* process group through its members' thread-group leaders. Here the leader is
|
||||
* not sandboxed and thus has a different domain than the registering thread, so
|
||||
* the registration-time check cannot tell that it belongs to the owner's own
|
||||
* process. hook_file_send_sigiotask() must recognize it through the recorded
|
||||
* thread group and allow the delivery, matching the same-process guarantee of
|
||||
* commit 18eb75f3af40. Without that exemption the leader is wrongly denied and
|
||||
* never signaled.
|
||||
*/
|
||||
TEST(sigio_to_pgid_self)
|
||||
{
|
||||
int trigger[2];
|
||||
pthread_t thread;
|
||||
enum thread_return ret = THREAD_INVALID;
|
||||
int i;
|
||||
|
||||
drop_caps(_metadata);
|
||||
|
||||
/* Bounds the SIGIO fan-out to this process. */
|
||||
ASSERT_EQ(0, setpgid(0, 0));
|
||||
|
||||
/* The non-sandboxed thread-group leader is the SIGIO target. */
|
||||
ASSERT_EQ(0, setup_signal_handler(SIGURG));
|
||||
signal_received = 0;
|
||||
|
||||
ASSERT_EQ(0, pipe2(trigger, O_CLOEXEC));
|
||||
|
||||
/*
|
||||
* Registers the process-group fowner from a sibling thread that
|
||||
* sandboxes only itself, so its domain differs from the leader's.
|
||||
*/
|
||||
ASSERT_EQ(0, pthread_create(&thread, NULL, thread_setown_scoped,
|
||||
&trigger[0]));
|
||||
ASSERT_EQ(0, pthread_join(thread, (void **)&ret));
|
||||
ASSERT_EQ(THREAD_SUCCESS, ret);
|
||||
|
||||
/* Fans SIGURG out to the process group. */
|
||||
ASSERT_EQ(1, write(trigger[1], ".", 1));
|
||||
|
||||
for (i = 0; i < 1000 && !signal_received; i++)
|
||||
usleep(1000);
|
||||
|
||||
/*
|
||||
* Same-process delivery must always be allowed, even though the owner
|
||||
* was registered from a sandboxed sibling thread.
|
||||
*/
|
||||
EXPECT_EQ(1, signal_received);
|
||||
|
||||
EXPECT_EQ(0, close(trigger[0]));
|
||||
EXPECT_EQ(0, close(trigger[1]));
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user