summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/uapi/linux/landlock.h38
-rw-r--r--security/landlock/fs.c225
-rw-r--r--security/landlock/limits.h2
-rw-r--r--security/landlock/syscalls.c2
-rw-r--r--tools/testing/selftests/landlock/base_test.c2
-rw-r--r--tools/testing/selftests/landlock/fs_test.c5
6 files changed, 258 insertions, 16 deletions
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 25c8d7677539..68625e728f43 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -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 */
/**
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index c15559432d3d..22d8b7c28074 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -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)
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 93c9c6f91556..20fdb5ff3514 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -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
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 6788e73b6681..03b470f5a85a 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -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
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index a6f89aaea77d..3c1e9f35b531 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -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,
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 27744524df51..17b00e6c778d 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -536,9 +536,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 | \