[RFC 0/4] Landlock: ioctl support

Mickaël Salaün mic at digikod.net
Thu May 4 21:12:00 UTC 2023


Thanks for this RFC, this is interesting!

I previously thought a lot about IOCTLs restrictions and here are some 
notes:

IOCTLs are everywhere, from devices to filesystems (see fscrypt). Each 
different file type may behave differently for the same IOCTL 
command/ID. It is also worth noting that there are a lot of different 
IOCTLs, they are growing over time, some might be dedicated to get some 
data (i.e. read) and others to change the state of a device (i.e. 
write), some might be innocuous (e.g. FIOCLEX, FIONCLEX) and others 
potentially harmful. _IOC_READ and _IOC_WRITE can be useful but they are 
not mandatory, there are exceptions, and it may be difficult to identify 
if a command pertains to one, the other, or both kind of actions.

I then think it would be very useful to be able to tie file/device types 
to a set of IOCTLs, letting user space libraries define and classify the 
IOCTL semantic.

Instead of relying on a LANDLOCK_ACCESS_FS_IOCTL which would allow or 
deny all IOCTLs, we can extend the path_beneath struct to manage IOCTLs 
in addition to regular file accesses. Because dealing with a set of 
IOCTLs would imply to deal with a lot of data and combinations, I 
thought about creating groups of IOCTLs (defining access semantic) that 
could be matched against file hierarchies. The composability nature of 
Landlock domains is also another constraint to keep in mind.

// New rule type dedicated to define groups of IOCTLs.
struct landlock_ioctl_attr {
   __u32 command; // IOCTL number/ID
   dev_t device; // must be 0 for regular file and directory
   __u8 file_type;
   __u8 id_mask; // if 0, then applied globally
};

We could use landlock_add_rule(2) to fill a set of landlock_ioctl_attr 
into a ruleset and use them with landlock_path_beneath_attr entries:

// LANDLOCK_RULE_PATH_BENEATH, leveraging the extensible design of
// landlock_path_beneath_attr, hence the same first fields.
struct landlock_path_beneath_attr {
   __u64 allowed_access;
   __s32 parent_fd;
   __u16 allowed_ioctl_id_mask;
};

landlock_ioctl_attr includes a 8-bit mask for which each bit identifies 
a set of allowed IOCTLs per device/file type. This mask is then tied to 
a path_beneath_attr. We cannot use number IDs because of dev_t+IOCTL->ID 
intersection conflicts. Using an id_mask enables to group (specific) 
IOCTLs together, then creating synthetic access rights.

When merging a ruleset with a domain, each IOCTL ID mask is shifted and 
ORed with the other layer ones to get a (8*16) 128-bit mask, stored in 
an IOCTL/dev_t table and in the related landlock_object. When looking 
for an IOCTL request, Landlock first looks into the IOCTL set ID table 
and get the global set ID mask, which kind of translates to a 
composition of synthetic access rights (stored with the 
landlock_layer.ioctl_access bitmask). We then walk through all the 
inodes to match the whole mask.

I realize that this is complex and this explanation might be confusing 
though. What do you think?



On 02/05/2023 19:17, Günther Noack wrote:
> Hello!
> 
> These patches add ioctl support to Landlock.
> 
> It's an early version - it potentially needs more tests and
> documentation.  I'd like to circulate the patches early to discuss
> whether this approach is feasible.
> 
> Objective
> ~~~~~~~~~
> 
> Make ioctl(2) requests restrictable with Landlock,
> in a way that is useful for real-world applications.
> 
> Proposed approach
> ~~~~~~~~~~~~~~~~~
> 
> Introduce the LANDLOCK_ACCESS_FS_IOCTL right, which restricts the use
> of ioctl(2) on file descriptors.
> 
> We attach the LANDLOCK_ACCESS_FS_IOCTL right to opened file
> descriptors, as we already do for LANDLOCK_ACCESS_FS_TRUNCATE.
> 
> I believe that this approach works for the majority of use cases, and
> offers a good trade-off between Landlock API and implementation
> complexity and flexibility when the feature is used.
> 
> Notable implications of this approach
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> * Existing inherited file descriptors stay unaffected
>    when a program enables Landlock.
> 
>    This means in particular that in common scenarios,
>    the terminal's ioctls (ioctl_tty(2)) continue to work.
> 
> * ioctl(2) continues to be available for file descriptors acquired
>    through means other than open(2).  Example: Network sockets.
> 
> Examples
> ~~~~~~~~
> 
> Starting a sandboxed shell from $HOME with samples/landlock/sandboxer:
> 
>    LL_FS_RO=/ LL_FS_RW=. ./sandboxer /bin/bash
> 
> The LANDLOCK_ACCESS_FS_IOCTL right is part of the "read-write" rights
> here, so we expect that newly opened files outside of $HOME don't work
> with ioctl(2).
> 
>    * "stty" works: It probes terminal properties
> 
>    * "stty </dev/tty" fails: /dev/tty can be reopened, but the ioctl is
>      denied.
> 
>    * "eject" fails: ioctls to use CD-ROM drive are denied.
> 
>    * "ls /dev" works: It uses ioctl to get the terminal size for
>      columnar layout
> 
>    * The text editors "vim" and "mg" work.  (GNU Emacs fails because it
>      attempts to reopen /dev/tty.)
> 
> Alternatives considered: Allow-listing specific ioctl requests
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> It would be technically possible to keep an allow-list of ioctl
> requests and thereby control in more detail which ioctls should work.
> 
> I believe that this is not needed for the majority of use cases and
> that it is a reasonable trade-off to make here (but I'm happy to hear
> about counterexamples).  The reasoning is:
> 
> * Many programs do not need ioctl at all,
>    and denying all ioctl(2) requests OK for these.
> 
> * Other programs need ioctl, but only for the terminal FDs.
>    This is supported because these file descriptors are usually
>    inherited from the parent process - so the parent process gets to
>    control the ioctl(2) policy for them.
> 
> * Some programs need ioctl on specific files that they are opening
>    themselves.  They can allow-list these file paths for ioctl(2).
>    This makes the programs work, but it restricts a variety of other
>    ioctl requests which are otherwise possible through opening other
>    files.
> 
> Because the LANDLOCK_ACCESS_FS_IOCTL right is attached to the file
> descriptor, programs have flexible options to control which ioctl
> operations should work, without the implementation complexity of
> additional ioctl allow-lists in the kernel.
> 
> Finally, the proposed approach is simpler in implementation and has
> lower API complexity, but it does *not* preclude us from implementing
> per-ioctl-request allow lists later, if that turns out to be necessary
> at a later point.

I value this simplicity, but I'm also wondering about how much this 
allow/deny all IOCTLs approach would be useful in real case scenarios. ;)


> 
> Related Work
> ~~~~~~~~~~~~
> 
> OpenBSD's pledge(2) [1] restricts ioctl(2) independent of the file
> descriptor which is used.  The implementers maintain multiple
> allow-lists of predefined ioctl(2) operations required for different
> application domains such as "audio", "bpf", "tty" and "inet".
> 
> OpenBSD does not guarantee ABI backwards compatibility to the same
> extent as Linux does, so it's easier for them to update these lists in
> later versions.  It might not be a feasible approach for Linux though.
> 
> [1] https://man.openbsd.org/OpenBSD-7.3/pledge.2
> 
> Feedback I'm looking for
> ~~~~~~~~~~~~~~~~~~~~~~~~
> 
> Some specific points I would like to get your opinion on:
> 
> * Is this the right general approach to restricting ioctl(2)?
> 
>    It will probably be possible to find counter-examples where the
>    alternative (see below) is better.  I'd be interested in these, and
>    in how common they are, to understand whether we have picked the
>    right trade-off here.

In the long term, I'd like Landlock to be able to restrict a set of 
IOCTLs, taking into account the type of file/device. Being able to deny 
all IOCTLs might be useful and is much easier to implement though.


> 
> * Should we introduce a "landlock_fd_rights_limit()" syscall?
> 
>    We could potentially implement a system call for dropping the
>    LANDLOCK_ACCESS_FS_IOCTL and LANDLOCK_ACCESS_FS_TRUNCATE rights from
>    existing file descriptors (independent of the type of file
>    descriptor, even).
> 
>    Possible use cases would be to (a) restrict the rights on inherited
>    file descriptors like std{in,out,err} and to (b) restrict ioctl and
>    truncate operations on file descriptors that are not acquired
>    through open(2), such as network sockets.
> 
>    This would be similar to the cap_rights_limit(2) system call in
>    FreeBSD's Capsicum.
> 
>    This idea feels somewhat orthogonal to the ioctl patch, but it would
>    start to be more useful if the ioctl right exists.

This is indeed interesting, and that should be useful for the cases you 
explained. I think supporting IOCTLs is more important for now, but a 
new syscall to restrict FDs could be useful in the future. We should 
also think about batch operations on FD (see the close_range syscall), 
for instance to deny all IOCTLs on inherited or received FDs.


> 
> 
> Günther Noack (4):
>    landlock: Increment Landlock ABI version to 4
>    landlock: Add LANDLOCK_ACCESS_FS_IOCTL access right
>    selftests/landlock: Test ioctl support
>    samples/landlock: Add support for LANDLOCK_ACCESS_FS_IOCTL
> 
>   include/uapi/linux/landlock.h                | 19 ++++--
>   samples/landlock/sandboxer.c                 | 12 +++-
>   security/landlock/fs.c                       | 20 +++++-
>   security/landlock/limits.h                   |  2 +-
>   security/landlock/syscalls.c                 |  2 +-
>   tools/testing/selftests/landlock/base_test.c |  2 +-
>   tools/testing/selftests/landlock/fs_test.c   | 67 +++++++++++++++++++-
>   7 files changed, 107 insertions(+), 17 deletions(-)
> 
> 
> base-commit: 457391b0380335d5e9a5babdec90ac53928b23b4



More information about the Linux-security-module-archive mailing list