Page MenuHomeFreeBSD

devfs: Fix FLASTCLOSE handling
Needs ReviewPublic

Authored by markj on Wed, Sep 16, 12:06 PM.
Tags
None
Referenced Files
F174915833: D59725.diff
Tue, Oct 6, 11:02 PM
F174900716: D59725.id187094.diff
Tue, Oct 6, 8:52 PM
Unknown Object (File)
Mon, Oct 5, 10:43 PM
Unknown Object (File)
Mon, Oct 5, 9:54 PM
Unknown Object (File)
Sun, Oct 4, 2:49 PM
Unknown Object (File)
Thu, Oct 1, 4:08 AM
Unknown Object (File)
Thu, Oct 1, 3:57 AM
Unknown Object (File)
Tue, Sep 29, 10:15 PM
Subscribers
This revision needs review, but there are no reviewers specified.

Details

Reviewers
None
Summary

devfs uses the device's usecount to determine how many times a device
has been opened. If it transitions 1->0, then we should invoke d_close
(assuming D_TRACKCLOSE isn't set).

But, we bump the usecount before calling d_open. Suppose a thread races
to open a device while a different thread is closing it. The first
thread may bump usecount 1->2, and then the closing thread decrements it
2->1. Then, if the open fails, we will decrement again 1->0 but d_close
is not invoked at all.

Fix the problem by incrementing usecount only after a succesful open. I
believe this does not introduce any new races: the usecount is only used
to decide whether to revoke or not, and the session holds an additional
ref via devfs_ctty_ref(). Note however that d_close(FLASTCLOSE) can
now race with d_open().

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 77058
Build 73941: arc lint + arc unit

Event Timeline

It fixes the failed open race (from the mentioned issue) but I was able to trigger the following with an LLM mock:

At the new devfs_usecount_add() near line 1329:

  1. A already has the device open; usecount is 1.
  2. B’s d_open() succeeds, but B has not reacquired the vnode lock or incremented usecount.
  3. A closes: devfs observes the last reference and calls d_close().
  4. B increments usecount and returns success—after device teardown.

TTY is a concrete affected consumer: ttydev_close() (sys/kern/tty.c:378) clears its opened flags and releases queues and subsequent operations fail the ttydev_enter() (sys/kern/tty.c:221) check with ENXIO. With the old ordering, B already contributed a reference and A would skip d_close().

Serialize d_open and d_close when a FLASTCLOSE close is in progress.

This is unsatisfying, but I can't see any other way to fix the race.
The right solution is to handle this problem in drivers and make
D_TRACKCLOSE behaviour universal.

This now handles the close-first ordering, but the open-first ordering from my example still appears possible. Once an opener passes the SI_LASTCLOSE check, nothing records its presence until after d_open returns and the vnode lock is reacquired. A last close can still run in that interval. We need protection in both directions checking SI_LASTCLOSE only before d_open does not establish mutual exclusion.