mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 21:21:41 +00:00
Landlock updates for v6.10-rc1
-----BEGIN PGP SIGNATURE----- iIYEABYKAC4WIQSVyBthFV4iTW/VU1/l49DojIL20gUCZkYGahAcbWljQGRpZ2lr b2QubmV0AAoJEOXj0OiMgvbSaxUBAL1USJI2DUo+hJTDHqXdVZGEE6VfYnmEhIur NZfsti4UAQCabAej7+NHzh1jAnnKMJFbcyl4LRy99anJGlEWq9ExDA== =fViz -----END PGP SIGNATURE----- Merge tag 'landlock-6.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux Pull landlock updates from Mickaël Salaün: "This brings ioctl control to Landlock, contributed by Günther Noack. This also adds him as a Landlock reviewer, and fixes an issue in the sample" * tag 'landlock-6.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux: MAINTAINERS: Add Günther Noack as Landlock reviewer fs/ioctl: Add a comment to keep the logic in sync with LSM policies MAINTAINERS: Notify Landlock maintainers about changes to fs/ioctl.c landlock: Document IOCTL support samples/landlock: Add support for LANDLOCK_ACCESS_FS_IOCTL_DEV selftests/landlock: Exhaustive test for the IOCTL allow-list selftests/landlock: Check IOCTL restrictions for named UNIX domain sockets selftests/landlock: Test IOCTLs on named pipes selftests/landlock: Test ioctl(2) and ftruncate(2) with open(O_PATH) selftests/landlock: Test IOCTL with memfds selftests/landlock: Test IOCTL support landlock: Add IOCTL access right for character and block devices samples/landlock: Fix incorrect free in populate_ruleset_net
This commit is contained in:
commit
2fc0e7892c
@ -8,7 +8,7 @@ Landlock: unprivileged access control
|
||||
=====================================
|
||||
|
||||
:Author: Mickaël Salaün
|
||||
:Date: October 2023
|
||||
:Date: April 2024
|
||||
|
||||
The goal of Landlock is to enable to restrict ambient rights (e.g. global
|
||||
filesystem or network access) for a set of processes. Because Landlock
|
||||
@ -76,7 +76,8 @@ to be explicit about the denied-by-default access rights.
|
||||
LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
||||
LANDLOCK_ACCESS_FS_MAKE_SYM |
|
||||
LANDLOCK_ACCESS_FS_REFER |
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE,
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.handled_access_net =
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
@ -85,10 +86,10 @@ to be explicit about the denied-by-default access rights.
|
||||
Because we may not know on which kernel version an application will be
|
||||
executed, 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
|
||||
using. To avoid binary enforcement (i.e. either all security features or
|
||||
none), we can leverage a dedicated Landlock command to get the current version
|
||||
of the Landlock ABI and adapt the handled accesses. Let's check if we should
|
||||
remove access rights which are only supported in higher versions of the ABI.
|
||||
using.
|
||||
|
||||
To be compatible with older Linux versions, we detect the available Landlock ABI
|
||||
version, and only use the available subset of access rights:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
@ -114,6 +115,10 @@ remove access rights which are only supported in higher versions of the ABI.
|
||||
ruleset_attr.handled_access_net &=
|
||||
~(LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP);
|
||||
__attribute__((fallthrough));
|
||||
case 4:
|
||||
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
|
||||
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
}
|
||||
|
||||
This enables to create an inclusive ruleset that will contain our rules.
|
||||
@ -225,6 +230,7 @@ access rights per directory enables to change the location of such directory
|
||||
without relying on the destination directory access rights (except those that
|
||||
are required for this operation, see ``LANDLOCK_ACCESS_FS_REFER``
|
||||
documentation).
|
||||
|
||||
Having self-sufficient hierarchies also helps to tighten the required access
|
||||
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,
|
||||
@ -318,18 +324,26 @@ It should also be noted that truncating files does not require the
|
||||
system call, this can also be done through :manpage:`open(2)` with the flags
|
||||
``O_RDONLY | O_TRUNC``.
|
||||
|
||||
When opening a file, the availability of the ``LANDLOCK_ACCESS_FS_TRUNCATE``
|
||||
right is associated with the newly created file descriptor and will be used for
|
||||
subsequent truncation attempts using :manpage:`ftruncate(2)`. The behavior is
|
||||
similar to opening a file for reading or writing, where permissions are checked
|
||||
during :manpage:`open(2)`, but not during the subsequent :manpage:`read(2)` and
|
||||
The truncate right is associated with the opened file (see below).
|
||||
|
||||
Rights associated with file descriptors
|
||||
---------------------------------------
|
||||
|
||||
When opening a file, the availability of the ``LANDLOCK_ACCESS_FS_TRUNCATE`` and
|
||||
``LANDLOCK_ACCESS_FS_IOCTL_DEV`` rights is associated with the newly created
|
||||
file descriptor and will be used for subsequent truncation and ioctl attempts
|
||||
using :manpage:`ftruncate(2)` and :manpage:`ioctl(2)`. The behavior is similar
|
||||
to opening a file for reading or writing, where permissions are checked during
|
||||
:manpage:`open(2)`, but not during the subsequent :manpage:`read(2)` and
|
||||
:manpage:`write(2)` calls.
|
||||
|
||||
As a consequence, it is possible to have multiple open file descriptors for the
|
||||
same file, where one grants the right to truncate the file and the other does
|
||||
not. It is also possible to pass such file descriptors between processes,
|
||||
keeping their Landlock properties, even when these processes do not have an
|
||||
enforced Landlock ruleset.
|
||||
As a consequence, it is possible that a process has multiple open file
|
||||
descriptors referring to the same file, but Landlock enforces different things
|
||||
when operating with these file descriptors. This can happen when a Landlock
|
||||
ruleset gets enforced and the process keeps file descriptors which were opened
|
||||
both before and after the enforcement. It is also possible to pass such file
|
||||
descriptors between processes, keeping their Landlock properties, even when some
|
||||
of the involved processes do not have an enforced Landlock ruleset.
|
||||
|
||||
Compatibility
|
||||
=============
|
||||
@ -458,6 +472,28 @@ Memory usage
|
||||
Kernel memory allocated to create rulesets is accounted and can be restricted
|
||||
by the Documentation/admin-guide/cgroup-v1/memory.rst.
|
||||
|
||||
IOCTL support
|
||||
-------------
|
||||
|
||||
The ``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right restricts the use of
|
||||
:manpage:`ioctl(2)`, but it only applies to *newly opened* device files. This
|
||||
means specifically that pre-existing file descriptors like stdin, stdout and
|
||||
stderr are unaffected.
|
||||
|
||||
Users should be aware that TTY devices have traditionally permitted to control
|
||||
other processes on the same TTY through the ``TIOCSTI`` and ``TIOCLINUX`` IOCTL
|
||||
commands. Both of these require ``CAP_SYS_ADMIN`` on modern Linux systems, but
|
||||
the behavior is configurable for ``TIOCSTI``.
|
||||
|
||||
On older systems, it is therefore recommended to close inherited TTY file
|
||||
descriptors, or to reopen them from ``/proc/self/fd/*`` without the
|
||||
``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right, if possible.
|
||||
|
||||
Landlock's IOCTL support is coarse-grained at the moment, but may become more
|
||||
fine-grained in the future. Until then, users are advised to establish the
|
||||
guarantees that they need through the file hierarchy, by only allowing the
|
||||
``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right on files where it is really required.
|
||||
|
||||
Previous limitations
|
||||
====================
|
||||
|
||||
@ -495,6 +531,16 @@ 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``
|
||||
access rights.
|
||||
|
||||
IOCTL (ABI < 5)
|
||||
---------------
|
||||
|
||||
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
|
||||
earlier ABI.
|
||||
|
||||
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.
|
||||
|
||||
.. _kernel_support:
|
||||
|
||||
Kernel support
|
||||
|
@ -12374,12 +12374,14 @@ F: net/l3mdev
|
||||
|
||||
LANDLOCK SECURITY MODULE
|
||||
M: Mickaël Salaün <mic@digikod.net>
|
||||
R: Günther Noack <gnoack@google.com>
|
||||
L: linux-security-module@vger.kernel.org
|
||||
S: Supported
|
||||
W: https://landlock.io
|
||||
T: git https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git
|
||||
F: Documentation/security/landlock.rst
|
||||
F: Documentation/userspace-api/landlock.rst
|
||||
F: fs/ioctl.c
|
||||
F: include/uapi/linux/landlock.h
|
||||
F: samples/landlock/
|
||||
F: security/landlock/
|
||||
|
@ -796,6 +796,9 @@ static int ioctl_get_fs_sysfs_path(struct file *file, void __user *argp)
|
||||
*
|
||||
* When you add any new common ioctls to the switches above and below,
|
||||
* please ensure they have compatible arguments in compat mode.
|
||||
*
|
||||
* The LSM mailing list should also be notified of any command additions or
|
||||
* changes, as specific LSMs may be affected.
|
||||
*/
|
||||
static int do_vfs_ioctl(struct file *filp, unsigned int fd,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
|
@ -128,7 +128,7 @@ struct landlock_net_port_attr {
|
||||
* files and directories. Files or directories opened before the sandboxing
|
||||
* are not subject to these restrictions.
|
||||
*
|
||||
* A file can only receive these access rights:
|
||||
* The following access rights apply only to files:
|
||||
*
|
||||
* - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
|
||||
* - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that
|
||||
@ -138,12 +138,13 @@ struct landlock_net_port_attr {
|
||||
* - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access.
|
||||
* - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`,
|
||||
* :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with
|
||||
* ``O_TRUNC``. Whether an opened file can be truncated with
|
||||
* :manpage:`ftruncate(2)` is determined during :manpage:`open(2)`, in the
|
||||
* same way as read and write permissions are checked during
|
||||
* :manpage:`open(2)` using %LANDLOCK_ACCESS_FS_READ_FILE and
|
||||
* %LANDLOCK_ACCESS_FS_WRITE_FILE. This access right is available since the
|
||||
* third version of the Landlock ABI.
|
||||
* ``O_TRUNC``. This access right is available since the third version of the
|
||||
* Landlock ABI.
|
||||
*
|
||||
* Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used
|
||||
* with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
|
||||
* read and write permissions are checked during :manpage:`open(2)` using
|
||||
* %LANDLOCK_ACCESS_FS_READ_FILE and %LANDLOCK_ACCESS_FS_WRITE_FILE.
|
||||
*
|
||||
* A directory can receive access rights related to files or directories. The
|
||||
* following access right is applied to the directory itself, and the
|
||||
@ -198,13 +199,33 @@ struct landlock_net_port_attr {
|
||||
* If multiple requirements are not met, the ``EACCES`` error code takes
|
||||
* precedence over ``EXDEV``.
|
||||
*
|
||||
* The following access right applies both to files and directories:
|
||||
*
|
||||
* - %LANDLOCK_ACCESS_FS_IOCTL_DEV: Invoke :manpage:`ioctl(2)` commands on an opened
|
||||
* character or block device.
|
||||
*
|
||||
* This access right applies to all `ioctl(2)` commands implemented by device
|
||||
* drivers. However, the following common IOCTL commands continue to be
|
||||
* invokable independent of the %LANDLOCK_ACCESS_FS_IOCTL_DEV right:
|
||||
*
|
||||
* * IOCTL commands targeting file descriptors (``FIOCLEX``, ``FIONCLEX``),
|
||||
* * IOCTL commands targeting file descriptions (``FIONBIO``, ``FIOASYNC``),
|
||||
* * IOCTL commands targeting file systems (``FIFREEZE``, ``FITHAW``,
|
||||
* ``FIGETBSZ``, ``FS_IOC_GETFSUUID``, ``FS_IOC_GETFSSYSFSPATH``)
|
||||
* * Some IOCTL commands which do not make sense when used with devices, but
|
||||
* whose implementations are safe and return the right error codes
|
||||
* (``FS_IOC_FIEMAP``, ``FICLONE``, ``FICLONERANGE``, ``FIDEDUPERANGE``)
|
||||
*
|
||||
* This access right is available since the fifth version of the Landlock
|
||||
* ABI.
|
||||
*
|
||||
* .. warning::
|
||||
*
|
||||
* It is currently not possible to restrict some file-related actions
|
||||
* accessible through these syscall families: :manpage:`chdir(2)`,
|
||||
* :manpage:`stat(2)`, :manpage:`flock(2)`, :manpage:`chmod(2)`,
|
||||
* :manpage:`chown(2)`, :manpage:`setxattr(2)`, :manpage:`utime(2)`,
|
||||
* :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, :manpage:`access(2)`.
|
||||
* :manpage:`fcntl(2)`, :manpage:`access(2)`.
|
||||
* Future Landlock evolutions will enable to restrict them.
|
||||
*/
|
||||
/* clang-format off */
|
||||
@ -223,6 +244,7 @@ struct landlock_net_port_attr {
|
||||
#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
|
||||
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
|
||||
#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
|
||||
#define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15)
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
|
@ -81,7 +81,8 @@ static int parse_path(char *env_path, const char ***const path_list)
|
||||
LANDLOCK_ACCESS_FS_EXECUTE | \
|
||||
LANDLOCK_ACCESS_FS_WRITE_FILE | \
|
||||
LANDLOCK_ACCESS_FS_READ_FILE | \
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE)
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE | \
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV)
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
@ -153,7 +154,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
|
||||
const __u64 allowed_access)
|
||||
{
|
||||
int ret = 1;
|
||||
char *env_port_name, *strport;
|
||||
char *env_port_name, *env_port_name_next, *strport;
|
||||
struct landlock_net_port_attr net_port = {
|
||||
.allowed_access = allowed_access,
|
||||
.port = 0,
|
||||
@ -165,7 +166,8 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
|
||||
env_port_name = strdup(env_port_name);
|
||||
unsetenv(env_var);
|
||||
|
||||
while ((strport = strsep(&env_port_name, ENV_DELIMITER))) {
|
||||
env_port_name_next = env_port_name;
|
||||
while ((strport = strsep(&env_port_name_next, ENV_DELIMITER))) {
|
||||
net_port.port = atoi(strport);
|
||||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&net_port, 0)) {
|
||||
@ -201,11 +203,12 @@ out_free_name:
|
||||
LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
|
||||
LANDLOCK_ACCESS_FS_MAKE_SYM | \
|
||||
LANDLOCK_ACCESS_FS_REFER | \
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE)
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE | \
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV)
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
#define LANDLOCK_ABI_LAST 4
|
||||
#define LANDLOCK_ABI_LAST 5
|
||||
|
||||
int main(const int argc, char *const argv[], char *const *const envp)
|
||||
{
|
||||
@ -319,6 +322,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
||||
ruleset_attr.handled_access_net &=
|
||||
~(LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP);
|
||||
__attribute__((fallthrough));
|
||||
case 4:
|
||||
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
|
||||
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
|
||||
fprintf(stderr,
|
||||
"Hint: You should update the running kernel "
|
||||
"to leverage Landlock features "
|
||||
|
@ -5,8 +5,11 @@
|
||||
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
|
||||
* Copyright © 2018-2020 ANSSI
|
||||
* Copyright © 2021-2022 Microsoft Corporation
|
||||
* Copyright © 2022 Günther Noack <gnoack3000@gmail.com>
|
||||
* Copyright © 2023-2024 Google LLC
|
||||
*/
|
||||
|
||||
#include <asm/ioctls.h>
|
||||
#include <kunit/test.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/bitops.h>
|
||||
@ -14,6 +17,7 @@
|
||||
#include <linux/compiler_types.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/falloc.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -29,6 +33,7 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/wait_bit.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <uapi/linux/fiemap.h>
|
||||
#include <uapi/linux/landlock.h>
|
||||
|
||||
#include "common.h"
|
||||
@ -84,6 +89,160 @@ static const struct landlock_object_underops landlock_fs_underops = {
|
||||
.release = release_inode
|
||||
};
|
||||
|
||||
/* IOCTL helpers */
|
||||
|
||||
/**
|
||||
* is_masked_device_ioctl - Determine whether an IOCTL command is always
|
||||
* permitted with Landlock for device files. These commands can not be
|
||||
* restricted on device files by enforcing a Landlock policy.
|
||||
*
|
||||
* @cmd: The IOCTL command that is supposed to be run.
|
||||
*
|
||||
* By default, any IOCTL on a device file requires the
|
||||
* LANDLOCK_ACCESS_FS_IOCTL_DEV right. However, we blanket-permit some
|
||||
* commands, if:
|
||||
*
|
||||
* 1. The command is implemented in fs/ioctl.c's do_vfs_ioctl(),
|
||||
* not in f_ops->unlocked_ioctl() or f_ops->compat_ioctl().
|
||||
*
|
||||
* 2. The command is harmless when invoked on devices.
|
||||
*
|
||||
* We also permit commands that do not make sense for devices, but where the
|
||||
* do_vfs_ioctl() implementation returns a more conventional error code.
|
||||
*
|
||||
* Any new IOCTL commands that are implemented in fs/ioctl.c's do_vfs_ioctl()
|
||||
* should be considered for inclusion here.
|
||||
*
|
||||
* Returns: true if the IOCTL @cmd can not be restricted with Landlock for
|
||||
* device files.
|
||||
*/
|
||||
static __attribute_const__ bool is_masked_device_ioctl(const unsigned int cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
/*
|
||||
* FIOCLEX, FIONCLEX, FIONBIO and FIOASYNC manipulate the FD's
|
||||
* close-on-exec and the file's buffered-IO and async flags. These
|
||||
* operations are also available through fcntl(2), and are
|
||||
* unconditionally permitted in Landlock.
|
||||
*/
|
||||
case FIOCLEX:
|
||||
case FIONCLEX:
|
||||
case FIONBIO:
|
||||
case FIOASYNC:
|
||||
/*
|
||||
* FIOQSIZE queries the size of a regular file, directory, or link.
|
||||
*
|
||||
* We still permit it, because it always returns -ENOTTY for
|
||||
* other file types.
|
||||
*/
|
||||
case FIOQSIZE:
|
||||
/*
|
||||
* FIFREEZE and FITHAW freeze and thaw the file system which the
|
||||
* given file belongs to. Requires CAP_SYS_ADMIN.
|
||||
*
|
||||
* These commands operate on the file system's superblock rather
|
||||
* than on the file itself. The same operations can also be
|
||||
* done through any other file or directory on the same file
|
||||
* system, so it is safe to permit these.
|
||||
*/
|
||||
case FIFREEZE:
|
||||
case FITHAW:
|
||||
/*
|
||||
* FS_IOC_FIEMAP queries information about the allocation of
|
||||
* blocks within a file.
|
||||
*
|
||||
* This IOCTL command only makes sense for regular files and is
|
||||
* not implemented by devices. It is harmless to permit.
|
||||
*/
|
||||
case FS_IOC_FIEMAP:
|
||||
/*
|
||||
* FIGETBSZ queries the file system's block size for a file or
|
||||
* directory.
|
||||
*
|
||||
* This command operates on the file system's superblock rather
|
||||
* than on the file itself. The same operation can also be done
|
||||
* through any other file or directory on the same file system,
|
||||
* so it is safe to permit it.
|
||||
*/
|
||||
case FIGETBSZ:
|
||||
/*
|
||||
* FICLONE, FICLONERANGE and FIDEDUPERANGE make files share
|
||||
* their underlying storage ("reflink") between source and
|
||||
* destination FDs, on file systems which support that.
|
||||
*
|
||||
* These IOCTL commands only apply to regular files
|
||||
* and are harmless to permit for device files.
|
||||
*/
|
||||
case FICLONE:
|
||||
case FICLONERANGE:
|
||||
case FIDEDUPERANGE:
|
||||
/*
|
||||
* FS_IOC_GETFSUUID and FS_IOC_GETFSSYSFSPATH both operate on
|
||||
* the file system superblock, not on the specific file, so
|
||||
* these operations are available through any other file on the
|
||||
* same file system as well.
|
||||
*/
|
||||
case FS_IOC_GETFSUUID:
|
||||
case FS_IOC_GETFSSYSFSPATH:
|
||||
return true;
|
||||
|
||||
/*
|
||||
* FIONREAD, FS_IOC_GETFLAGS, FS_IOC_SETFLAGS, FS_IOC_FSGETXATTR and
|
||||
* FS_IOC_FSSETXATTR are forwarded to device implementations.
|
||||
*/
|
||||
|
||||
/*
|
||||
* file_ioctl() commands (FIBMAP, FS_IOC_RESVSP, FS_IOC_RESVSP64,
|
||||
* FS_IOC_UNRESVSP, FS_IOC_UNRESVSP64 and FS_IOC_ZERO_RANGE) are
|
||||
* forwarded to device implementations, so not permitted.
|
||||
*/
|
||||
|
||||
/* Other commands are guarded by the access right. */
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* is_masked_device_ioctl_compat - same as the helper above, but checking the
|
||||
* "compat" IOCTL commands.
|
||||
*
|
||||
* The IOCTL commands with special handling in compat-mode should behave the
|
||||
* same as their non-compat counterparts.
|
||||
*/
|
||||
static __attribute_const__ bool
|
||||
is_masked_device_ioctl_compat(const unsigned int cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
/* FICLONE is permitted, same as in the non-compat variant. */
|
||||
case FICLONE:
|
||||
return true;
|
||||
|
||||
#if defined(CONFIG_X86_64)
|
||||
/*
|
||||
* FS_IOC_RESVSP_32, FS_IOC_RESVSP64_32, FS_IOC_UNRESVSP_32,
|
||||
* FS_IOC_UNRESVSP64_32, FS_IOC_ZERO_RANGE_32: not blanket-permitted,
|
||||
* for consistency with their non-compat variants.
|
||||
*/
|
||||
case FS_IOC_RESVSP_32:
|
||||
case FS_IOC_RESVSP64_32:
|
||||
case FS_IOC_UNRESVSP_32:
|
||||
case FS_IOC_UNRESVSP64_32:
|
||||
case FS_IOC_ZERO_RANGE_32:
|
||||
#endif
|
||||
|
||||
/*
|
||||
* FS_IOC32_GETFLAGS, FS_IOC32_SETFLAGS are forwarded to their device
|
||||
* implementations.
|
||||
*/
|
||||
case FS_IOC32_GETFLAGS:
|
||||
case FS_IOC32_SETFLAGS:
|
||||
return false;
|
||||
default:
|
||||
return is_masked_device_ioctl(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/* Ruleset management */
|
||||
|
||||
static struct landlock_object *get_inode_object(struct inode *const inode)
|
||||
@ -148,7 +307,8 @@ retry:
|
||||
LANDLOCK_ACCESS_FS_EXECUTE | \
|
||||
LANDLOCK_ACCESS_FS_WRITE_FILE | \
|
||||
LANDLOCK_ACCESS_FS_READ_FILE | \
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE)
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE | \
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV)
|
||||
/* clang-format on */
|
||||
|
||||
/*
|
||||
@ -1332,11 +1492,18 @@ static int hook_file_alloc_security(struct file *const file)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_device(const struct file *const file)
|
||||
{
|
||||
const struct inode *inode = file_inode(file);
|
||||
|
||||
return S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode);
|
||||
}
|
||||
|
||||
static int hook_file_open(struct file *const file)
|
||||
{
|
||||
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
|
||||
access_mask_t open_access_request, full_access_request, allowed_access;
|
||||
const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
access_mask_t open_access_request, full_access_request, allowed_access,
|
||||
optional_access;
|
||||
const struct landlock_ruleset *const dom =
|
||||
get_fs_domain(landlock_cred(file->f_cred)->domain);
|
||||
|
||||
@ -1354,6 +1521,10 @@ static int hook_file_open(struct file *const file)
|
||||
* We look up more access than what we immediately need for open(), so
|
||||
* that we can later authorize operations on opened files.
|
||||
*/
|
||||
optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
if (is_device(file))
|
||||
optional_access |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
|
||||
full_access_request = open_access_request | optional_access;
|
||||
|
||||
if (is_access_to_paths_allowed(
|
||||
@ -1410,6 +1581,52 @@ static int hook_file_truncate(struct file *const file)
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
static int hook_file_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
access_mask_t allowed_access = landlock_file(file)->allowed_access;
|
||||
|
||||
/*
|
||||
* It is the access rights at the time of opening the file which
|
||||
* determine whether IOCTL can be used on the opened file later.
|
||||
*
|
||||
* The access right is attached to the opened file in hook_file_open().
|
||||
*/
|
||||
if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV)
|
||||
return 0;
|
||||
|
||||
if (!is_device(file))
|
||||
return 0;
|
||||
|
||||
if (is_masked_device_ioctl(cmd))
|
||||
return 0;
|
||||
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
access_mask_t allowed_access = landlock_file(file)->allowed_access;
|
||||
|
||||
/*
|
||||
* It is the access rights at the time of opening the file which
|
||||
* determine whether IOCTL can be used on the opened file later.
|
||||
*
|
||||
* The access right is attached to the opened file in hook_file_open().
|
||||
*/
|
||||
if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV)
|
||||
return 0;
|
||||
|
||||
if (!is_device(file))
|
||||
return 0;
|
||||
|
||||
if (is_masked_device_ioctl_compat(cmd))
|
||||
return 0;
|
||||
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
static struct security_hook_list landlock_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(inode_free_security, hook_inode_free_security),
|
||||
|
||||
@ -1432,6 +1649,8 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
|
||||
LSM_HOOK_INIT(file_open, hook_file_open),
|
||||
LSM_HOOK_INIT(file_truncate, hook_file_truncate),
|
||||
LSM_HOOK_INIT(file_ioctl, hook_file_ioctl),
|
||||
LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat),
|
||||
};
|
||||
|
||||
__init void landlock_add_fs_hooks(void)
|
||||
|
@ -18,7 +18,7 @@
|
||||
#define LANDLOCK_MAX_NUM_LAYERS 16
|
||||
#define LANDLOCK_MAX_NUM_RULES U32_MAX
|
||||
|
||||
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_TRUNCATE
|
||||
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
|
||||
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
|
||||
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
|
||||
#define LANDLOCK_SHIFT_ACCESS_FS 0
|
||||
|
@ -149,7 +149,7 @@ static const struct file_operations ruleset_fops = {
|
||||
.write = fop_dummy_write,
|
||||
};
|
||||
|
||||
#define LANDLOCK_ABI_VERSION 4
|
||||
#define LANDLOCK_ABI_VERSION 5
|
||||
|
||||
/**
|
||||
* sys_landlock_create_ruleset - Create a new ruleset
|
||||
|
@ -75,7 +75,7 @@ TEST(abi_version)
|
||||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
};
|
||||
ASSERT_EQ(4, landlock_create_ruleset(NULL, 0,
|
||||
ASSERT_EQ(5, landlock_create_ruleset(NULL, 0,
|
||||
LANDLOCK_CREATE_RULESET_VERSION));
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
|
||||
|
@ -8,22 +8,34 @@
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <asm/termbits.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <linux/fiemap.h>
|
||||
#include <linux/landlock.h>
|
||||
#include <linux/magic.h>
|
||||
#include <sched.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/vfs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* Intentionally included last to work around header conflict.
|
||||
* See https://sourceware.org/glibc/wiki/Synchronizing_Headers.
|
||||
*/
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#ifndef renameat2
|
||||
@ -536,9 +548,10 @@ TEST_F_FORK(layout1, inval)
|
||||
LANDLOCK_ACCESS_FS_EXECUTE | \
|
||||
LANDLOCK_ACCESS_FS_WRITE_FILE | \
|
||||
LANDLOCK_ACCESS_FS_READ_FILE | \
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE)
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE | \
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV)
|
||||
|
||||
#define ACCESS_LAST LANDLOCK_ACCESS_FS_TRUNCATE
|
||||
#define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV
|
||||
|
||||
#define ACCESS_ALL ( \
|
||||
ACCESS_FILE | \
|
||||
@ -743,6 +756,9 @@ static int create_ruleset(struct __test_metadata *const _metadata,
|
||||
}
|
||||
|
||||
for (i = 0; rules[i].path; i++) {
|
||||
if (!rules[i].access)
|
||||
continue;
|
||||
|
||||
add_path_beneath(_metadata, ruleset_fd, rules[i].access,
|
||||
rules[i].path);
|
||||
}
|
||||
@ -3451,7 +3467,7 @@ TEST_F_FORK(layout1, truncate_unhandled)
|
||||
LANDLOCK_ACCESS_FS_WRITE_FILE;
|
||||
int ruleset_fd;
|
||||
|
||||
/* Enable Landlock. */
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = create_ruleset(_metadata, handled, rules);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
@ -3534,7 +3550,7 @@ TEST_F_FORK(layout1, truncate)
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
int ruleset_fd;
|
||||
|
||||
/* Enable Landlock. */
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = create_ruleset(_metadata, handled, rules);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
@ -3760,7 +3776,7 @@ TEST_F_FORK(ftruncate, open_and_ftruncate)
|
||||
};
|
||||
int fd, ruleset_fd;
|
||||
|
||||
/* Enable Landlock. */
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
@ -3837,22 +3853,471 @@ TEST_F_FORK(ftruncate, open_and_ftruncate_in_different_processes)
|
||||
ASSERT_EQ(0, close(socket_fds[1]));
|
||||
}
|
||||
|
||||
TEST(memfd_ftruncate)
|
||||
/* Invokes the FS_IOC_GETFLAGS IOCTL and returns its errno or 0. */
|
||||
static int test_fs_ioc_getflags_ioctl(int fd)
|
||||
{
|
||||
int fd;
|
||||
uint32_t flags;
|
||||
|
||||
fd = memfd_create("name", MFD_CLOEXEC);
|
||||
if (ioctl(fd, FS_IOC_GETFLAGS, &flags) < 0)
|
||||
return errno;
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(memfd_ftruncate_and_ioctl)
|
||||
{
|
||||
const struct landlock_ruleset_attr attr = {
|
||||
.handled_access_fs = ACCESS_ALL,
|
||||
};
|
||||
int ruleset_fd, fd, i;
|
||||
|
||||
/*
|
||||
* We exercise the same test both with and without Landlock enabled, to
|
||||
* ensure that it behaves the same in both cases.
|
||||
*/
|
||||
for (i = 0; i < 2; i++) {
|
||||
/* Creates a new memfd. */
|
||||
fd = memfd_create("name", MFD_CLOEXEC);
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
/*
|
||||
* Checks that operations associated with the opened file
|
||||
* (ftruncate, ioctl) are permitted on file descriptors that are
|
||||
* created in ways other than open(2).
|
||||
*/
|
||||
EXPECT_EQ(0, test_ftruncate(fd));
|
||||
EXPECT_EQ(0, test_fs_ioc_getflags_ioctl(fd));
|
||||
|
||||
ASSERT_EQ(0, close(fd));
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
}
|
||||
}
|
||||
|
||||
static int test_fionread_ioctl(int fd)
|
||||
{
|
||||
size_t sz = 0;
|
||||
|
||||
if (ioctl(fd, FIONREAD, &sz) < 0 && errno == EACCES)
|
||||
return errno;
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl)
|
||||
{
|
||||
const struct landlock_ruleset_attr attr = {
|
||||
.handled_access_fs = ACCESS_ALL,
|
||||
};
|
||||
int ruleset_fd, fd;
|
||||
|
||||
/*
|
||||
* Checks that for files opened with O_PATH, both ioctl(2) and
|
||||
* ftruncate(2) yield EBADF, as it is documented in open(2) for the
|
||||
* O_PATH flag.
|
||||
*/
|
||||
fd = open(dir_s1d1, O_PATH | O_CLOEXEC);
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
EXPECT_EQ(EBADF, test_ftruncate(fd));
|
||||
EXPECT_EQ(EBADF, test_fs_ioc_getflags_ioctl(fd));
|
||||
|
||||
ASSERT_EQ(0, close(fd));
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
/*
|
||||
* Checks that after enabling Landlock,
|
||||
* - the file can still be opened with O_PATH
|
||||
* - both ioctl and truncate still yield EBADF (not EACCES).
|
||||
*/
|
||||
fd = open(dir_s1d1, O_PATH | O_CLOEXEC);
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
EXPECT_EQ(EBADF, test_ftruncate(fd));
|
||||
EXPECT_EQ(EBADF, test_fs_ioc_getflags_ioctl(fd));
|
||||
|
||||
ASSERT_EQ(0, close(fd));
|
||||
}
|
||||
|
||||
/*
|
||||
* ioctl_error - generically call the given ioctl with a pointer to a
|
||||
* sufficiently large zeroed-out memory region.
|
||||
*
|
||||
* Returns the IOCTLs error, or 0.
|
||||
*/
|
||||
static int ioctl_error(struct __test_metadata *const _metadata, int fd,
|
||||
unsigned int cmd)
|
||||
{
|
||||
char buf[128]; /* sufficiently large */
|
||||
int res, stdinbak_fd;
|
||||
|
||||
/*
|
||||
* Depending on the IOCTL command, parts of the zeroed-out buffer might
|
||||
* be interpreted as file descriptor numbers. We do not want to
|
||||
* accidentally operate on file descriptor 0 (stdin), so we temporarily
|
||||
* move stdin to a different FD and close FD 0 for the IOCTL call.
|
||||
*/
|
||||
stdinbak_fd = dup(0);
|
||||
ASSERT_LT(0, stdinbak_fd);
|
||||
ASSERT_EQ(0, close(0));
|
||||
|
||||
/* Invokes the IOCTL with a zeroed-out buffer. */
|
||||
bzero(&buf, sizeof(buf));
|
||||
res = ioctl(fd, cmd, &buf);
|
||||
|
||||
/* Restores the old FD 0 and closes the backup FD. */
|
||||
ASSERT_EQ(0, dup2(stdinbak_fd, 0));
|
||||
ASSERT_EQ(0, close(stdinbak_fd));
|
||||
|
||||
if (res < 0)
|
||||
return errno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Define some linux/falloc.h IOCTL commands which are not available in uapi headers. */
|
||||
struct space_resv {
|
||||
__s16 l_type;
|
||||
__s16 l_whence;
|
||||
__s64 l_start;
|
||||
__s64 l_len; /* len == 0 means until end of file */
|
||||
__s32 l_sysid;
|
||||
__u32 l_pid;
|
||||
__s32 l_pad[4]; /* reserved area */
|
||||
};
|
||||
|
||||
#define FS_IOC_RESVSP _IOW('X', 40, struct space_resv)
|
||||
#define FS_IOC_UNRESVSP _IOW('X', 41, struct space_resv)
|
||||
#define FS_IOC_RESVSP64 _IOW('X', 42, struct space_resv)
|
||||
#define FS_IOC_UNRESVSP64 _IOW('X', 43, struct space_resv)
|
||||
#define FS_IOC_ZERO_RANGE _IOW('X', 57, struct space_resv)
|
||||
|
||||
/*
|
||||
* Tests a series of blanket-permitted and denied IOCTLs.
|
||||
*/
|
||||
TEST_F_FORK(layout1, blanket_permitted_ioctls)
|
||||
{
|
||||
const struct landlock_ruleset_attr attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
};
|
||||
int ruleset_fd, fd;
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
fd = open("/dev/null", O_RDWR | O_CLOEXEC);
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
/*
|
||||
* Checks that ftruncate is permitted on file descriptors that are
|
||||
* created in ways other than open(2).
|
||||
* Checks permitted commands.
|
||||
* These ones may return errors, but should not be blocked by Landlock.
|
||||
*/
|
||||
EXPECT_EQ(0, test_ftruncate(fd));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOCLEX));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONCLEX));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONBIO));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOASYNC));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOQSIZE));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIFREEZE));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FITHAW));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_FIEMAP));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIGETBSZ));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONE));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONERANGE));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIDEDUPERANGE));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSUUID));
|
||||
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSSYSFSPATH));
|
||||
|
||||
/*
|
||||
* Checks blocked commands.
|
||||
* A call to a blocked IOCTL command always returns EACCES.
|
||||
*/
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFLAGS));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_SETFLAGS));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSGETXATTR));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSSETXATTR));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIBMAP));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP64));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP64));
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_ZERO_RANGE));
|
||||
|
||||
/* Default case is also blocked. */
|
||||
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, 0xc00ffeee));
|
||||
|
||||
ASSERT_EQ(0, close(fd));
|
||||
}
|
||||
|
||||
/*
|
||||
* Named pipes are not governed by the LANDLOCK_ACCESS_FS_IOCTL_DEV right,
|
||||
* because they are not character or block devices.
|
||||
*/
|
||||
TEST_F_FORK(layout1, named_pipe_ioctl)
|
||||
{
|
||||
pid_t child_pid;
|
||||
int fd, ruleset_fd;
|
||||
const char *const path = file1_s1d1;
|
||||
const struct landlock_ruleset_attr attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
};
|
||||
|
||||
ASSERT_EQ(0, unlink(path));
|
||||
ASSERT_EQ(0, mkfifo(path, 0600));
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
/* The child process opens the pipe for writing. */
|
||||
child_pid = fork();
|
||||
ASSERT_NE(-1, child_pid);
|
||||
if (child_pid == 0) {
|
||||
fd = open(path, O_WRONLY);
|
||||
close(fd);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
/* FIONREAD is implemented by pipefifo_fops. */
|
||||
EXPECT_EQ(0, test_fionread_ioctl(fd));
|
||||
|
||||
ASSERT_EQ(0, close(fd));
|
||||
ASSERT_EQ(0, unlink(path));
|
||||
|
||||
ASSERT_EQ(child_pid, waitpid(child_pid, NULL, 0));
|
||||
}
|
||||
|
||||
/* For named UNIX domain sockets, no IOCTL restrictions apply. */
|
||||
TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
|
||||
{
|
||||
const char *const path = file1_s1d1;
|
||||
int srv_fd, cli_fd, ruleset_fd;
|
||||
socklen_t size;
|
||||
struct sockaddr_un srv_un, cli_un;
|
||||
const struct landlock_ruleset_attr attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
};
|
||||
|
||||
/* Sets up a server */
|
||||
srv_un.sun_family = AF_UNIX;
|
||||
strncpy(srv_un.sun_path, path, sizeof(srv_un.sun_path));
|
||||
|
||||
ASSERT_EQ(0, unlink(path));
|
||||
srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, srv_fd);
|
||||
|
||||
size = offsetof(struct sockaddr_un, sun_path) + strlen(srv_un.sun_path);
|
||||
ASSERT_EQ(0, bind(srv_fd, (struct sockaddr *)&srv_un, size));
|
||||
ASSERT_EQ(0, listen(srv_fd, 10 /* qlen */));
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
/* Sets up a client connection to it */
|
||||
cli_un.sun_family = AF_UNIX;
|
||||
cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, cli_fd);
|
||||
|
||||
size = offsetof(struct sockaddr_un, sun_path) + strlen(cli_un.sun_path);
|
||||
ASSERT_EQ(0, bind(cli_fd, (struct sockaddr *)&cli_un, size));
|
||||
|
||||
bzero(&cli_un, sizeof(cli_un));
|
||||
cli_un.sun_family = AF_UNIX;
|
||||
strncpy(cli_un.sun_path, path, sizeof(cli_un.sun_path));
|
||||
size = offsetof(struct sockaddr_un, sun_path) + strlen(cli_un.sun_path);
|
||||
|
||||
ASSERT_EQ(0, connect(cli_fd, (struct sockaddr *)&cli_un, size));
|
||||
|
||||
/* FIONREAD and other IOCTLs should not be forbidden. */
|
||||
EXPECT_EQ(0, test_fionread_ioctl(cli_fd));
|
||||
|
||||
ASSERT_EQ(0, close(cli_fd));
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(ioctl) {};
|
||||
|
||||
FIXTURE_SETUP(ioctl) {};
|
||||
|
||||
FIXTURE_TEARDOWN(ioctl) {};
|
||||
/* clang-format on */
|
||||
|
||||
FIXTURE_VARIANT(ioctl)
|
||||
{
|
||||
const __u64 handled;
|
||||
const __u64 allowed;
|
||||
const mode_t open_mode;
|
||||
/*
|
||||
* FIONREAD is used as a characteristic device-specific IOCTL command.
|
||||
* It is implemented in fs/ioctl.c for regular files,
|
||||
* but we do not blanket-permit it for devices.
|
||||
*/
|
||||
const int expected_fionread_result;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_none) {
|
||||
/* clang-format on */
|
||||
.handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.allowed = 0,
|
||||
.open_mode = O_RDWR,
|
||||
.expected_fionread_result = EACCES,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_i) {
|
||||
/* clang-format on */
|
||||
.handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.allowed = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
.open_mode = O_RDWR,
|
||||
.expected_fionread_result = 0,
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(ioctl, unhandled) {
|
||||
/* clang-format on */
|
||||
.handled = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.allowed = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
.open_mode = O_RDWR,
|
||||
.expected_fionread_result = 0,
|
||||
};
|
||||
|
||||
TEST_F_FORK(ioctl, handle_dir_access_file)
|
||||
{
|
||||
const int flag = 0;
|
||||
const struct rule rules[] = {
|
||||
{
|
||||
.path = "/dev",
|
||||
.access = variant->allowed,
|
||||
},
|
||||
{},
|
||||
};
|
||||
int file_fd, ruleset_fd;
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
file_fd = open("/dev/zero", variant->open_mode);
|
||||
ASSERT_LE(0, file_fd);
|
||||
|
||||
/* Checks that IOCTL commands return the expected errors. */
|
||||
EXPECT_EQ(variant->expected_fionread_result,
|
||||
test_fionread_ioctl(file_fd));
|
||||
|
||||
/* Checks that unrestrictable commands are unrestricted. */
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));
|
||||
|
||||
ASSERT_EQ(0, close(file_fd));
|
||||
}
|
||||
|
||||
TEST_F_FORK(ioctl, handle_dir_access_dir)
|
||||
{
|
||||
const int flag = 0;
|
||||
const struct rule rules[] = {
|
||||
{
|
||||
.path = "/dev",
|
||||
.access = variant->allowed,
|
||||
},
|
||||
{},
|
||||
};
|
||||
int dir_fd, ruleset_fd;
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
/*
|
||||
* Ignore variant->open_mode for this test, as we intend to open a
|
||||
* directory. If the directory can not be opened, the variant is
|
||||
* infeasible to test with an opened directory.
|
||||
*/
|
||||
dir_fd = open("/dev", O_RDONLY);
|
||||
if (dir_fd < 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Checks that IOCTL commands return the expected errors.
|
||||
* We do not use the expected values from the fixture here.
|
||||
*
|
||||
* When using IOCTL on a directory, no Landlock restrictions apply.
|
||||
*/
|
||||
EXPECT_EQ(0, test_fionread_ioctl(dir_fd));
|
||||
|
||||
/* Checks that unrestrictable commands are unrestricted. */
|
||||
EXPECT_EQ(0, ioctl(dir_fd, FIOCLEX));
|
||||
EXPECT_EQ(0, ioctl(dir_fd, FIONCLEX));
|
||||
EXPECT_EQ(0, ioctl(dir_fd, FIONBIO, &flag));
|
||||
EXPECT_EQ(0, ioctl(dir_fd, FIOASYNC, &flag));
|
||||
EXPECT_EQ(0, ioctl(dir_fd, FIGETBSZ, &flag));
|
||||
|
||||
ASSERT_EQ(0, close(dir_fd));
|
||||
}
|
||||
|
||||
TEST_F_FORK(ioctl, handle_file_access_file)
|
||||
{
|
||||
const int flag = 0;
|
||||
const struct rule rules[] = {
|
||||
{
|
||||
.path = "/dev/zero",
|
||||
.access = variant->allowed,
|
||||
},
|
||||
{},
|
||||
};
|
||||
int file_fd, ruleset_fd;
|
||||
|
||||
/* Enables Landlock. */
|
||||
ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
file_fd = open("/dev/zero", variant->open_mode);
|
||||
ASSERT_LE(0, file_fd)
|
||||
{
|
||||
TH_LOG("Failed to open /dev/zero: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* Checks that IOCTL commands return the expected errors. */
|
||||
EXPECT_EQ(variant->expected_fionread_result,
|
||||
test_fionread_ioctl(file_fd));
|
||||
|
||||
/* Checks that unrestrictable commands are unrestricted. */
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
|
||||
EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));
|
||||
|
||||
ASSERT_EQ(0, close(file_fd));
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(layout1_bind) {};
|
||||
/* clang-format on */
|
||||
|
Loading…
Reference in New Issue
Block a user