[RFC 0/4] Landlock: ioctl support

Mickaël Salaün mic at digikod.net
Mon Jun 19 18:57:40 UTC 2023


On 19/06/2023 18:21, Günther Noack wrote:
> Hello Mickaël!
> 
> On Sat, Jun 17, 2023 at 11:47:55AM +0200, Mickaël Salaün wrote:
>> This subject is not easy, but I think we're reaching a consensus (see my
>> 3-steps proposal plan below). I answered your questions about the (complex)
>> interface I proposed, but we should focus on the first step now (your
>> initial proposal) and get back to the other steps later in another email
>> thread.
> 
> Thanks for the review!
> 
> 
>> On 10/05/2023 21:21, Günther Noack wrote:
>>> [...]
>>> Some specific things I don't understand well are:
>>> [...]
> 
> Thanks, this all make sense now. 👍
> 
> 
>>>     Would it not be a more orthogonal API if the "file selection" part
>>>     of the Landlock API and the "policy adding" part for these selected
>>>     files were independent of each other?  Then the .device and
>>>     .file_type selection scheme could be used for the existing policies
>>>     as well?
>>
>> Both approaches have pros and cons. I propose a new incremental approach
>> below that starts with the simple case where there is no direct links
>> between different rule types (only the third step add that).
>>
>>>
>>> * When restricting by dev_t (major and minor number), aren't there use
>>>     cases where a system might have 256 CDROM drives, and you'd need to
>>>     allow-list all of these minor number combinations?
>>
>> Indeed, we should be able to just ignore device minors.
> 
> They device numbers are listed in
> https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
> 
> Some major numbers are grab-bags of miscellaneous devices, for example
> major numbers 10 and 13; 13/0-31 (maj/min) are joysticks, whereas
> 13/32-62) are mice.
> 
> Maybe this can be specified as ranges on dev_t, so that it would be
> possible to specify 13/32-62, without matching the joysticks too?

Indeed, being able to specifying a range would be useful. I'm not sure 
about the whole dev_t range or only a minor range though. From a 
semantic POV, there is no links between majors.

> 
> 
>>> I think that it might be a feasible approach to start with the
>>> LANDLOCK_ACCESS_FS_IOCTL approach and then look at its usage to
>>> understand whether we see a significant number of programs whose
>>> sandboxes are too coarse because of this.
>>>
>>> If more fine-granular control is needed, we can still put the other
>>> approach on top, and the additional complexity from
>>> LANDLOCK_ACCESS_FS_IOCTL that we have to support is not that
>>> dramatically high.
>>>
>>> [...]
>>
>> I agree that IOCTLs are a security risk and that we should propose a simple
>> solution short-term, and maybe a more complete one long-term.
>>
>> The main issue with a unique IOCTL access right for a file hierarchy is that
>> we may not know which device/driver will be the target/server, and hence if
>> we need to allow some IOCTL for regular files (e.g., fscrypt), we might end
>> up allowing all IOCTLs.
>>
>> Here is a plan to incrementally develop a fine-grained IOCTL access control
>> in 3 steps:
>>
>> 1/ Add a simple IOCTL access right for path_beneath: what you initially
>> proposed. For systems that already configure nodev mount points, it could be
>> even more useful (e.g., safely allow IOCTL on /home for fscrypt, and
>> specific /dev files otherwise).
> 
> Ack, I'll continue on that implementation then. 👍
> 
> 
>> 2/ Create a new type of rule to identify file/device type:
>> struct landlock_inode_type_attr {
>>      __u64 allowed_access; /* same as for path_beneath */
>>      __u64 flags; /* bit 0: ignores device minor */
>>      dev_t device; /* same as stat's st_rdev */
>>      __u16 file_type; /* same as stat's st_mode & S_IFMT */
>> };
>>
>> We'll probably need to differentiate the handled accesses for path_beneath
>> rules and those for inode_type rules to be more useful.
>>
>> One issue with this type of rule is that it could be used as an oracle to
>> bypass stat restrictions. We could check if such (virtual) action is allowed
>> without the current domain though.
>>
>>
>> 3/ Add a new type of rule to match IOCTL commands, with a mechanism to tie
>> this to inode_type rules (because a command ID is relative to a file
>> type/device), and potentially the same mechanism to tie inode_type rules to
>> path_beneath rules.
>>
>>
>> Each of this step can be implemented one after the other, and each one is
>> valuable. What do you think?
> 
> I think it is a good idea to do it in multiple steps,
> as I also believe that step 1) already provides value on its own.
> 
> To make sure we are on the same page, let me paraphrase my understanding here:
> 
> 1) is what I already sent as RFC, with tests and documentation etc.
> 
>     With 1), callers could allow and deny ioctls on newly opened files,
>     independent of file path.

Yes

> 
> 2) makes dev_t and file_type predicates which can be used to limit
>     file accesses on already opened files.
> 
>     With 2), callers could allow and deny ioctls and other operations
>     also on files which are already opened before enablement, such
>     as the TTY FD inherited from the parent process.

This is not on already opened files, it would be a complementary rule 
type to path_beneath ones: if a rule with dev_t/file_type matches and 
allowed action FOO, and a path_beneath rules matches and also allowed 
action FOO, then FOO is allowed.

However, this could be extended in the future (with a new dedicated 
flag) to also support already-opened files.


>     
> 3) would make it possible to restrict individual ioctl commands,
>     depending on the dev_t, the file_type, and possibly the path.

Yes. I think we could even only extend the "landlock_inode_attr" struct 
to add an ioctl_command field, and potentially others too. I think it 
would make sense because IOCTLs are tied to a specific device/file type 
(or even the underlying filesystem).

In fact, we can split the third step in two:
3/ Extend landlock_inode_attr with ioctl_command.
4/ Define an inode_category field that contain an arbitrary number which 
can be match against a path_beneath rule (with a bitmask to be able to 
match others too).

Other steps could allow to match landlock_inode_attr with FDs, add a 
landlock_fd_range_attr…


> 
> Is that accurate?
> 
> In 2) and 3), I'm still contemplating a bit whether we have
> alternative approaches, but I believe in any case, it's clear that
> they can be built on top of each other without much overhead, and for
> many programs, outright denying ioctl on newly opened files with 1)
> might be the most straightforward approach which already delivers
> value.

Right

> 
> Some notes which I collected on the side:
> 
> * I'm still worried about the already-opened file descriptors like the
>    TTY, through which it can be easy to escape a sandbox
>    (c.f. https://nvd.nist.gov/vuln/detail/CVE-2017-5226) - I would like
>    to have a solution for that.  Step (2) would fix it, but maybe I can
>    find a workaround to at least detect the problem in step (1)
>    already.

Yes, good example, Landlock should be able to help with this issue 
thanks to the third step.


> 
> * I looked at OpenBSD's pledge and unveil.  In OpenBSD, the ioctl
>    policy is based on the properties of an already opened file.  They
>    have hardcoded their ioctl logic in the kernel, which would be a
>    mistake to do in Linux due to stronger backwards compatibility
>    guarantees.  OpenBSD is making the allow/deny decisions based on
>    device major/minor numbers and file type (usually block/char
>    device).
> 
>    I think it is noteworthy that OpenBSD does *not* use the file path
>    to make that decision.

Yes, we could have the same behavior by only using the rule type added 
with the second step, but we could easily improve that by combining a 
path_beneath rule thanks to the third step.


> 
>    I wonder whether we should replicate that in steps (2) and (3).  It
>    would make for a simpler, more orthogonal API, where the ioctl
>    policy would become a property of the task, and it would be enforced
>    independently of the existing path-based logic.  When callers
>    combine that with the overall ioctl check from (1), it might be
>    flexible enough for practical purposes.

Yes, this will be possible with the four steps.


> 
> * We should not rely on the way that ioctl request numbers are
>    structured, because it is used inconsistently.  More background:
>    https://lwn.net/Articles/428140/ (Thank you for pointing out this
>    article, Jeff!)

Definitely, but the user space libraries might help with that. ;)


> 
>>>>> * Should we introduce a "landlock_fd_rights_limit()" syscall?
>>>> [...]
>>>>
>>>> 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.
>>>
>>> Hm, you mean a landlock_fd_rights_limit_range() syscall to limit the
>>> rights for an entire range of FDs?
>>>
>>> I have to admit, I'm not familiar with the real-life use cases of
>>> close_range().  In most programs I work with, it's difficult to reason
>>> about their ordering once the program has really started to run. So I
>>> imagine that close_range() is mostly used to "sanitize" the open file
>>> descriptors at the start of main(), and you have a similar use case in
>>> mind for this one as well?
>>>
>>> If it's just about closing the range from 0 to 2, I'm not sure it's
>>> worth adding a custom syscall. :)
>>
>> The advantage of this kind of range is to efficiently manage all potential
>> FDs, and the main use case is to close (or change, see the flags) everything
>> *except" 0-2 (i.e. 3-~0), and then avoid a lot of (potentially useless)
>> syscalls.
>>
>> The Landlock interface doesn't need to be a syscall. We could just add a new
>> rule type which could take a FD range and restrict them when calling
>> landlock_restrict_self(). Something like this:
>> struct landlock_fd_attr {
>>      __u64 allowed_access;
>>      __u32 first;
>>      __u32 last;
>> }
> 
> Ack, I see it a bit better. Let's discuss this topic separately.
> 
> —Günther
> 



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