Thread (33 messages) 33 messages, 6 authors, 2018-09-25

Re: [PATCH v6 4/5] seccomp: add support for passing fds via USER_NOTIF

From: Jann Horn <jannh@google.com>
Date: 2018-09-21 20:46:45
Also in: lkml

On Fri, Sep 21, 2018 at 3:39 PM Tycho Andersen [off-list ref] wrote:
On Thu, Sep 20, 2018 at 07:18:45PM -0700, Andy Lutomirski wrote:
quoted
On Thu, Sep 20, 2018 at 4:42 PM Tycho Andersen [off-list ref] wrote:
quoted
On Wed, Sep 19, 2018 at 12:58:20PM -0700, Andy Lutomirski wrote:
quoted
On Wed, Sep 19, 2018 at 7:38 AM, Tycho Andersen [off-list ref] wrote:
quoted
On Wed, Sep 19, 2018 at 07:19:56AM -0700, Andy Lutomirski wrote:
quoted
Do we really allow non-“kill” signals to interrupt the whole process?  It might be the case that we don’t really need to clean up from signals if there’s a guarantee that the thread dies.
Yes, we do, because of this: https://lkml.org/lkml/2018/3/15/1122
I'm still not sure I see the problem.  Suppose I'm implementing a user
notifier for a nasty syscall like recvmsg().  If I'm the tracer, by
the time I decide to install an fd, I've committed to returning
something other than -EINTR, even if a non-fatal signal is sent before
I finish.  No rollback should be necessary.
I don't understand why this is true. Surely you could stop a handler
on receipt of a new signal, and have it do something else entirely?
I think you *could*, but I'm not sure why you would.  In general,
syscalls never execute signal handlers mid-syscall.  There is a very
small number of syscalls that use sys_restart_syscall(), but I don't
think any of them allocate fds, and I'm not sure we need or want to
support them with user notifiers.  The rest of the syscalls will, if
they're behaving correct, either do *something* (like reading some or
all of a buffer) and return success or they'll do nothing and return
-EINTR.  Or they return an -ERESTARTSYS variant.  And then, only
*after* the syscall logically returns (i.e. completely finishes
processing and puts its return code into the relevant register) will a
signal be delivered.  In other words, the case where something like
recv() gets interrupted but still returns a success code does not mean
that a signal handler was called and then recv() resumed.  It means
that recv() noticed the signal, stopped receiving, returned the number
of bytes read, and then allowed the signal to be delivered.

In the -ERESTARTSYS case, the syscall returns -ERESTARTSYS (or a
variant) and returns without doing anything.  But it returns in a
special case where, after the signal returns, the syscall will happen
again.

So, for user notifiers, I think that any sane handler that notices a
non-fatal signal will do one of these things:

 - Return -EINTR without changing any tracee state.

 - Return success, possibly without blocking as long as it would have
without the signal.

 - Return -ERESTARTSYS without changing any tracee state.

 - Kill the tracee.

None of these would involve backing out an fd that was already
installed.  I suppose another way of looking at this is that.

Although... now that I think about it, there are some special cases,
like socketpair().  Look for put_unused_fd().  So maybe we need to
expose get_unused_fd_flags() and put_unused_fd(), but I think that
these are exceptions and will be very uncommon in the context of
seccomp user notifiers.  (For example, socketpair() can be implemented
almost correctly without put_unused_fd().)
socketpair() is a good point. In particular, if we use this queuing
thing I've done above, then you can only ever send one fd, and you'll
need to send two here. So perhaps we really do need to do this as soon
as the tracer calls ioctl(), vs queuing and waiting.
quoted
Hmm.  This does mean that we need a test case for a user notifier
returning -ERESTARTSYS.  It should Just Work (tm), but those are
famous last words.

-ERESTARTSYS_RESTARTBLOCK is the case that I don't think we need to worry about.
quoted
quoted
In the (unlikely?) event that some tracer needs to be able to rollback
an fd installation to return -EINTR, a SECCOMP_NOTIF_CLOSE_FD
operation should be good enough, I think.  Or maybe PUT_FD can put -1
to delete an fd.
Yes, I think even with something like what I did below we'd need some
sort of REMOVE_FD option, because otherwise there's no way to change
your mind and send -EINTR without the fd you just PUT_FD'd.
I think we just want the operation to cover all the cases.  Let PUT_FD
take a source fd and a dest fd.  If the source fd is -1, the dest is
closed.  If the source is -1 and the dest is -1, return -EINVAL.  If
the dest is -1, allocate an fd.  If the dest is >= 0, work like
dup2().  (The latter could be necessary to emulate things like, say,
dup2 :))
...then if we're going to allow overwriting fds, we'd need to lift out
the logic from do_dup2 somewhere? Is this getting too complicated? :)
In particular if you end up allowing overwriting fds of a remote task,
please add a scary warning to the code that does that, informing the
reader that that's only safe because you know that the target task is
stopped outside syscall context, and that it would be a very bad idea
to just copypaste that code to somewhere else. If someone tried doing
that to a single-threaded task that's in the middle of a syscall, the
results would be interesting - and by "interesting", I mean
"use-after-free on a struct file".
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help