[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAG48ez2OmOdMo7TCn4M8tSPSdXVwMDty6fMsHc9Q+NFQ-sfBUA@mail.gmail.com>
Date: Wed, 7 Aug 2024 00:55:46 +0200
From: Jann Horn <jannh@...gle.com>
To: Tahera Fahimi <fahimitahera@...il.com>
Cc: outreachy@...ts.linux.dev, mic@...ikod.net, gnoack@...gle.com,
paul@...l-moore.com, jmorris@...ei.org, serge@...lyn.com,
linux-security-module@...r.kernel.org, linux-kernel@...r.kernel.org,
bjorn3_gh@...tonmail.com, netdev@...r.kernel.org
Subject: Re: [PATCH v2 1/4] Landlock: Add signal control
Hi!
On Wed, Aug 7, 2024 at 12:00 AM Tahera Fahimi <fahimitahera@...il.com> wrote:
> On Tue, Aug 06, 2024 at 08:56:15PM +0200, Jann Horn wrote:
> > On Tue, Aug 6, 2024 at 8:11 PM Tahera Fahimi <fahimitahera@...il.com> wrote:
> > > + 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.
> > 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.)
> > > + /* 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;
> > > +}
Powered by blists - more mailing lists