mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-01 10:45:49 +00:00
Landlock fix for v6.12-rc7
-----BEGIN PGP SIGNATURE----- iIYEABYKAC4WIQSVyBthFV4iTW/VU1/l49DojIL20gUCZy+y6BAcbWljQGRpZ2lr b2QubmV0AAoJEOXj0OiMgvbSXcAA/jrpBdfMi6MXbZkOXHw2H46j2jpBpOq67pND LqC2LA8bAP9JyiGdFF8ETch59zSa3mpKp4C/k8g/F3XwmSsqLOh3BA== =pF7T -----END PGP SIGNATURE----- Merge tag 'landlock-6.12-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux Pull landlock fixes from Mickaël Salaün: "This fixes issues in the Landlock's sandboxer sample and documentation, slightly refactors helpers (required for ongoing patch series), and improve/fix a feature merged in v6.12 (signal and abstract UNIX socket scoping)" * tag 'landlock-6.12-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux: landlock: Optimize scope enforcement landlock: Refactor network access mask management landlock: Refactor filesystem access mask management samples/landlock: Clarify option parsing behaviour samples/landlock: Refactor help message samples/landlock: Fix port parsing in sandboxer landlock: Fix grammar issues in documentation landlock: Improve documentation of previous limitations
This commit is contained in:
commit
92dda329e3
@ -11,18 +11,18 @@ Landlock LSM: kernel documentation
|
|||||||
|
|
||||||
Landlock's goal is to create scoped access-control (i.e. sandboxing). To
|
Landlock's goal is to create scoped access-control (i.e. sandboxing). To
|
||||||
harden a whole system, this feature should be available to any process,
|
harden a whole system, this feature should be available to any process,
|
||||||
including unprivileged ones. Because such process may be compromised or
|
including unprivileged ones. Because such a process may be compromised or
|
||||||
backdoored (i.e. untrusted), Landlock's features must be safe to use from the
|
backdoored (i.e. untrusted), Landlock's features must be safe to use from the
|
||||||
kernel and other processes point of view. Landlock's interface must therefore
|
kernel and other processes point of view. Landlock's interface must therefore
|
||||||
expose a minimal attack surface.
|
expose a minimal attack surface.
|
||||||
|
|
||||||
Landlock is designed to be usable by unprivileged processes while following the
|
Landlock is designed to be usable by unprivileged processes while following the
|
||||||
system security policy enforced by other access control mechanisms (e.g. DAC,
|
system security policy enforced by other access control mechanisms (e.g. DAC,
|
||||||
LSM). Indeed, a Landlock rule shall not interfere with other access-controls
|
LSM). A Landlock rule shall not interfere with other access-controls enforced
|
||||||
enforced on the system, only add more restrictions.
|
on the system, only add more restrictions.
|
||||||
|
|
||||||
Any user can enforce Landlock rulesets on their processes. They are merged and
|
Any user can enforce Landlock rulesets on their processes. They are merged and
|
||||||
evaluated according to the inherited ones in a way that ensures that only more
|
evaluated against inherited rulesets in a way that ensures that only more
|
||||||
constraints can be added.
|
constraints can be added.
|
||||||
|
|
||||||
User space documentation can be found here:
|
User space documentation can be found here:
|
||||||
@ -43,7 +43,7 @@ Guiding principles for safe access controls
|
|||||||
only impact the processes requesting them.
|
only impact the processes requesting them.
|
||||||
* Resources (e.g. file descriptors) directly obtained from the kernel by a
|
* Resources (e.g. file descriptors) directly obtained from the kernel by a
|
||||||
sandboxed process shall retain their scoped accesses (at the time of resource
|
sandboxed process shall retain their scoped accesses (at the time of resource
|
||||||
acquisition) whatever process use them.
|
acquisition) whatever process uses them.
|
||||||
Cf. `File descriptor access rights`_.
|
Cf. `File descriptor access rights`_.
|
||||||
|
|
||||||
Design choices
|
Design choices
|
||||||
@ -71,7 +71,7 @@ the same results, when they are executed under the same Landlock domain.
|
|||||||
Taking the ``LANDLOCK_ACCESS_FS_TRUNCATE`` right as an example, it may be
|
Taking the ``LANDLOCK_ACCESS_FS_TRUNCATE`` right as an example, it may be
|
||||||
allowed to open a file for writing without being allowed to
|
allowed to open a file for writing without being allowed to
|
||||||
:manpage:`ftruncate` the resulting file descriptor if the related file
|
:manpage:`ftruncate` the resulting file descriptor if the related file
|
||||||
hierarchy doesn't grant such access right. The following sequences of
|
hierarchy doesn't grant that access right. The following sequences of
|
||||||
operations have the same semantic and should then have the same result:
|
operations have the same semantic and should then have the same result:
|
||||||
|
|
||||||
* ``truncate(path);``
|
* ``truncate(path);``
|
||||||
@ -81,7 +81,7 @@ Similarly to file access modes (e.g. ``O_RDWR``), Landlock access rights
|
|||||||
attached to file descriptors are retained even if they are passed between
|
attached to file descriptors are retained even if they are passed between
|
||||||
processes (e.g. through a Unix domain socket). Such access rights will then be
|
processes (e.g. through a Unix domain socket). Such access rights will then be
|
||||||
enforced even if the receiving process is not sandboxed by Landlock. Indeed,
|
enforced even if the receiving process is not sandboxed by Landlock. Indeed,
|
||||||
this is required to keep a consistent access control over the whole system, and
|
this is required to keep access controls consistent over the whole system, and
|
||||||
this avoids unattended bypasses through file descriptor passing (i.e. confused
|
this avoids unattended bypasses through file descriptor passing (i.e. confused
|
||||||
deputy attack).
|
deputy attack).
|
||||||
|
|
||||||
|
@ -8,13 +8,13 @@ Landlock: unprivileged access control
|
|||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
:Author: Mickaël Salaün
|
:Author: Mickaël Salaün
|
||||||
:Date: September 2024
|
:Date: October 2024
|
||||||
|
|
||||||
The goal of Landlock is to enable to restrict ambient rights (e.g. global
|
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
|
filesystem or network access) for a set of processes. Because Landlock
|
||||||
is a stackable LSM, it makes possible to create safe security sandboxes as new
|
is a stackable LSM, it makes it possible to create safe security sandboxes as
|
||||||
security layers in addition to the existing system-wide access-controls. This
|
new security layers in addition to the existing system-wide access-controls.
|
||||||
kind of sandbox is expected to help mitigate the security impact of bugs or
|
This kind of sandbox is expected to help mitigate the security impact of bugs or
|
||||||
unexpected/malicious behaviors in user space applications. Landlock empowers
|
unexpected/malicious behaviors in user space applications. Landlock empowers
|
||||||
any process, including unprivileged ones, to securely restrict themselves.
|
any process, including unprivileged ones, to securely restrict themselves.
|
||||||
|
|
||||||
@ -86,8 +86,8 @@ to be explicit about the denied-by-default access rights.
|
|||||||
LANDLOCK_SCOPE_SIGNAL,
|
LANDLOCK_SCOPE_SIGNAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
Because we may not know on which kernel version an application will be
|
Because we may not know which kernel version an application will be executed
|
||||||
executed, it is safer to follow a best-effort security approach. Indeed, we
|
on, it is safer to follow a best-effort security approach. Indeed, we
|
||||||
should try to protect users as much as possible whatever the kernel they are
|
should try to protect users as much as possible whatever the kernel they are
|
||||||
using.
|
using.
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ version, and only use the available subset of access rights:
|
|||||||
LANDLOCK_SCOPE_SIGNAL);
|
LANDLOCK_SCOPE_SIGNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
This enables to create an inclusive ruleset that will contain our rules.
|
This enables the creation of an inclusive ruleset that will contain our rules.
|
||||||
|
|
||||||
.. code-block:: c
|
.. code-block:: c
|
||||||
|
|
||||||
@ -219,42 +219,41 @@ If the ``landlock_restrict_self`` system call succeeds, the current thread is
|
|||||||
now restricted and this policy will be enforced on all its subsequently created
|
now restricted and this policy will be enforced on all its subsequently created
|
||||||
children as well. Once a thread is landlocked, there is no way to remove its
|
children as well. Once a thread is landlocked, there is no way to remove its
|
||||||
security policy; only adding more restrictions is allowed. These threads are
|
security policy; only adding more restrictions is allowed. These threads are
|
||||||
now in a new Landlock domain, merge of their parent one (if any) with the new
|
now in a new Landlock domain, which is a merger of their parent one (if any)
|
||||||
ruleset.
|
with the new ruleset.
|
||||||
|
|
||||||
Full working code can be found in `samples/landlock/sandboxer.c`_.
|
Full working code can be found in `samples/landlock/sandboxer.c`_.
|
||||||
|
|
||||||
Good practices
|
Good practices
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
It is recommended setting access rights to file hierarchy leaves as much as
|
It is recommended to set access rights to file hierarchy leaves as much as
|
||||||
possible. For instance, it is better to be able to have ``~/doc/`` as a
|
possible. For instance, it is better to be able to have ``~/doc/`` as a
|
||||||
read-only hierarchy and ``~/tmp/`` as a read-write hierarchy, compared to
|
read-only hierarchy and ``~/tmp/`` as a read-write hierarchy, compared to
|
||||||
``~/`` as a read-only hierarchy and ``~/tmp/`` as a read-write hierarchy.
|
``~/`` as a read-only hierarchy and ``~/tmp/`` as a read-write hierarchy.
|
||||||
Following this good practice leads to self-sufficient hierarchies that do not
|
Following this good practice leads to self-sufficient hierarchies that do not
|
||||||
depend on their location (i.e. parent directories). This is particularly
|
depend on their location (i.e. parent directories). This is particularly
|
||||||
relevant when we want to allow linking or renaming. Indeed, having consistent
|
relevant when we want to allow linking or renaming. Indeed, having consistent
|
||||||
access rights per directory enables to change the location of such directory
|
access rights per directory enables changing the location of such directories
|
||||||
without relying on the destination directory access rights (except those that
|
without relying on the destination directory access rights (except those that
|
||||||
are required for this operation, see ``LANDLOCK_ACCESS_FS_REFER``
|
are required for this operation, see ``LANDLOCK_ACCESS_FS_REFER``
|
||||||
documentation).
|
documentation).
|
||||||
|
|
||||||
Having self-sufficient hierarchies also helps to tighten the required access
|
Having self-sufficient hierarchies also helps to tighten the required access
|
||||||
rights to the minimal set of data. This also helps avoid sinkhole directories,
|
rights to the minimal set of data. This also helps avoid sinkhole directories,
|
||||||
i.e. directories where data can be linked to but not linked from. However,
|
i.e. directories where data can be linked to but not linked from. However,
|
||||||
this depends on data organization, which might not be controlled by developers.
|
this depends on data organization, which might not be controlled by developers.
|
||||||
In this case, granting read-write access to ``~/tmp/``, instead of write-only
|
In this case, granting read-write access to ``~/tmp/``, instead of write-only
|
||||||
access, would potentially allow to move ``~/tmp/`` to a non-readable directory
|
access, would potentially allow moving ``~/tmp/`` to a non-readable directory
|
||||||
and still keep the ability to list the content of ``~/tmp/``.
|
and still keep the ability to list the content of ``~/tmp/``.
|
||||||
|
|
||||||
Layers of file path access rights
|
Layers of file path access rights
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Each time a thread enforces a ruleset on itself, it updates its Landlock domain
|
Each time a thread enforces a ruleset on itself, it updates its Landlock domain
|
||||||
with a new layer of policy. Indeed, this complementary policy is stacked with
|
with a new layer of policy. This complementary policy is stacked with any
|
||||||
the potentially other rulesets already restricting this thread. A sandboxed
|
other rulesets potentially already restricting this thread. A sandboxed thread
|
||||||
thread can then safely add more constraints to itself with a new enforced
|
can then safely add more constraints to itself with a new enforced ruleset.
|
||||||
ruleset.
|
|
||||||
|
|
||||||
One policy layer grants access to a file path if at least one of its rules
|
One policy layer grants access to a file path if at least one of its rules
|
||||||
encountered on the path grants the access. A sandboxed thread can only access
|
encountered on the path grants the access. A sandboxed thread can only access
|
||||||
@ -265,7 +264,7 @@ etc.).
|
|||||||
Bind mounts and OverlayFS
|
Bind mounts and OverlayFS
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
Landlock enables to restrict access to file hierarchies, which means that these
|
Landlock enables restricting access to file hierarchies, which means that these
|
||||||
access rights can be propagated with bind mounts (cf.
|
access rights can be propagated with bind mounts (cf.
|
||||||
Documentation/filesystems/sharedsubtree.rst) but not with
|
Documentation/filesystems/sharedsubtree.rst) but not with
|
||||||
Documentation/filesystems/overlayfs.rst.
|
Documentation/filesystems/overlayfs.rst.
|
||||||
@ -278,21 +277,21 @@ access to multiple file hierarchies at the same time, whether these hierarchies
|
|||||||
are the result of bind mounts or not.
|
are the result of bind mounts or not.
|
||||||
|
|
||||||
An OverlayFS mount point consists of upper and lower layers. These layers are
|
An OverlayFS mount point consists of upper and lower layers. These layers are
|
||||||
combined in a merge directory, result of the mount point. This merge hierarchy
|
combined in a merge directory, and that merged directory becomes available at
|
||||||
may include files from the upper and lower layers, but modifications performed
|
the mount point. This merge hierarchy may include files from the upper and
|
||||||
on the merge hierarchy only reflects on the upper layer. From a Landlock
|
lower layers, but modifications performed on the merge hierarchy only reflect
|
||||||
policy point of view, each OverlayFS layers and merge hierarchies are
|
on the upper layer. From a Landlock policy point of view, all OverlayFS layers
|
||||||
standalone and contains their own set of files and directories, which is
|
and merge hierarchies are standalone and each contains their own set of files
|
||||||
different from bind mounts. A policy restricting an OverlayFS layer will not
|
and directories, which is different from bind mounts. A policy restricting an
|
||||||
restrict the resulted merged hierarchy, and vice versa. Landlock users should
|
OverlayFS layer will not restrict the resulted merged hierarchy, and vice versa.
|
||||||
then only think about file hierarchies they want to allow access to, regardless
|
Landlock users should then only think about file hierarchies they want to allow
|
||||||
of the underlying filesystem.
|
access to, regardless of the underlying filesystem.
|
||||||
|
|
||||||
Inheritance
|
Inheritance
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
Every new thread resulting from a :manpage:`clone(2)` inherits Landlock domain
|
Every new thread resulting from a :manpage:`clone(2)` inherits Landlock domain
|
||||||
restrictions from its parent. This is similar to the seccomp inheritance (cf.
|
restrictions from its parent. This is similar to seccomp inheritance (cf.
|
||||||
Documentation/userspace-api/seccomp_filter.rst) or any other LSM dealing with
|
Documentation/userspace-api/seccomp_filter.rst) or any other LSM dealing with
|
||||||
task's :manpage:`credentials(7)`. For instance, one process's thread may apply
|
task's :manpage:`credentials(7)`. For instance, one process's thread may apply
|
||||||
Landlock rules to itself, but they will not be automatically applied to other
|
Landlock rules to itself, but they will not be automatically applied to other
|
||||||
@ -311,8 +310,8 @@ Ptrace restrictions
|
|||||||
A sandboxed process has less privileges than a non-sandboxed process and must
|
A sandboxed process has less privileges than a non-sandboxed process and must
|
||||||
then be subject to additional restrictions when manipulating another process.
|
then be subject to additional restrictions when manipulating another process.
|
||||||
To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target
|
To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target
|
||||||
process, a sandboxed process should have a subset of the target process rules,
|
process, a sandboxed process should have a superset of the target process's
|
||||||
which means the tracee must be in a sub-domain of the tracer.
|
access rights, which means the tracee must be in a sub-domain of the tracer.
|
||||||
|
|
||||||
IPC scoping
|
IPC scoping
|
||||||
-----------
|
-----------
|
||||||
@ -322,7 +321,7 @@ interactions between sandboxes. Each Landlock domain can be explicitly scoped
|
|||||||
for a set of actions by specifying it on a ruleset. For example, if a
|
for a set of actions by specifying it on a ruleset. For example, if a
|
||||||
sandboxed process should not be able to :manpage:`connect(2)` to a
|
sandboxed process should not be able to :manpage:`connect(2)` to a
|
||||||
non-sandboxed process through abstract :manpage:`unix(7)` sockets, we can
|
non-sandboxed process through abstract :manpage:`unix(7)` sockets, we can
|
||||||
specify such restriction with ``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET``.
|
specify such a restriction with ``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET``.
|
||||||
Moreover, if a sandboxed process should not be able to send a signal to a
|
Moreover, if a sandboxed process should not be able to send a signal to a
|
||||||
non-sandboxed process, we can specify this restriction with
|
non-sandboxed process, we can specify this restriction with
|
||||||
``LANDLOCK_SCOPE_SIGNAL``.
|
``LANDLOCK_SCOPE_SIGNAL``.
|
||||||
@ -394,7 +393,7 @@ Backward and forward compatibility
|
|||||||
Landlock is designed to be compatible with past and future versions of the
|
Landlock is designed to be compatible with past and future versions of the
|
||||||
kernel. This is achieved thanks to the system call attributes and the
|
kernel. This is achieved thanks to the system call attributes and the
|
||||||
associated bitflags, particularly the ruleset's ``handled_access_fs``. Making
|
associated bitflags, particularly the ruleset's ``handled_access_fs``. Making
|
||||||
handled access right explicit enables the kernel and user space to have a clear
|
handled access rights explicit enables the kernel and user space to have a clear
|
||||||
contract with each other. This is required to make sure sandboxing will not
|
contract with each other. This is required to make sure sandboxing will not
|
||||||
get stricter with a system update, which could break applications.
|
get stricter with a system update, which could break applications.
|
||||||
|
|
||||||
@ -563,33 +562,34 @@ always allowed when using a kernel that only supports the first or second ABI.
|
|||||||
Starting with the Landlock ABI version 3, it is now possible to securely control
|
Starting with the Landlock ABI version 3, it is now possible to securely control
|
||||||
truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.
|
truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.
|
||||||
|
|
||||||
Network support (ABI < 4)
|
TCP bind and connect (ABI < 4)
|
||||||
-------------------------
|
------------------------------
|
||||||
|
|
||||||
Starting with the Landlock ABI version 4, it is now possible to restrict TCP
|
Starting with the Landlock ABI version 4, it is now possible to restrict TCP
|
||||||
bind and connect actions to only a set of allowed ports thanks to the new
|
bind and connect actions to only a set of allowed ports thanks to the new
|
||||||
``LANDLOCK_ACCESS_NET_BIND_TCP`` and ``LANDLOCK_ACCESS_NET_CONNECT_TCP``
|
``LANDLOCK_ACCESS_NET_BIND_TCP`` and ``LANDLOCK_ACCESS_NET_CONNECT_TCP``
|
||||||
access rights.
|
access rights.
|
||||||
|
|
||||||
IOCTL (ABI < 5)
|
Device IOCTL (ABI < 5)
|
||||||
---------------
|
----------------------
|
||||||
|
|
||||||
IOCTL operations could not be denied before the fifth Landlock ABI, so
|
IOCTL operations could not be denied before the fifth Landlock ABI, so
|
||||||
:manpage:`ioctl(2)` is always allowed when using a kernel that only supports an
|
:manpage:`ioctl(2)` is always allowed when using a kernel that only supports an
|
||||||
earlier ABI.
|
earlier ABI.
|
||||||
|
|
||||||
Starting with the Landlock ABI version 5, it is possible to restrict the use of
|
Starting with the Landlock ABI version 5, it is possible to restrict the use of
|
||||||
:manpage:`ioctl(2)` using the new ``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right.
|
:manpage:`ioctl(2)` on character and block devices using the new
|
||||||
|
``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right.
|
||||||
|
|
||||||
Abstract UNIX socket scoping (ABI < 6)
|
Abstract UNIX socket (ABI < 6)
|
||||||
--------------------------------------
|
------------------------------
|
||||||
|
|
||||||
Starting with the Landlock ABI version 6, it is possible to restrict
|
Starting with the Landlock ABI version 6, it is possible to restrict
|
||||||
connections to an abstract :manpage:`unix(7)` socket by setting
|
connections to an abstract :manpage:`unix(7)` socket by setting
|
||||||
``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` to the ``scoped`` ruleset attribute.
|
``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` to the ``scoped`` ruleset attribute.
|
||||||
|
|
||||||
Signal scoping (ABI < 6)
|
Signal (ABI < 6)
|
||||||
------------------------
|
----------------
|
||||||
|
|
||||||
Starting with the Landlock ABI version 6, it is possible to restrict
|
Starting with the Landlock ABI version 6, it is possible to restrict
|
||||||
:manpage:`signal(7)` sending by setting ``LANDLOCK_SCOPE_SIGNAL`` to the
|
:manpage:`signal(7)` sending by setting ``LANDLOCK_SCOPE_SIGNAL`` to the
|
||||||
@ -605,9 +605,9 @@ Build time configuration
|
|||||||
|
|
||||||
Landlock was first introduced in Linux 5.13 but it must be configured at build
|
Landlock was first introduced in Linux 5.13 but it must be configured at build
|
||||||
time with ``CONFIG_SECURITY_LANDLOCK=y``. Landlock must also be enabled at boot
|
time with ``CONFIG_SECURITY_LANDLOCK=y``. Landlock must also be enabled at boot
|
||||||
time as the other security modules. The list of security modules enabled by
|
time like other security modules. The list of security modules enabled by
|
||||||
default is set with ``CONFIG_LSM``. The kernel configuration should then
|
default is set with ``CONFIG_LSM``. The kernel configuration should then
|
||||||
contains ``CONFIG_LSM=landlock,[...]`` with ``[...]`` as the list of other
|
contain ``CONFIG_LSM=landlock,[...]`` with ``[...]`` as the list of other
|
||||||
potentially useful security modules for the running system (see the
|
potentially useful security modules for the running system (see the
|
||||||
``CONFIG_LSM`` help).
|
``CONFIG_LSM`` help).
|
||||||
|
|
||||||
@ -669,7 +669,7 @@ Questions and answers
|
|||||||
What about user space sandbox managers?
|
What about user space sandbox managers?
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
Using user space process to enforce restrictions on kernel resources can lead
|
Using user space processes to enforce restrictions on kernel resources can lead
|
||||||
to race conditions or inconsistent evaluations (i.e. `Incorrect mirroring of
|
to race conditions or inconsistent evaluations (i.e. `Incorrect mirroring of
|
||||||
the OS code and state
|
the OS code and state
|
||||||
<https://www.ndss-symposium.org/ndss2003/traps-and-pitfalls-practical-problems-system-call-interposition-based-security-tools/>`_).
|
<https://www.ndss-symposium.org/ndss2003/traps-and-pitfalls-practical-problems-system-call-interposition-based-security-tools/>`_).
|
||||||
|
@ -60,6 +60,25 @@ static inline int landlock_restrict_self(const int ruleset_fd,
|
|||||||
#define ENV_SCOPED_NAME "LL_SCOPED"
|
#define ENV_SCOPED_NAME "LL_SCOPED"
|
||||||
#define ENV_DELIMITER ":"
|
#define ENV_DELIMITER ":"
|
||||||
|
|
||||||
|
static int str2num(const char *numstr, __u64 *num_dst)
|
||||||
|
{
|
||||||
|
char *endptr = NULL;
|
||||||
|
int err = 0;
|
||||||
|
__u64 num;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
num = strtoull(numstr, &endptr, 10);
|
||||||
|
if (errno != 0)
|
||||||
|
err = errno;
|
||||||
|
/* Was the string empty, or not entirely parsed successfully? */
|
||||||
|
else if ((*numstr == '\0') || (*endptr != '\0'))
|
||||||
|
err = EINVAL;
|
||||||
|
else
|
||||||
|
*num_dst = num;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
static int parse_path(char *env_path, const char ***const path_list)
|
static int parse_path(char *env_path, const char ***const path_list)
|
||||||
{
|
{
|
||||||
int i, num_paths = 0;
|
int i, num_paths = 0;
|
||||||
@ -160,7 +179,6 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
|
|||||||
char *env_port_name, *env_port_name_next, *strport;
|
char *env_port_name, *env_port_name_next, *strport;
|
||||||
struct landlock_net_port_attr net_port = {
|
struct landlock_net_port_attr net_port = {
|
||||||
.allowed_access = allowed_access,
|
.allowed_access = allowed_access,
|
||||||
.port = 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
env_port_name = getenv(env_var);
|
env_port_name = getenv(env_var);
|
||||||
@ -171,7 +189,17 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
|
|||||||
|
|
||||||
env_port_name_next = env_port_name;
|
env_port_name_next = env_port_name;
|
||||||
while ((strport = strsep(&env_port_name_next, ENV_DELIMITER))) {
|
while ((strport = strsep(&env_port_name_next, ENV_DELIMITER))) {
|
||||||
net_port.port = atoi(strport);
|
__u64 port;
|
||||||
|
|
||||||
|
if (strcmp(strport, "") == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (str2num(strport, &port)) {
|
||||||
|
fprintf(stderr, "Failed to parse port at \"%s\"\n",
|
||||||
|
strport);
|
||||||
|
goto out_free_name;
|
||||||
|
}
|
||||||
|
net_port.port = port;
|
||||||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||||
&net_port, 0)) {
|
&net_port, 0)) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@ -262,6 +290,44 @@ static bool check_ruleset_scope(const char *const env_var,
|
|||||||
|
|
||||||
#define LANDLOCK_ABI_LAST 6
|
#define LANDLOCK_ABI_LAST 6
|
||||||
|
|
||||||
|
#define XSTR(s) #s
|
||||||
|
#define STR(s) XSTR(s)
|
||||||
|
|
||||||
|
/* clang-format off */
|
||||||
|
|
||||||
|
static const char help[] =
|
||||||
|
"usage: " ENV_FS_RO_NAME "=\"...\" " ENV_FS_RW_NAME "=\"...\" "
|
||||||
|
"[other environment variables] %1$s <cmd> [args]...\n"
|
||||||
|
"\n"
|
||||||
|
"Execute the given command in a restricted environment.\n"
|
||||||
|
"Multi-valued settings (lists of ports, paths, scopes) are colon-delimited.\n"
|
||||||
|
"\n"
|
||||||
|
"Mandatory settings:\n"
|
||||||
|
"* " ENV_FS_RO_NAME ": paths allowed to be used in a read-only way\n"
|
||||||
|
"* " ENV_FS_RW_NAME ": paths allowed to be used in a read-write way\n"
|
||||||
|
"\n"
|
||||||
|
"Optional settings (when not set, their associated access check "
|
||||||
|
"is always allowed, which is different from an empty string which "
|
||||||
|
"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_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"
|
||||||
|
"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_SCOPED_NAME "=\"a:s\" "
|
||||||
|
"%1$s bash -i\n"
|
||||||
|
"\n"
|
||||||
|
"This sandboxer can use Landlock features up to ABI version "
|
||||||
|
STR(LANDLOCK_ABI_LAST) ".\n";
|
||||||
|
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
int main(const int argc, char *const argv[], char *const *const envp)
|
int main(const int argc, char *const argv[], char *const *const envp)
|
||||||
{
|
{
|
||||||
const char *cmd_path;
|
const char *cmd_path;
|
||||||
@ -280,47 +346,7 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
fprintf(stderr,
|
fprintf(stderr, help, argv[0]);
|
||||||
"usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s "
|
|
||||||
"<cmd> [args]...\n\n",
|
|
||||||
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
|
|
||||||
ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
|
|
||||||
fprintf(stderr,
|
|
||||||
"Execute a command in a restricted environment.\n\n");
|
|
||||||
fprintf(stderr,
|
|
||||||
"Environment variables containing paths and ports "
|
|
||||||
"each separated by a colon:\n");
|
|
||||||
fprintf(stderr,
|
|
||||||
"* %s: list of paths allowed to be used in a read-only way.\n",
|
|
||||||
ENV_FS_RO_NAME);
|
|
||||||
fprintf(stderr,
|
|
||||||
"* %s: list of paths allowed to be used in a read-write way.\n\n",
|
|
||||||
ENV_FS_RW_NAME);
|
|
||||||
fprintf(stderr,
|
|
||||||
"Environment variables containing ports are optional "
|
|
||||||
"and could be skipped.\n");
|
|
||||||
fprintf(stderr,
|
|
||||||
"* %s: list of ports allowed to bind (server).\n",
|
|
||||||
ENV_TCP_BIND_NAME);
|
|
||||||
fprintf(stderr,
|
|
||||||
"* %s: list of ports allowed to connect (client).\n",
|
|
||||||
ENV_TCP_CONNECT_NAME);
|
|
||||||
fprintf(stderr, "* %s: list of scoped IPCs.\n",
|
|
||||||
ENV_SCOPED_NAME);
|
|
||||||
fprintf(stderr,
|
|
||||||
"\nexample:\n"
|
|
||||||
"%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
|
|
||||||
"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
|
|
||||||
"%s=\"9418\" "
|
|
||||||
"%s=\"80:443\" "
|
|
||||||
"%s=\"a:s\" "
|
|
||||||
"%s bash -i\n\n",
|
|
||||||
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
|
|
||||||
ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
|
|
||||||
fprintf(stderr,
|
|
||||||
"This sandboxer can use Landlock features "
|
|
||||||
"up to ABI version %d.\n",
|
|
||||||
LANDLOCK_ABI_LAST);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,38 +388,22 @@ static bool is_nouser_or_private(const struct dentry *dentry)
|
|||||||
unlikely(IS_PRIVATE(d_backing_inode(dentry))));
|
unlikely(IS_PRIVATE(d_backing_inode(dentry))));
|
||||||
}
|
}
|
||||||
|
|
||||||
static access_mask_t
|
|
||||||
get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
|
|
||||||
{
|
|
||||||
access_mask_t access_dom = 0;
|
|
||||||
size_t layer_level;
|
|
||||||
|
|
||||||
for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
|
|
||||||
access_dom |=
|
|
||||||
landlock_get_raw_fs_access_mask(domain, layer_level);
|
|
||||||
return access_dom;
|
|
||||||
}
|
|
||||||
|
|
||||||
static access_mask_t
|
static access_mask_t
|
||||||
get_handled_fs_accesses(const struct landlock_ruleset *const domain)
|
get_handled_fs_accesses(const struct landlock_ruleset *const domain)
|
||||||
{
|
{
|
||||||
/* Handles all initially denied by default access rights. */
|
/* Handles all initially denied by default access rights. */
|
||||||
return get_raw_handled_fs_accesses(domain) |
|
return landlock_union_access_masks(domain).fs |
|
||||||
LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
|
LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct landlock_ruleset *
|
static const struct access_masks any_fs = {
|
||||||
get_fs_domain(const struct landlock_ruleset *const domain)
|
.fs = ~0,
|
||||||
{
|
};
|
||||||
if (!domain || !get_raw_handled_fs_accesses(domain))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct landlock_ruleset *get_current_fs_domain(void)
|
static const struct landlock_ruleset *get_current_fs_domain(void)
|
||||||
{
|
{
|
||||||
return get_fs_domain(landlock_get_current_domain());
|
return landlock_get_applicable_domain(landlock_get_current_domain(),
|
||||||
|
any_fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1517,7 +1501,8 @@ static int hook_file_open(struct file *const file)
|
|||||||
access_mask_t open_access_request, full_access_request, allowed_access,
|
access_mask_t open_access_request, full_access_request, allowed_access,
|
||||||
optional_access;
|
optional_access;
|
||||||
const struct landlock_ruleset *const dom =
|
const struct landlock_ruleset *const dom =
|
||||||
get_fs_domain(landlock_cred(file->f_cred)->domain);
|
landlock_get_applicable_domain(
|
||||||
|
landlock_cred(file->f_cred)->domain, any_fs);
|
||||||
|
|
||||||
if (!dom)
|
if (!dom)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -39,27 +39,9 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static access_mask_t
|
static const struct access_masks any_net = {
|
||||||
get_raw_handled_net_accesses(const struct landlock_ruleset *const domain)
|
.net = ~0,
|
||||||
{
|
};
|
||||||
access_mask_t access_dom = 0;
|
|
||||||
size_t layer_level;
|
|
||||||
|
|
||||||
for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
|
|
||||||
access_dom |= landlock_get_net_access_mask(domain, layer_level);
|
|
||||||
return access_dom;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct landlock_ruleset *get_current_net_domain(void)
|
|
||||||
{
|
|
||||||
const struct landlock_ruleset *const dom =
|
|
||||||
landlock_get_current_domain();
|
|
||||||
|
|
||||||
if (!dom || !get_raw_handled_net_accesses(dom))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return dom;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int current_check_access_socket(struct socket *const sock,
|
static int current_check_access_socket(struct socket *const sock,
|
||||||
struct sockaddr *const address,
|
struct sockaddr *const address,
|
||||||
@ -72,7 +54,9 @@ static int current_check_access_socket(struct socket *const sock,
|
|||||||
struct landlock_id id = {
|
struct landlock_id id = {
|
||||||
.type = LANDLOCK_KEY_NET_PORT,
|
.type = LANDLOCK_KEY_NET_PORT,
|
||||||
};
|
};
|
||||||
const struct landlock_ruleset *const dom = get_current_net_domain();
|
const struct landlock_ruleset *const dom =
|
||||||
|
landlock_get_applicable_domain(landlock_get_current_domain(),
|
||||||
|
any_net);
|
||||||
|
|
||||||
if (!dom)
|
if (!dom)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/build_bug.h>
|
#include <linux/build_bug.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/rbtree.h>
|
#include <linux/rbtree.h>
|
||||||
#include <linux/refcount.h>
|
#include <linux/refcount.h>
|
||||||
@ -47,6 +48,15 @@ struct access_masks {
|
|||||||
access_mask_t scope : LANDLOCK_NUM_SCOPE;
|
access_mask_t scope : LANDLOCK_NUM_SCOPE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
union access_masks_all {
|
||||||
|
struct access_masks masks;
|
||||||
|
u32 all;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Makes sure all fields are covered. */
|
||||||
|
static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
|
||||||
|
sizeof(typeof_member(union access_masks_all, all)));
|
||||||
|
|
||||||
typedef u16 layer_mask_t;
|
typedef u16 layer_mask_t;
|
||||||
/* Makes sure all layers can be checked. */
|
/* Makes sure all layers can be checked. */
|
||||||
static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
|
static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
|
||||||
@ -260,6 +270,61 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
|
|||||||
refcount_inc(&ruleset->usage);
|
refcount_inc(&ruleset->usage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* landlock_union_access_masks - Return all access rights handled in the
|
||||||
|
* domain
|
||||||
|
*
|
||||||
|
* @domain: Landlock ruleset (used as a domain)
|
||||||
|
*
|
||||||
|
* Returns: an access_masks result of the OR of all the domain's access masks.
|
||||||
|
*/
|
||||||
|
static inline struct access_masks
|
||||||
|
landlock_union_access_masks(const struct landlock_ruleset *const domain)
|
||||||
|
{
|
||||||
|
union access_masks_all matches = {};
|
||||||
|
size_t layer_level;
|
||||||
|
|
||||||
|
for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
|
||||||
|
union access_masks_all layer = {
|
||||||
|
.masks = domain->access_masks[layer_level],
|
||||||
|
};
|
||||||
|
|
||||||
|
matches.all |= layer.all;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches.masks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* landlock_get_applicable_domain - Return @domain if it applies to (handles)
|
||||||
|
* at least one of the access rights specified
|
||||||
|
* in @masks
|
||||||
|
*
|
||||||
|
* @domain: Landlock ruleset (used as a domain)
|
||||||
|
* @masks: access masks
|
||||||
|
*
|
||||||
|
* Returns: @domain if any access rights specified in @masks is handled, or
|
||||||
|
* NULL otherwise.
|
||||||
|
*/
|
||||||
|
static inline const struct landlock_ruleset *
|
||||||
|
landlock_get_applicable_domain(const struct landlock_ruleset *const domain,
|
||||||
|
const struct access_masks masks)
|
||||||
|
{
|
||||||
|
const union access_masks_all masks_all = {
|
||||||
|
.masks = masks,
|
||||||
|
};
|
||||||
|
union access_masks_all merge = {};
|
||||||
|
|
||||||
|
if (!domain)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
merge.masks = landlock_union_access_masks(domain);
|
||||||
|
if (merge.all & masks_all.all)
|
||||||
|
return domain;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
|
landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
|
||||||
const access_mask_t fs_access_mask,
|
const access_mask_t fs_access_mask,
|
||||||
@ -295,19 +360,12 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
|
|||||||
ruleset->access_masks[layer_level].scope |= mask;
|
ruleset->access_masks[layer_level].scope |= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline access_mask_t
|
|
||||||
landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
|
|
||||||
const u16 layer_level)
|
|
||||||
{
|
|
||||||
return ruleset->access_masks[layer_level].fs;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline access_mask_t
|
static inline access_mask_t
|
||||||
landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
|
landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
|
||||||
const u16 layer_level)
|
const u16 layer_level)
|
||||||
{
|
{
|
||||||
/* Handles all initially denied by default access rights. */
|
/* Handles all initially denied by default access rights. */
|
||||||
return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
|
return ruleset->access_masks[layer_level].fs |
|
||||||
LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
|
LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +329,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
|
|||||||
return -ENOMSG;
|
return -ENOMSG;
|
||||||
|
|
||||||
/* Checks that allowed_access matches the @ruleset constraints. */
|
/* Checks that allowed_access matches the @ruleset constraints. */
|
||||||
mask = landlock_get_raw_fs_access_mask(ruleset, 0);
|
mask = ruleset->access_masks[0].fs;
|
||||||
if ((path_beneath_attr.allowed_access | mask) != mask)
|
if ((path_beneath_attr.allowed_access | mask) != mask)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
@ -204,12 +204,17 @@ static bool is_abstract_socket(struct sock *const sock)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct access_masks unix_scope = {
|
||||||
|
.scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||||
|
};
|
||||||
|
|
||||||
static int hook_unix_stream_connect(struct sock *const sock,
|
static int hook_unix_stream_connect(struct sock *const sock,
|
||||||
struct sock *const other,
|
struct sock *const other,
|
||||||
struct sock *const newsk)
|
struct sock *const newsk)
|
||||||
{
|
{
|
||||||
const struct landlock_ruleset *const dom =
|
const struct landlock_ruleset *const dom =
|
||||||
landlock_get_current_domain();
|
landlock_get_applicable_domain(landlock_get_current_domain(),
|
||||||
|
unix_scope);
|
||||||
|
|
||||||
/* Quick return for non-landlocked tasks. */
|
/* Quick return for non-landlocked tasks. */
|
||||||
if (!dom)
|
if (!dom)
|
||||||
@ -225,7 +230,8 @@ static int hook_unix_may_send(struct socket *const sock,
|
|||||||
struct socket *const other)
|
struct socket *const other)
|
||||||
{
|
{
|
||||||
const struct landlock_ruleset *const dom =
|
const struct landlock_ruleset *const dom =
|
||||||
landlock_get_current_domain();
|
landlock_get_applicable_domain(landlock_get_current_domain(),
|
||||||
|
unix_scope);
|
||||||
|
|
||||||
if (!dom)
|
if (!dom)
|
||||||
return 0;
|
return 0;
|
||||||
@ -243,6 +249,10 @@ static int hook_unix_may_send(struct socket *const sock,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct access_masks signal_scope = {
|
||||||
|
.scope = LANDLOCK_SCOPE_SIGNAL,
|
||||||
|
};
|
||||||
|
|
||||||
static int hook_task_kill(struct task_struct *const p,
|
static int hook_task_kill(struct task_struct *const p,
|
||||||
struct kernel_siginfo *const info, const int sig,
|
struct kernel_siginfo *const info, const int sig,
|
||||||
const struct cred *const cred)
|
const struct cred *const cred)
|
||||||
@ -256,6 +266,7 @@ static int hook_task_kill(struct task_struct *const p,
|
|||||||
} else {
|
} else {
|
||||||
dom = landlock_get_current_domain();
|
dom = landlock_get_current_domain();
|
||||||
}
|
}
|
||||||
|
dom = landlock_get_applicable_domain(dom, signal_scope);
|
||||||
|
|
||||||
/* Quick return for non-landlocked tasks. */
|
/* Quick return for non-landlocked tasks. */
|
||||||
if (!dom)
|
if (!dom)
|
||||||
@ -279,7 +290,8 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
|
|||||||
|
|
||||||
/* Lock already held by send_sigio() and send_sigurg(). */
|
/* Lock already held by send_sigio() and send_sigurg(). */
|
||||||
lockdep_assert_held(&fown->lock);
|
lockdep_assert_held(&fown->lock);
|
||||||
dom = landlock_file(fown->file)->fown_domain;
|
dom = landlock_get_applicable_domain(
|
||||||
|
landlock_file(fown->file)->fown_domain, signal_scope);
|
||||||
|
|
||||||
/* Quick return for unowned socket. */
|
/* Quick return for unowned socket. */
|
||||||
if (!dom)
|
if (!dom)
|
||||||
|
Loading…
Reference in New Issue
Block a user