Re: [PATCH v2 1/4] Landlock: Add signal control
From: Jann Horn <jannh@google.com>
Date: 2024-08-06 22:56:25
Also in:
linux-security-module, lkml
Hi! On Wed, Aug 7, 2024 at 12:00 AM Tahera Fahimi [off-list ref] wrote:
On Tue, Aug 06, 2024 at 08:56:15PM +0200, Jann Horn wrote:quoted
On Tue, Aug 6, 2024 at 8:11 PM Tahera Fahimi [off-list ref] wrote:quoted
+ if (is_scoped) + return 0; + + return -EPERM; +} + +static int hook_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int signum) +{ + bool is_scoped; + const struct landlock_ruleset *dom, *target_dom; + struct task_struct *result = get_pid_task(fown->pid, fown->pid_type);I'm not an expert on how the fowner stuff works, but I think this will probably give you "result = NULL" if the file owner PID has already exited, and then the following landlock_get_task_domain() would probably crash? But I'm not entirely sure about how this works.I considered since the file structure can always be obtained, then the file owner PID always exist.
I think the file owner "struct pid" always exists here; but I think
the PID does not necessarily point to a task_struct.
I think you can have a scenario where userspace does something like this:
int fd = <open some file descriptor somehow>;
fcntl(fd, F_SETOWN, <PID of some other process>);
pid_t child_pid = fork();
if (child_pid == 0) {
/* this executes in the child process */
sleep(5);
<do something on the fd that triggers send_sigio() or send_sigurg()>
sleep(5);
exit(0);
}
/* this continues executing in the parent process */
exit(0);
At the fcntl(fd, F_SETOWN, ...) call, a reference-counted reference to
the "struct pid" of the parent process will be stored in the file
(this happens in "f_modown", which increments the reference count of
the "struct pid" using "get_pid(...)"). But when the parent process
exits, the pointer from the parent's "struct pid" to the parent's
"task_struct" is removed, and the "task_struct" will eventually be
deleted from memory.
So when, at a later time, something triggers send_sigio() or
send_sigurg() and enters this LSM hook, I think
"get_pid_task(fown->pid, fown->pid_type)" can return NULL.
I can check if we can use the credentials stored in struct file instead.
(sort of relevant: The manpage https://man7.org/linux/man-pages/man2/fcntl.2.html says "Note: The F_SETOWN operation records the caller's credentials at the time of the fcntl() call, and it is these saved credentials that are used for the permission checks.") You mean the credentials in the "f_cred" member of "struct file", right? If so: I don't think Landlock can use the existing credentials stored in file->f_cred for this. Consider the following scenario: 1. A process (with no landlock restrictions so far) opens a file. At this point, the caller's credentials (which indicate no landlock restrictions) are recorded in file->f_cred. 2. The process installs a landlock filter that restricts the ability to send signals. (This changes the credentials pointer of the process to a new set of credentials that points to the new landlock domain.) 3. Malicious code starts executing in the sandboxed process. 4. The malicious code uses F_SETOWN on the already-open file. 5. Something causes sending of a signal via send_sigio() or send_sigurg() on this file. In this scenario, if the LSM hook used the landlock restrictions attached to file->f_cred, it would think the operation is not filtered by any landlock domain.
quoted
I think the intended way to use this hook would be to instead use the "file_set_fowner" hook to record the owning domain (though the setup for that is going to be kind of a pain...), see the Smack and SELinux definitions of that hook. Or alternatively maybe it would be even nicer to change the fown_struct to record a cred* instead of a uid and euid and then use the domain from those credentials for this hook... I'm not sure which of those would be easier.Because Landlock does not use any security blob for this purpose, I am not sure how to record the owner's doamin.
I think landlock already has a landlock_file_security blob attached to "struct file" that you could use to store an extra landlock domain pointer for this, kinda like the "fown_sid" in SELinux? But doing this for landlock would be a little more annoying because you'd have to store a reference-counted pointer to the landlock domain in the landlock_file_security blob, so you'd have to make sure to also decrement the reference count when the file is freed (with the file_free_security hook), and you'd have to use locking (or atomic operations) to make sure that two concurrent file_set_fowner calls don't race in a dangerous way... So I think it's fairly straightforward, but it would be kinda ugly.
The alternative way looks nice.
Yeah, I agree that it looks nicer. (If you decide to implement it by refactoring the fown_struct, it would be a good idea to make that a separate patch in your patch series - partly because that way, the maintainers of that fs/fcntl.c code can review just the refactoring, and partly just because splitting logical changes into separate patches makes it easier to review the individual patches.)
quoted
quoted
+ /* rcu is already locked! */ + dom = landlock_get_task_domain(result); + target_dom = landlock_get_task_domain(tsk); + is_scoped = domain_IPC_scope(dom, target_dom, LANDLOCK_SCOPED_SIGNAL); + put_task_struct(result); + if (is_scoped) + return 0; + return -EPERM; +}