NetBSD-Bugs archive

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

Re: kern/60504: lfs_cleanerd can kernel crash on load in lfs_fcntl; lfs_sp == NULL



I'm a little puzzled by how this could possibly ever work.

I don't see anything taking the seglock in this _except_ lfs_segwrite
-- I don't think lfs_writer_enter, lfs_prelock, or lfs_flush_dirops
will do that:

   2021 	case LFCNRECLAIM:
   2022 		/*
   2023 		 * Flush dirops and write Ifile, allowing empty segments
   2024 		 * to be immediately reclaimed.
   2025 		 */
   2026 		lfs_writer_enter(fs, "pndirop");
   2027 		off = lfs_sb_getoffset(fs);
   2028 		lfs_prelock(fs, 0);
   2029 		lfs_flush_dirops(fs);
   2030 		LFS_CLEANERINFO(cip, fs, bp);
   2031 		oclean = lfs_ci_getclean(fs, cip);
   2032 		LFS_SYNC_CLEANERINFO(cip, fs, bp, 1);
   2033 		lfs_segwrite(ap->a_vp->v_mount, SEGM_CKP | SEGM_FORCE_CKP);
   2034 		/* Copy out write stats */
   2035 		if (ap != NULL && ap->a_data != NULL) {
   2036 			lws.direct = 0;
   2037 			lws.offset = lfs_btofsb(fs, fs->lfs_sp->bytes_written);
   2038 			*(struct lfs_write_stats *)ap->a_data = lws;
   2039 		}
   2040 		lfs_preunlock(fs);
   2041 		lfs_writer_leave(fs);

https://nxr.netbsd.org/xref/src/sys/ufs/lfs/lfs_vnops.c?r=1.353#2021

Inside lfs_segwrite, there is only one path out if the file system
isn't read-only -- these are the only two returns:

    542 int
    543 lfs_segwrite(struct mount *mp, int flags)
    544 {
...
    563 	if (fs->lfs_ronly)
    564 		return EROFS;
...
    588 	lfs_seglock(fs, flags | (do_ckp ? SEGM_CKP : 0));
    589 	sp = fs->lfs_sp;
...
    807 	lfs_segunlock(fs);
    808 
    809 	DLOG((DLOG_SEG, "  returning 0\n"));
    810 	return (0);
    811 }

https://nxr.netbsd.org/xref/src/sys/ufs/lfs/lfs_segment.c?r=1.308#539

So I think when we enter lfs_seglock from fcntl(LFCNRECLAIM), we are
guaranteed _not_ to hold the seglock, and thus the seglock depth from
line 588 to line 807 should be 1.  And in that case, lfs_segunlock
will free fs->lfs_sp and null it out:

    490 void
    491 lfs_segunlock(struct lfs *fs)
    492 {
...
    503 	if (fs->lfs_seglock == 1) {
...
    526 		if (!sync)
    527 			pool_put(&fs->lfs_segpool, sp);
    528 		fs->lfs_sp = NULL;
...
    569 		if (sync)
    570 			pool_put(&fs->lfs_segpool, sp);
...
    594 	} else {
    595 		--fs->lfs_seglock;
    596 		KASSERT(fs->lfs_seglock != 0);
    597 	}
    598 
    599 	lfs_preunlock(fs);
    600 }

https://nxr.netbsd.org/xref/src/sys/ufs/lfs/lfs_subr.c?r=1.110#486

So by the time lfs_segwrite returns, fs->lfs_sp should be null!

Now it looks like the LFCNRECLAIM code is racy: it's possible that
_another thread_ will set fs->lfs_sp to nonnull under the seglock, and
then LFCNRECLAIM might get lucky and read out a nonnull fs->lfs_sp by
chance.  But I expect that to be the exception, not the rule.

It looks like perseant@ recently added a null test here, in January --
although it is sensible to add this null test for other reasons, I
wonder whether it was prompted by the same crash you're describing?

Module Name:    src
Committed By:   perseant
Date:           Fri Jan 30 15:54:29 UTC 2026

Modified Files:
        src/sys/ufs/lfs: lfs_vnops.c

Log Message:
Check whether ap->a_data is NULL before using it.  Prevents a crash when
calling LCFNRECLAIM with a null stats pointer.


To generate a diff of this commit:
cvs rdiff -u -r1.352 -r1.353 src/sys/ufs/lfs/lfs_vnops.c

--- a/sys/ufs/lfs/lfs_vnops.c
+++ b/sys/ufs/lfs/lfs_vnops.c
...
@@ -2032,7 +2032,7 @@ lfs_fcntl(void *v)
 		LFS_SYNC_CLEANERINFO(cip, fs, bp, 1);
 		lfs_segwrite(ap->a_vp->v_mount, SEGM_CKP | SEGM_FORCE_CKP);
 		/* Copy out write stats */
-		if (ap != NULL) {
+		if (ap != NULL && ap->a_data != NULL) {
 			lws.direct = 0;
 			lws.offset = lfs_btofsb(fs, fs->lfs_sp->bytes_written);
 			*(struct lfs_write_stats *)ap->a_data = lws;

https://mail-index.netbsd.org/source-changes/2026/01/30/msg160457.html

Going back slightly further in January, it looks like LFCNRECLAIM
_used to_ hold the seglock across the logic where this crash happened:

Module Name:    src
Committed By:   perseant
Date:           Tue Jan 20 15:30:15 UTC 2026

Modified Files:
        src/sys/ufs/lfs: lfs_segment.c

Log Message:
Avoid a deadlock between vnode reclamation and lfs_writevnodes().  A vnode
being reclaimed will be in state VS_RECLAIMING, while it attemmpts to get the
segment lock.  lfs_writevnodes() holds the segment lock while traversing the
list of vnodes; so it must skip vnodes in the process of reclamation in order
to avoid a deadlock.


To generate a diff of this commit:
cvs rdiff -u -r1.306 -r1.307 src/sys/ufs/lfs/lfs_segment.c

--- a/sys/ufs/lfs/lfs_vnops.c
+++ b/sys/ufs/lfs/lfs_vnops.c
...
@@ -2025,20 +2025,19 @@ lfs_fcntl(void *v)
 		 */
 		lfs_writer_enter(fs, "pndirop");
 		off = lfs_sb_getoffset(fs);
-		lfs_seglock(fs, SEGM_FORCE_CKP | SEGM_CKP);
+		lfs_prelock(fs, 0);
 		lfs_flush_dirops(fs);
 		LFS_CLEANERINFO(cip, fs, bp);
 		oclean = lfs_ci_getclean(fs, cip);
 		LFS_SYNC_CLEANERINFO(cip, fs, bp, 1);
-		lfs_segwrite(ap->a_vp->v_mount, SEGM_FORCE_CKP);
-		fs->lfs_sp->seg_flags |= SEGM_PROT;
+		lfs_segwrite(ap->a_vp->v_mount, SEGM_CKP | SEGM_FORCE_CKP);
 		/* Copy out write stats */
 		if (ap != NULL) {
 			lws.direct = 0;
 			lws.offset = lfs_btofsb(fs, fs->lfs_sp->bytes_written);
 			*(struct lfs_write_stats *)ap->a_data = lws;
 		}
-		lfs_segunlock(fs);
+		lfs_preunlock(fs);
 		lfs_writer_leave(fs);

https://mail-index.netbsd.org/source-changes/2026/01/20/msg160304.html

So it looks like that change introduced the bug.



Home | Main Index | Thread Index | Old Index