tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: closing pad(4) before audio(4) causes panic



> Date: Sun, 02 Feb 2020 14:25:12 +0900
> From: Tetsuya Isaki <isaki%pastel-flower.jp@localhost>
> 
> According to audio.c comment, vdevgone() calls close().  But it is
> not called actually.

vdevgone calls VOP_REVOKE on the vnode, which _used_ to call
audio_close, but it does essentially nothing for cloning file
descriptors as we have used since sys/dev/audio.c r1.302.

Instead of vdevgone,

- audiodetach must go through sc->sc_files and render them invalid,
  and

- audioread, audiowrite, &c. -- all the audio_fileops -- must be able
  to handle an invalid file gracefully by returning error.

But it's tricky because audioread and audiowrite need to get a handle
on the softc, without holding any locks, while audiodetach may be
racing to destroy the softc.

Here's one way you could do it, using pserialize(9) in
audioread/audiowrite to reliably tell whether the file is still valid,
and psref(9) to get a handle on the softc:

1. Add struct pserialize * and struct psref_target to struct
   audio_softc so we can passive references to it:

	struct audio_softc {
		...
		struct pserialize	*sc_psz;
		struct psref_target	sc_psref;
		...
	};

   (Create/destroy an audio psref class in MODULE_CMD_INIT/FINI, and
   then do pserialize_create/destroy and psref_target_init/destroy in
   audioattach/detach.)

2. In audioread, audiowrite, &c., get the sc and acquire a psref to it
   in a pserialize read section:

	static int
	audioread(struct file *fp, ...)
	{
		struct audio_file *file = fp->f_audioctx;
		struct audio_softc *sc;
		struct psref sc_ref;
		int error;
		int s;

		/* Block audiodetach while we acquire a reference.  */
		s = pserialize_read_enter();

		/* If audiodetach already ran, tough -- no more audio.  */
		if ((sc = atomic_load_relaxed(&file->sc)) == NULL) {
			pserialize_read_exit(s);
			return ENXIO;
		}

		/* Acquire a reference.  */
		psref_acquire(&sc_ref, &sc->sc_psref, audio_psref_class);

		/* Now sc won't go away until we drop the reference count.  */
		pserialize_read_exit(s);

		... use sc->sc_lock, do the read, &c. ...
		... make sure sc_dying prevents long waits ...

		/* Release the reference.  */
		psref_release(&sc_ref, &sc->sc_psref, audio_psref_class);

		return error;
	}

3. In audio_close, remove it from the list _only_ if it's still valid.

	audio_close(sc, file)
	{
		...
		mutex_enter(sc->sc_intr_lock);
		/* Detach races to remove us from the list too.  */
		if (file->sc) {
			KASSERT(sc == file->sc);
			SLIST_REMOVE(&sc->sc_files, file, audio_file, entry);
			file->sc = NULL; /* paranoia */
		}
		mutex_exit(sc->sc_intr_lock);
		...
	}

3. In audiodetach, you need to (a) prevent new users, (b) wait for
   existing users to drain, (c) free it.  So, after setting
   sc->sc_dying, and in the place of the logic that currently does
   vdevgone:

	audiodetach()
	{
		struct audio_file *file;
		...
		/* (a) Prevent new users.  */
		mutex_enter(sc->sc_intr_lock);
		while ((file = SLIST_FIRST(&sc->sc_files)) != NULL) {
			SLIST_REMOVE_HEAD(&sc->sc_files, entry);
			atomic_store_relaxed(&file->sc, NULL);
		}
		mutex_exit(sc->sc_intr_lock);

		/*
		 * (b) Wait for existing users to drain:
		 *     - pserialize_perform waits for all
		 *       pserialize_read sections on all CPUs; after
		 *       this, no more new psref_acquire can happen.
		 *     - psref_target_destroy waits for all extant
		 *       acquired psrefs to be psref_released.
		 */
		mutex_enter(sc->sc_lock);
		pserialize_perform(sc->sc_psz);
		mutex_exit(sc->sc_lock);
		psref_target_destroy(&sc->sc_psref, audio_psref_class);

		/*
		 * We are now guaranteed that there are no calls to
		 * audio fileops that hold sc, and any new calls with
		 * files that were for sc will fail.  Thus, we now
		 * have exclusive access to the softc.
		 */
		callout_destroy(...);
		kmem_free(...);
	}


Side notes after reviewing audio.c:

- You will need to find a way to ensure that audio_close can free all
  of its resources unconditionally: although it can technically return
  an error code, it can't fail to free resources; close is final and
  can never be retried.  The file descriptor will be freed by closef
  even if .fo_close returns an error code.

  If audio_close really really needs to hold the long-term exclusive
  lock (sc_exlock), then you need to provide a way to interrupt anyone
  holding the exclusive lock so that audio_close can proceed without
  blocking indefinitely.  But if there's any way to avoid taking the
  long-term exclusive lock at all, that would be better.

- kmem_alloc/free (or any other sleeping allocation -- for example,
  softint_establish/disestablish) is not allowed while holding a lock.
  It may work accidentally sometimes, but it's wrong for audio to do
  -- it can lead to deadlocks, uninterruptible blocking waits, &c.

- KASSERT(!mutex_owned(lock)) is not generally allowed.  If you're
  about to do mutex_enter(lock) anyway, just skip the kassert --
  mutex_enter will detect a recursive lock and panic itself.


Home | Main Index | Thread Index | Old Index