Skip to content

Commit b25f741

Browse files
gnoackl0kod
authored andcommitted
landlock: Add IOCTL access right for character and block devices
Introduces the LANDLOCK_ACCESS_FS_IOCTL_DEV right and increments the Landlock ABI version to 5. This access right applies to device-custom IOCTL commands when they are invoked on block or character device files. Like the truncate right, this right is associated with a file descriptor at the time of open(2), and gets respected even when the file descriptor is used outside of the thread which it was originally opened in. Therefore, a newly enabled Landlock policy does not apply to file descriptors which are already open. If the LANDLOCK_ACCESS_FS_IOCTL_DEV right is handled, only a small number of safe IOCTL commands will be permitted on newly opened device files. These include FIOCLEX, FIONCLEX, FIONBIO and FIOASYNC, as well as other IOCTL commands for regular files which are implemented in fs/ioctl.c. Noteworthy scenarios which require special attention: TTY devices are often passed into a process from the parent process, and so a newly enabled Landlock policy does not retroactively apply to them automatically. In the past, TTY devices have often supported IOCTL commands like TIOCSTI and some TIOCLINUX subcommands, which were letting callers control the TTY input buffer (and simulate keypresses). This should be restricted to CAP_SYS_ADMIN programs on modern kernels though. Known limitations: The LANDLOCK_ACCESS_FS_IOCTL_DEV access right is a coarse-grained control over IOCTL commands. Landlock users may use path-based restrictions in combination with their knowledge about the file system layout to control what IOCTLs can be done. Cc: Paul Moore <[email protected]> Cc: Christian Brauner <[email protected]> Cc: Arnd Bergmann <[email protected]> Signed-off-by: Günther Noack <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mickaël Salaün <[email protected]>
1 parent 4221293 commit b25f741

File tree

6 files changed

+258
-16
lines changed

6 files changed

+258
-16
lines changed

include/uapi/linux/landlock.h

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ struct landlock_net_port_attr {
128128
* files and directories. Files or directories opened before the sandboxing
129129
* are not subject to these restrictions.
130130
*
131-
* A file can only receive these access rights:
131+
* The following access rights apply only to files:
132132
*
133133
* - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
134134
* - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that
@@ -138,12 +138,13 @@ struct landlock_net_port_attr {
138138
* - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access.
139139
* - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`,
140140
* :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with
141-
* ``O_TRUNC``. Whether an opened file can be truncated with
142-
* :manpage:`ftruncate(2)` is determined during :manpage:`open(2)`, in the
143-
* same way as read and write permissions are checked during
144-
* :manpage:`open(2)` using %LANDLOCK_ACCESS_FS_READ_FILE and
145-
* %LANDLOCK_ACCESS_FS_WRITE_FILE. This access right is available since the
146-
* third version of the Landlock ABI.
141+
* ``O_TRUNC``. This access right is available since the third version of the
142+
* Landlock ABI.
143+
*
144+
* Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used
145+
* with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
146+
* read and write permissions are checked during :manpage:`open(2)` using
147+
* %LANDLOCK_ACCESS_FS_READ_FILE and %LANDLOCK_ACCESS_FS_WRITE_FILE.
147148
*
148149
* A directory can receive access rights related to files or directories. The
149150
* following access right is applied to the directory itself, and the
@@ -198,13 +199,33 @@ struct landlock_net_port_attr {
198199
* If multiple requirements are not met, the ``EACCES`` error code takes
199200
* precedence over ``EXDEV``.
200201
*
202+
* The following access right applies both to files and directories:
203+
*
204+
* - %LANDLOCK_ACCESS_FS_IOCTL_DEV: Invoke :manpage:`ioctl(2)` commands on an opened
205+
* character or block device.
206+
*
207+
* This access right applies to all `ioctl(2)` commands implemented by device
208+
* drivers. However, the following common IOCTL commands continue to be
209+
* invokable independent of the %LANDLOCK_ACCESS_FS_IOCTL_DEV right:
210+
*
211+
* * IOCTL commands targeting file descriptors (``FIOCLEX``, ``FIONCLEX``),
212+
* * IOCTL commands targeting file descriptions (``FIONBIO``, ``FIOASYNC``),
213+
* * IOCTL commands targeting file systems (``FIFREEZE``, ``FITHAW``,
214+
* ``FIGETBSZ``, ``FS_IOC_GETFSUUID``, ``FS_IOC_GETFSSYSFSPATH``)
215+
* * Some IOCTL commands which do not make sense when used with devices, but
216+
* whose implementations are safe and return the right error codes
217+
* (``FS_IOC_FIEMAP``, ``FICLONE``, ``FICLONERANGE``, ``FIDEDUPERANGE``)
218+
*
219+
* This access right is available since the fifth version of the Landlock
220+
* ABI.
221+
*
201222
* .. warning::
202223
*
203224
* It is currently not possible to restrict some file-related actions
204225
* accessible through these syscall families: :manpage:`chdir(2)`,
205226
* :manpage:`stat(2)`, :manpage:`flock(2)`, :manpage:`chmod(2)`,
206227
* :manpage:`chown(2)`, :manpage:`setxattr(2)`, :manpage:`utime(2)`,
207-
* :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, :manpage:`access(2)`.
228+
* :manpage:`fcntl(2)`, :manpage:`access(2)`.
208229
* Future Landlock evolutions will enable to restrict them.
209230
*/
210231
/* clang-format off */
@@ -223,6 +244,7 @@ struct landlock_net_port_attr {
223244
#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
224245
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
225246
#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
247+
#define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15)
226248
/* clang-format on */
227249

228250
/**

security/landlock/fs.c

Lines changed: 222 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
* Copyright © 2016-2020 Mickaël Salaün <[email protected]>
66
* Copyright © 2018-2020 ANSSI
77
* Copyright © 2021-2022 Microsoft Corporation
8+
* Copyright © 2022 Günther Noack <[email protected]>
9+
* Copyright © 2023-2024 Google LLC
810
*/
911

12+
#include <asm/ioctls.h>
1013
#include <kunit/test.h>
1114
#include <linux/atomic.h>
1215
#include <linux/bitops.h>
1316
#include <linux/bits.h>
1417
#include <linux/compiler_types.h>
1518
#include <linux/dcache.h>
1619
#include <linux/err.h>
20+
#include <linux/falloc.h>
1721
#include <linux/fs.h>
1822
#include <linux/init.h>
1923
#include <linux/kernel.h>
@@ -29,6 +33,7 @@
2933
#include <linux/types.h>
3034
#include <linux/wait_bit.h>
3135
#include <linux/workqueue.h>
36+
#include <uapi/linux/fiemap.h>
3237
#include <uapi/linux/landlock.h>
3338

3439
#include "common.h"
@@ -84,6 +89,160 @@ static const struct landlock_object_underops landlock_fs_underops = {
8489
.release = release_inode
8590
};
8691

92+
/* IOCTL helpers */
93+
94+
/**
95+
* is_masked_device_ioctl - Determine whether an IOCTL command is always
96+
* permitted with Landlock for device files. These commands can not be
97+
* restricted on device files by enforcing a Landlock policy.
98+
*
99+
* @cmd: The IOCTL command that is supposed to be run.
100+
*
101+
* By default, any IOCTL on a device file requires the
102+
* LANDLOCK_ACCESS_FS_IOCTL_DEV right. However, we blanket-permit some
103+
* commands, if:
104+
*
105+
* 1. The command is implemented in fs/ioctl.c's do_vfs_ioctl(),
106+
* not in f_ops->unlocked_ioctl() or f_ops->compat_ioctl().
107+
*
108+
* 2. The command is harmless when invoked on devices.
109+
*
110+
* We also permit commands that do not make sense for devices, but where the
111+
* do_vfs_ioctl() implementation returns a more conventional error code.
112+
*
113+
* Any new IOCTL commands that are implemented in fs/ioctl.c's do_vfs_ioctl()
114+
* should be considered for inclusion here.
115+
*
116+
* Returns: true if the IOCTL @cmd can not be restricted with Landlock for
117+
* device files.
118+
*/
119+
static __attribute_const__ bool is_masked_device_ioctl(const unsigned int cmd)
120+
{
121+
switch (cmd) {
122+
/*
123+
* FIOCLEX, FIONCLEX, FIONBIO and FIOASYNC manipulate the FD's
124+
* close-on-exec and the file's buffered-IO and async flags. These
125+
* operations are also available through fcntl(2), and are
126+
* unconditionally permitted in Landlock.
127+
*/
128+
case FIOCLEX:
129+
case FIONCLEX:
130+
case FIONBIO:
131+
case FIOASYNC:
132+
/*
133+
* FIOQSIZE queries the size of a regular file, directory, or link.
134+
*
135+
* We still permit it, because it always returns -ENOTTY for
136+
* other file types.
137+
*/
138+
case FIOQSIZE:
139+
/*
140+
* FIFREEZE and FITHAW freeze and thaw the file system which the
141+
* given file belongs to. Requires CAP_SYS_ADMIN.
142+
*
143+
* These commands operate on the file system's superblock rather
144+
* than on the file itself. The same operations can also be
145+
* done through any other file or directory on the same file
146+
* system, so it is safe to permit these.
147+
*/
148+
case FIFREEZE:
149+
case FITHAW:
150+
/*
151+
* FS_IOC_FIEMAP queries information about the allocation of
152+
* blocks within a file.
153+
*
154+
* This IOCTL command only makes sense for regular files and is
155+
* not implemented by devices. It is harmless to permit.
156+
*/
157+
case FS_IOC_FIEMAP:
158+
/*
159+
* FIGETBSZ queries the file system's block size for a file or
160+
* directory.
161+
*
162+
* This command operates on the file system's superblock rather
163+
* than on the file itself. The same operation can also be done
164+
* through any other file or directory on the same file system,
165+
* so it is safe to permit it.
166+
*/
167+
case FIGETBSZ:
168+
/*
169+
* FICLONE, FICLONERANGE and FIDEDUPERANGE make files share
170+
* their underlying storage ("reflink") between source and
171+
* destination FDs, on file systems which support that.
172+
*
173+
* These IOCTL commands only apply to regular files
174+
* and are harmless to permit for device files.
175+
*/
176+
case FICLONE:
177+
case FICLONERANGE:
178+
case FIDEDUPERANGE:
179+
/*
180+
* FS_IOC_GETFSUUID and FS_IOC_GETFSSYSFSPATH both operate on
181+
* the file system superblock, not on the specific file, so
182+
* these operations are available through any other file on the
183+
* same file system as well.
184+
*/
185+
case FS_IOC_GETFSUUID:
186+
case FS_IOC_GETFSSYSFSPATH:
187+
return true;
188+
189+
/*
190+
* FIONREAD, FS_IOC_GETFLAGS, FS_IOC_SETFLAGS, FS_IOC_FSGETXATTR and
191+
* FS_IOC_FSSETXATTR are forwarded to device implementations.
192+
*/
193+
194+
/*
195+
* file_ioctl() commands (FIBMAP, FS_IOC_RESVSP, FS_IOC_RESVSP64,
196+
* FS_IOC_UNRESVSP, FS_IOC_UNRESVSP64 and FS_IOC_ZERO_RANGE) are
197+
* forwarded to device implementations, so not permitted.
198+
*/
199+
200+
/* Other commands are guarded by the access right. */
201+
default:
202+
return false;
203+
}
204+
}
205+
206+
/*
207+
* is_masked_device_ioctl_compat - same as the helper above, but checking the
208+
* "compat" IOCTL commands.
209+
*
210+
* The IOCTL commands with special handling in compat-mode should behave the
211+
* same as their non-compat counterparts.
212+
*/
213+
static __attribute_const__ bool
214+
is_masked_device_ioctl_compat(const unsigned int cmd)
215+
{
216+
switch (cmd) {
217+
/* FICLONE is permitted, same as in the non-compat variant. */
218+
case FICLONE:
219+
return true;
220+
221+
#if defined(CONFIG_X86_64)
222+
/*
223+
* FS_IOC_RESVSP_32, FS_IOC_RESVSP64_32, FS_IOC_UNRESVSP_32,
224+
* FS_IOC_UNRESVSP64_32, FS_IOC_ZERO_RANGE_32: not blanket-permitted,
225+
* for consistency with their non-compat variants.
226+
*/
227+
case FS_IOC_RESVSP_32:
228+
case FS_IOC_RESVSP64_32:
229+
case FS_IOC_UNRESVSP_32:
230+
case FS_IOC_UNRESVSP64_32:
231+
case FS_IOC_ZERO_RANGE_32:
232+
#endif
233+
234+
/*
235+
* FS_IOC32_GETFLAGS, FS_IOC32_SETFLAGS are forwarded to their device
236+
* implementations.
237+
*/
238+
case FS_IOC32_GETFLAGS:
239+
case FS_IOC32_SETFLAGS:
240+
return false;
241+
default:
242+
return is_masked_device_ioctl(cmd);
243+
}
244+
}
245+
87246
/* Ruleset management */
88247

89248
static struct landlock_object *get_inode_object(struct inode *const inode)
@@ -148,7 +307,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
148307
LANDLOCK_ACCESS_FS_EXECUTE | \
149308
LANDLOCK_ACCESS_FS_WRITE_FILE | \
150309
LANDLOCK_ACCESS_FS_READ_FILE | \
151-
LANDLOCK_ACCESS_FS_TRUNCATE)
310+
LANDLOCK_ACCESS_FS_TRUNCATE | \
311+
LANDLOCK_ACCESS_FS_IOCTL_DEV)
152312
/* clang-format on */
153313

154314
/*
@@ -1332,11 +1492,18 @@ static int hook_file_alloc_security(struct file *const file)
13321492
return 0;
13331493
}
13341494

1495+
static bool is_device(const struct file *const file)
1496+
{
1497+
const struct inode *inode = file_inode(file);
1498+
1499+
return S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode);
1500+
}
1501+
13351502
static int hook_file_open(struct file *const file)
13361503
{
13371504
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
1338-
access_mask_t open_access_request, full_access_request, allowed_access;
1339-
const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
1505+
access_mask_t open_access_request, full_access_request, allowed_access,
1506+
optional_access;
13401507
const struct landlock_ruleset *const dom =
13411508
get_fs_domain(landlock_cred(file->f_cred)->domain);
13421509

@@ -1354,6 +1521,10 @@ static int hook_file_open(struct file *const file)
13541521
* We look up more access than what we immediately need for open(), so
13551522
* that we can later authorize operations on opened files.
13561523
*/
1524+
optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
1525+
if (is_device(file))
1526+
optional_access |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
1527+
13571528
full_access_request = open_access_request | optional_access;
13581529

13591530
if (is_access_to_paths_allowed(
@@ -1410,6 +1581,52 @@ static int hook_file_truncate(struct file *const file)
14101581
return -EACCES;
14111582
}
14121583

1584+
static int hook_file_ioctl(struct file *file, unsigned int cmd,
1585+
unsigned long arg)
1586+
{
1587+
access_mask_t allowed_access = landlock_file(file)->allowed_access;
1588+
1589+
/*
1590+
* It is the access rights at the time of opening the file which
1591+
* determine whether IOCTL can be used on the opened file later.
1592+
*
1593+
* The access right is attached to the opened file in hook_file_open().
1594+
*/
1595+
if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV)
1596+
return 0;
1597+
1598+
if (!is_device(file))
1599+
return 0;
1600+
1601+
if (is_masked_device_ioctl(cmd))
1602+
return 0;
1603+
1604+
return -EACCES;
1605+
}
1606+
1607+
static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
1608+
unsigned long arg)
1609+
{
1610+
access_mask_t allowed_access = landlock_file(file)->allowed_access;
1611+
1612+
/*
1613+
* It is the access rights at the time of opening the file which
1614+
* determine whether IOCTL can be used on the opened file later.
1615+
*
1616+
* The access right is attached to the opened file in hook_file_open().
1617+
*/
1618+
if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV)
1619+
return 0;
1620+
1621+
if (!is_device(file))
1622+
return 0;
1623+
1624+
if (is_masked_device_ioctl_compat(cmd))
1625+
return 0;
1626+
1627+
return -EACCES;
1628+
}
1629+
14131630
static struct security_hook_list landlock_hooks[] __ro_after_init = {
14141631
LSM_HOOK_INIT(inode_free_security, hook_inode_free_security),
14151632

@@ -1432,6 +1649,8 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
14321649
LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
14331650
LSM_HOOK_INIT(file_open, hook_file_open),
14341651
LSM_HOOK_INIT(file_truncate, hook_file_truncate),
1652+
LSM_HOOK_INIT(file_ioctl, hook_file_ioctl),
1653+
LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat),
14351654
};
14361655

14371656
__init void landlock_add_fs_hooks(void)

security/landlock/limits.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#define LANDLOCK_MAX_NUM_LAYERS 16
1919
#define LANDLOCK_MAX_NUM_RULES U32_MAX
2020

21-
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_TRUNCATE
21+
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
2222
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
2323
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
2424
#define LANDLOCK_SHIFT_ACCESS_FS 0

security/landlock/syscalls.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ static const struct file_operations ruleset_fops = {
149149
.write = fop_dummy_write,
150150
};
151151

152-
#define LANDLOCK_ABI_VERSION 4
152+
#define LANDLOCK_ABI_VERSION 5
153153

154154
/**
155155
* sys_landlock_create_ruleset - Create a new ruleset

tools/testing/selftests/landlock/base_test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ TEST(abi_version)
7575
const struct landlock_ruleset_attr ruleset_attr = {
7676
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
7777
};
78-
ASSERT_EQ(4, landlock_create_ruleset(NULL, 0,
78+
ASSERT_EQ(5, landlock_create_ruleset(NULL, 0,
7979
LANDLOCK_CREATE_RULESET_VERSION));
8080

8181
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,

0 commit comments

Comments
 (0)