NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
kern/60756: compat_linux inotify(2): TOCTOU and locking shenanigans
>Number: 60756
>Category: kern
>Synopsis: compat_linux inotify(2): TOCTOU and locking shenanigans
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sun Sep 20 13:25:01 +0000 2026
>Originator: Taylor R Campbell
>Release: current, 11, 10, ...
>Organization:
The CompatBSD Inotification, Inc.
>Environment:
>Description:
inotify_readdir sometimes runs with the vnode lock, and
sometimes unlocks and relocks the interlock, which strikes
me as incoherent and likely fundamentally buggy:
790 /* XXX: should pass whether to lock or not */
791 if (needs_lock)
792 vn_lock(vp, LK_SHARED | LK_RETRY);
793 else
794 /*
795 * XXX We need to temporarily drop v_interlock because
796 * it may be temporarily acquired by biowait().
797 */
798 mutex_exit(vp->v_interlock);
799 KASSERT(!mutex_owned(vp->v_interlock));
800 error = VOP_READDIR(vp, &uio, fp->f_cred, &eofflag, NULL, NULL);
801 if (needs_lock)
802 VOP_UNLOCK(vp);
803 else
804 mutex_enter(vp->v_interlock);
https://nxr.netbsd.org/xref/src/sys/compat/linux/common/linux_inotify.c?r=1.8#761
It is not clear to me whether in the needs_lock=false case it
actually holds the vnode lock. This should be asserted.
The needs_lock=true case used by get_inotify_dir_entries
creates a TOCTOU issue: in one pass, it it counts the number of
entries with inotify_readdir; in a second pass, it copies them
into an array of that many entries -- but the number of entries
may have changed between the first pass and the second pass
(with some large entries being split into multiple small ones),
possibly leading the inner while loop in the second pass to
walk off the end of the array.
842 mutex_enter(&wp->f_lock);
843 wp->f_offset = 0;
844 mutex_exit(&wp->f_lock);
845 decount = 0;
846 for (;;) {
847 error = inotify_readdir(wp, &de, &done, needs_lock);
848 if (error != 0)
849 goto leave;
850 if (done == 0)
851 break;
852
853 currdep = &de;
854 while ((char *)currdep < ((char *)&de) + done) {
855 decount++;
856 currdep = _DIRENT_NEXT(currdep);
857 }
858 }
859
860 idep = kmem_zalloc(INOTIFY_DIR_ENTRIES_SIZE(decount), KM_SLEEP);
861 idep->ide_count = decount;
862
863 mutex_enter(&wp->f_lock);
864 wp->f_offset = 0;
865 mutex_exit(&wp->f_lock);
866 for (i = 0; i < decount;) {
867 error = inotify_readdir(wp, &de, &done, needs_lock);
868 if (error != 0 || done == 0) {
869 kmem_free(idep, INOTIFY_DIR_ENTRIES_SIZE(decount));
870 idep = NULL;
871 goto leave;
872 }
873
874 currdep = &de;
875 while ((char *)currdep < ((char *)&de) + done) {
876 idep->ide_entries[i].fileno = currdep->d_fileno;
877 strcpy(idep->ide_entries[i].name, currdep->d_name);
878
879 currdep = _DIRENT_NEXT(currdep);
880 i++;
881 }
882 }
https://nxr.netbsd.org/xref/src/sys/compat/linux/common/linux_inotify.c?r=1.8#842
>How-To-Repeat:
code inspection
>Fix:
1. At least bound the inner while loop in the second pass of
get_inotify_dir_entries.
2. Fix the locking shenanigans.
3. Nix strcpy while here; use strlcpy, strncpy, or copystr
instead, depending on the nature of the input and output
buffers.
Home |
Main Index |
Thread Index |
Old Index