Re: git: 40a42785dbba - main - fcntl(F_SETFL): only allow one thread to perform F_SETFL
Date: Tue, 23 Sep 2025 15:34:24 UTC
On 9/22/25 17:05, Konstantin Belousov wrote: > On Mon, Sep 22, 2025 at 09:50:09AM +0100, John Baldwin wrote: >> On 9/22/25 04:41, John Baldwin wrote: >>> On 9/19/25 10:19, Konstantin Belousov wrote: >>>> The branch main has been updated by kib: >>>> >>>> URL: https://cgit.FreeBSD.org/src/commit/?id=40a42785dbba93cc5196178fc49d340c1a89cabe >>>> >>>> commit 40a42785dbba93cc5196178fc49d340c1a89cabe >>>> Author: Konstantin Belousov <kib@FreeBSD.org> >>>> AuthorDate: 2025-09-11 10:05:04 +0000 >>>> Commit: Konstantin Belousov <kib@FreeBSD.org> >>>> CommitDate: 2025-09-19 14:19:13 +0000 >>>> >>>> fcntl(F_SETFL): only allow one thread to perform F_SETFL >>>> Use f_vflags file locking for this. >>>> Allowing more than one thread handling F_SETFL might cause de-sync >>>> between real driver state and flags. >>>> Reviewed by: markj >>>> Tested by: pho >>>> Sponsored by: The FreeBSD Foundation >>>> MFC after: 2 weeks >>>> Differential revision: https://reviews.freebsd.org/D52487 >>> >>> Thanks for fixing this. I still slightly worry that "home-grown" locks >>> aren't visible to WITNESS and it's checking. >>> >>> I was also expecting this to require more changes, but apparently if a >>> process directly invokes FIONBIO on a file descriptor, f_flags isn't >>> updated currently. I wonder if that is a bug. (Similarly for FIOASYNC.) >>> >>> Oh, we do handle that, but poorly. We don't revert on errors, and this >>> should be updated to use fsetfl_lock now I think: >>> >>> kern_ioctl(...) >>> { >>> ... >>> switch (com) { >>> ... >>> case FIONBIO: >>> if ((tmp = *(int *)data)) >>> atomic_set_int(&fp->f_flag, FNONBLOCK); >>> else >>> atomic_clear_int(&fp->f_flag, FNONBLOCK); >>> data = (void *)&tmp; >>> break; >>> case FIOASYNC: >>> if ((tmp = *(int *)data)) >>> atomic_set_int(&fp->f_flag, FASYNC); >>> else >>> atomic_clear_int(&fp->f_flag, FASYNC); >>> data = (void *)&tmp; >>> break; >>> } >>> >>> error = fo_ioctl(fp, com, data, td->td_ucred, td); >>> out: >>> >>> I think instead we want something like: >>> >>> int f_flag; >>> >>> switch (com) { >>> ... >>> case FIONBIO: >>> case FIOASYNC: >>> fsetfl_lock(fp); >>> tmp = *(int *)data; >>> f_flag = com == FIONBIO ? FNONBLOCK : FASYNC; >>> if ((fp->f_flag & f_flag) != 0) { >> >> This is wrong, should be: >> >> if (((fp->f_flag & f_flag) != 0) == (tmp != 0)) >> >>> fsetfl_unlock(fp); >>> goto out; >>> } >>> data = (void *)&tmp; >>> break; >>> } >>> >>> error = fo_ioctl(fp, com, data, td->td_ucred, td); >>> switch (com) { >>> ... >>> case FIONBIO: >>> case FIOASYNC: >>> if (error == 0) { >>> if (tmp) >> >> Probably 'if (tmp != 0)' >> >>> atomic_set_int(&fp->f_flag, f_flag); >>> else >>> atomic_clear_int(&fp->f_flag, f_flag); >>> } >>> fsetfl_unlock(fp); >>> break; >>> } >>> >>> out: >>> >>> This only updates the flag if the underlying ioctl succeeds, and it also >>> avoids invoking the underlying ioctl if the flag is already in the correct\ >>> state. > > So will you handle this? I can. Do you think this is a good idea? :) -- John Baldwin