Page MenuHomeFreeBSD

tty: do not recurse on ttydev_close()
ClosedPublic

Authored by kib on Fri, Aug 7, 7:56 PM.
Tags
None
Referenced Files
Unknown Object (File)
Tue, Sep 1, 10:35 PM
Unknown Object (File)
Fri, Aug 28, 11:00 AM
Unknown Object (File)
Thu, Aug 27, 4:02 PM
Unknown Object (File)
Thu, Aug 27, 4:00 PM
Unknown Object (File)
Thu, Aug 27, 10:06 AM
Unknown Object (File)
Thu, Aug 27, 10:01 AM
Unknown Object (File)
Thu, Aug 27, 6:11 AM
Unknown Object (File)
Tue, Aug 25, 8:49 PM
Subscribers

Details

Summary
When the terminal cdev is closed due to revoke, ttydev_close() destroys
t_inpoll and t_outpoll selinfos.  Since corresponding knotes reference
files pointing to the same tty cdev, it fdrop()s them.  But then the
VOP_CLOSE() call would recurse into the ttydev_close() for the same tty.
More, because the devfs vnode is already doomed, each close call gets
the FREVOKE flag set.

As result, the kernel is recursing as deep into the ttydev_close() as
there are opened files referencing the same tty, which have the knotes
installed.  Basically, the recursion level is controlled by userspace.

Prevent it by marking the tty that is handled by ttydev_close(), with
the TF_INDEVCLOSE flag.  Do nothing in ttydev_close() when the flag is
already set, avoiding recursion.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

I'll look at adding a test for this scenario, and will take a closer look at this in a few hours

I guess this is Fixes: acd5638e268a6?

sys/kern/tty.c
402

Are we leaking the flag here?

kib marked an inline comment as done.

Clear TF_INDEVCLOSE in one more place.

Writing a test for this seems to be a little harder than I anticipated, but I'm also wondering now if deleting the knotes was infact the right thing to do. From revoke(2):

 [...].  Subsequent operations on any such
descriptors fail, with the exceptions that a read() from a character
device file which has been revoked returns a count of zero (end of file),
and a close() system call will succeed.  [...]

I think the philosophically correct thing to do would be the exact same thing we do for a pipe whose other end has been closed: surface the tty as readable (for all of select/poll/kqueue) so that the consumer can observe the EOF/revocation event and cleanup accordingly. That's kind of challenging, though, so maybe this is the least bad option.

Writing a test for this seems to be a little harder than I anticipated, but I'm also wondering now if deleting the knotes was infact the right thing to do. From revoke(2):

 [...].  Subsequent operations on any such
descriptors fail, with the exceptions that a read() from a character
device file which has been revoked returns a count of zero (end of file),
and a close() system call will succeed.  [...]

I think the philosophically correct thing to do would be the exact same thing we do for a pipe whose other end has been closed: surface the tty as readable (for all of select/poll/kqueue) so that the consumer can observe the EOF/revocation event and cleanup accordingly. That's kind of challenging, though, so maybe this is the least bad option.

Yes, it would require either making struct tty type-stable, or even more complicated, refcount it from the knotes, I do not think it is worth the trouble.

sys/kern/tty.c
234

What's the purpose of the flag parameter? Isn't it equivalent to (tp->t_flags & TF_INDEVCLOSE) != 0? Same question about tty_rel_free().

kib marked an inline comment as done.Wed, Aug 12, 10:10 PM
kib added inline comments.
sys/kern/tty.c
234

I do not think that it is equivalent. Consider ttydev_open(): after the tty is unlocked in ttydev_close() for fdrop() inside knlist_delete(), other thread might call ttydev_open() and observe _our_ flag.

Similarly, tty_rel_session() and other callers of tty_rel_free() might call while we ttydev_close() dropped the tty lock, and these calls must not clear our flag.

This revision is now accepted and ready to land.Thu, Aug 13, 2:25 PM
This revision was automatically updated to reflect the committed changes.
kib marked an inline comment as done.

I guess this is Fixes: acd5638e268a6?

it's not clear to me off-hand but how did acd5638e268a6 cause the recursion behavior that this review fixes?

hindsight, I probably should have bisected the commit that acd5638e268a6 fixed

In D58706#1349924, @rew wrote:

I guess this is Fixes: acd5638e268a6?

it's not clear to me off-hand but how did acd5638e268a6 cause the recursion behavior that this review fixes?

hindsight, I probably should have bisected the commit that acd5638e268a6 fixed

knlist_delete() calls fddrop() for each knote on the list, which might recurse into ttydev_close() if the fd was the last reference on the different file opened on the same tty. The commit message provides more elaborate explanation.