Source-Changes-D archive

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

Re: CVS commit: src/sys/compat/linux/common



> Module Name:    src
> Committed By:   christos
> Date:           Thu Aug 24 19:51:24 UTC 2023
> 
> Modified Files:
>         src/sys/compat/linux/common: linux_inotify.c
> 
> Log Message:
> fix a locking bug (Theodore Preduta)
> 
>         if (needs_lock)
>                 vn_lock(vp, LK_SHARED | LK_RETRY);
> +       else
> +               /*
> +                * XXX We need to temprarily drop v_interlock because
> +                * it may be temporarily acquired by biowait().
> +                */
> +               mutex_exit(vp->v_interlock);
> +       KASSERT(!mutex_owned(vp->v_interlock));
>         error = VOP_READDIR(vp, &uio, fp->f_cred, &eofflag, NULL, NULL);
>         if (needs_lock)
>                 VOP_UNLOCK(vp);
> +       else
> +               mutex_enter(vp->v_interlock);

This looks questionable.

1. Why is v_interlock held in the first place?

2. Why is it safe to release v_interlock here, if the caller holds it?

3. What is the lock order for all the locks involved?

I see that inotify_readdir via get_inotify_dir_entries has two call
sites, leading to the following lock orders:

- linux_sys_inotify_add_watch
  -> mutex_enter(&ifd->ifd_lock)
  -> get_inotify_dir_entries(..., needs_lock=true)
     -> inotify_readdir(..., needs_lock=true)
        -> vn_lock(vp)
        -> VOP_READDIR(vp)

- handle_write
  -> mutex_enter(&ifd->ifd_lock)
  -> get_inotify_dir_entries(..., needs_lock=false)
     -> inotify_readdir(..., needs_lock=true)
        -> mutex_exit(vp->v_interlock)
        -> VOP_READDIR(vp)

Since VOP_READDIR requires vp to be locked, I can infer that the
handle_write caller must already hold vp locked.  But that means that
we have ifd_lock -> vnode lock in one path, and vnode lock -> ifd_lock
in another path, which is forbidden (unless there's some reason we can
prove these paths never happen concurrently).

I'm also suspicious of logic like this:

        mutex_enter(&fp->f_lock);
        uio.uio_offset = fp->f_offset;
        mutex_exit(&fp->f_lock);
...
        mutex_enter(&fp->f_lock);
        fp->f_offset = uio.uio_offset;
        mutex_exit(&fp->f_lock);

If fp->f_offset can change concurrently while f_lock is released, this
looks like a problem.  But if it can't, why do we need the lock at
all?  I suspect this really does need a lock over the whole
transaction (maybe fp->f_lock, maybe something else), which is not
released while I/O is happening -- possibly not with mutex(9) but
something interruptible instead.


Home | Main Index | Thread Index | Old Index