DragonFlyBSD Kernel Audit
DF-2632 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/hammer2/hammer2.h	2026-08-29 07:17:55.442213003 +0000
+++ b/sys/vfs/hammer2/hammer2.h	2026-08-29 07:19:39.972891167 +0000
@@ -707,6 +707,15 @@
 	uint8_t			comp_heuristic;
 	hammer2_inode_meta_t	meta;		/* copy of meta-data */
 	hammer2_off_t		osize;
+	/*
+	 * DF-2633: asynchronous backend error state (errno).  Backend
+	 * paths that cannot return an error to the syscall which
+	 * started the work (strategy xop allocation failures, syncer
+	 * inode topology insertions) store the errno here so that
+	 * write(2) and fsync(2) can report it, instead of silently
+	 * succeeding while data is being lost.  Accessed atomically.
+	 */
+	int			error;
 };
 
 typedef struct hammer2_inode hammer2_inode_t;
@@ -1603,7 +1612,7 @@
 				hammer2_blockref_t *base, int count,
 				hammer2_chain_t *chain,
 				hammer2_blockref_t *obref);
-void hammer2_base_insert(hammer2_chain_t *parent,
+int hammer2_base_insert(hammer2_chain_t *parent,
 				hammer2_blockref_t *base, int count,
 				hammer2_chain_t *chain,
 				hammer2_blockref_t *elm);
--- a/sys/vfs/hammer2/hammer2_chain.c	2026-08-29 07:17:36.418453561 +0000
+++ b/sys/vfs/hammer2/hammer2_chain.c	2026-08-29 07:19:30.969005026 +0000
@@ -3489,6 +3489,8 @@
 			   hammer2_tid_t mtid, int flags,
 			   hammer2_blockref_t *obref)
 {
+	int error;
+
 	hammer2_chain_rename(parentp, chain, mtid, flags);
 
 	if (obref->type != HAMMER2_BREF_TYPE_EMPTY) {
@@ -3498,7 +3500,27 @@
 		KKASSERT((chain->flags & HAMMER2_CHAIN_BLKMAPPED) == 0);
 		hammer2_chain_modify(*parentp, mtid, 0, 0);
 		tbase = hammer2_chain_base_and_count(*parentp, &tcount);
-		hammer2_base_insert(*parentp, tbase, tcount, chain, obref);
+		error = hammer2_base_insert(*parentp, tbase, tcount, chain,
+					    obref);
+		if (error) {
+			/*
+			 * DF-2632: inserting the preserved obref overlaps an
+			 * element already present in the target blockref
+			 * array.  Observed from the flusher's indirect-
+			 * maintenance collapse path when directory-entry
+			 * keys are densely packed into one dirhash window.
+			 *
+			 * Skipping the media insert is safe: the chain is
+			 * already in the new parent's RBTREE.  Set
+			 * UPDATE|BLKMAPUPD so a later flush retries the
+			 * blockref insertion -- i.e. the flush defers
+			 * instead of panicking the kernel.
+			 */
+			atomic_set_int(&chain->flags,
+				       HAMMER2_CHAIN_BLKMAPUPD |
+				       HAMMER2_CHAIN_UPDATE);
+			return;
+		}
 		if (bcmp(obref, &chain->bref, sizeof(chain->bref))) {
 			atomic_set_int(&chain->flags, HAMMER2_CHAIN_BLKMAPUPD |
 						      HAMMER2_CHAIN_UPDATE);
@@ -5218,8 +5240,15 @@
  *
  * NOTE: live_count was adjusted when the chain was deleted, so it does not
  *	 need to be adjusted when we commit the media change.
+ *
+ * DF-2632: Returns 0 on success or an error if the element would overlap
+ * an existing element in the array.  In that case the insert is skipped
+ * (the array is left unchanged) and the caller is expected to defer the
+ * operation (e.g. set HAMMER2_CHAIN_UPDATE so a later flush retries),
+ * instead of the previous unconditional panic which made a dense
+ * directory-entry window a remote kernel-panic DoS.
  */
-void
+int
 hammer2_base_insert(hammer2_chain_t *parent,
 		    hammer2_blockref_t *base, int count,
 		    hammer2_chain_t *chain, hammer2_blockref_t *elm)
@@ -5231,6 +5260,7 @@
 	int k;
 	int l;
 	int u = 1;
+	int overlap;
 
 	/*
 	 * Insert new element.  Expect the element to not already exist
@@ -5250,6 +5280,40 @@
 	KKASSERT(i >= 0 && i <= count);
 
 	/*
+	 * DF-2632: check the overlap condition BEFORE any stats side
+	 * effects so a skipped insert leaves the parent fully consistent.
+	 * The append shortcut below (i == count) cannot overlap by
+	 * construction.
+	 */
+	xkey = elm->key + ((hammer2_key_t)1 << elm->keybits) - 1;
+	overlap = (i != count &&
+		   (base[i].key < elm->key || xkey >= base[i].key));
+	if (overlap) {
+		/*
+		 * Do NOT touch the spinlock here: some callers
+		 * (hammer2_chain_rename_obref from indirect maintenance)
+		 * do not hold parent->core.spin; the old panic path's
+		 * unconditional hammer2_spin_unex() corrupted the
+		 * critcount in exactly that case (the INVARIANTS
+		 * "td_critcount is/would-go negative" panic).
+		 *
+		 * kprintf is spin-safe.
+		 */
+		if (chain) {
+			atomic_clear_int(&chain->flags,
+					 HAMMER2_CHAIN_BLKMAPPED);
+		}
+		krateprintf(&krate_h2me,
+			    "hammer2: base_insert overlap deferred: "
+			    "parent %p type %d elm %016jx/%d type %d "
+			    "vs base[%d] %016jx/%d type %d\n",
+			    parent, parent->bref.type,
+			    elm->key, elm->keybits, elm->type,
+			    i, base[i].key, base[i].keybits, base[i].type);
+		return (HAMMER2_ERROR_EINVAL);
+	}
+
+	/*
 	 * Set appropriate blockmap flags in chain (if not NULL)
 	 */
 	if (chain)
@@ -5301,14 +5365,7 @@
 	if (i == count && parent->core.live_zero < count) {
 		i = parent->core.live_zero++;
 		base[i] = *elm;
-		return;
-	}
-
-	xkey = elm->key + ((hammer2_key_t)1 << elm->keybits) - 1;
-	if (i != count && (base[i].key < elm->key || xkey >= base[i].key)) {
-		hammer2_spin_unex(&parent->core.spin);
-		panic("insert base %p overlapping elements at %d elm %p\n",
-		      base, i, elm);
+		return(0);
 	}
 
 	/*
@@ -5368,6 +5425,7 @@
 		}
 	}
 
+	return(0);
 }
 
 
--- a/sys/vfs/hammer2/hammer2_flush.c	2026-08-29 07:17:36.422453511 +0000
+++ b/sys/vfs/hammer2/hammer2_flush.c	2026-08-29 07:19:07.225305277 +0000
@@ -487,6 +487,7 @@
 	hammer2_chain_t *parent;
 	hammer2_dev_t *hmp;
 	int save_error;
+	int error;
 	int retry;
 
 	retry = 0;
@@ -1127,8 +1128,21 @@
 		}
 		if (base && (chain->flags & HAMMER2_CHAIN_BLKMAPPED) == 0) {
 			hammer2_spin_ex(&parent->core.spin);
-			hammer2_base_insert(parent, base, count,
-					    chain, &chain->bref);
+			error = hammer2_base_insert(parent, base, count,
+						    chain, &chain->bref);
+			if (error) {
+				/*
+				 * DF-2632: overlap detected while updating
+				 * the parent's blockref array.  Defer: keep
+				 * the UPDATE flag set so a later flush
+				 * retries the insertion instead of the
+				 * previous unconditional panic.
+				 */
+				atomic_set_int(&chain->flags,
+					       HAMMER2_CHAIN_UPDATE);
+				info->error |= error;
+				retry = 1;
+			}
 			hammer2_spin_unex(&parent->core.spin);
 			/* base_insert sets BLKMAPPED */
 		}
--- a/sys/vfs/hammer2/hammer2_inode.c	2026-08-29 07:17:36.426453460 +0000
+++ b/sys/vfs/hammer2/hammer2_inode.c	2026-08-29 07:19:58.072662285 +0000
@@ -42,6 +42,8 @@
 
 #include "hammer2.h"
 
+static struct krate krate_h2err = { .freq = 1 };
+
 #define INODE_DEBUG	0
 
 /*
@@ -1723,13 +1725,16 @@
 		if (error == HAMMER2_ERROR_ENOENT)
 			error = 0;
 		if (error) {
-			kprintf("hammer2: unable to fsync inode %p\n", ip);
 			/*
-			atomic_set_int(&ip->flags,
-				       xop->ipflags & (HAMMER2_INODE_RESIZED |
-						       HAMMER2_INODE_MODIFIED));
-			*/
-			/* XXX return error somehow? */
+			 * DF-2633: record the fsync-time inode sync
+			 * failure so the next write(2)/fsync(2) reports
+			 * it; rate-limit the console output.
+			 */
+			atomic_swap_int(&ip->error,
+					hammer2_error_to_errno(error));
+			krateprintf(&krate_h2err,
+				    "hammer2: unable to fsync inode %p: %s\n",
+				    ip, hammer2_error_str(error));
 		}
 	}
 	return error;
@@ -1758,9 +1763,22 @@
 		if (error == HAMMER2_ERROR_ENOENT)
 			error = 0;
 		if (error) {
-			kprintf("hammer2: backend unable to "
-				"insert inode %p %ld\n", ip, (long)ip->meta.inum);
-			/* XXX return error somehow? */
+			/*
+			 * DF-2633: the file appeared to be created
+			 * successfully (the dirent is live, the data may
+			 * even be flushed), but its inode never made it
+			 * into the on-media topology.  Record the failure
+			 * on the inode so write(2)/fsync(2) report it
+			 * instead of silently succeeding; rate-limit the
+			 * console flood.
+			 */
+			atomic_swap_int(&ip->error,
+					hammer2_error_to_errno(error));
+			krateprintf(&krate_h2err,
+				    "hammer2: backend unable to "
+				    "insert inode %p %ld: %s\n",
+				    ip, (long)ip->meta.inum,
+				    hammer2_error_str(error));
 		}
 	}
 	return error;
--- a/sys/vfs/hammer2/hammer2_strategy.c	2026-08-29 07:17:36.430453409 +0000
+++ b/sys/vfs/hammer2/hammer2_strategy.c	2026-08-29 07:20:17.440417367 +0000
@@ -53,6 +53,8 @@
 #include <sys/objcache.h>
 
 #include "hammer2.h"
+
+static struct krate krate_h2err = { .freq = 1 };
 #include "hammer2_lz4.h"
 
 #include "zlib/hammer2_zlib.h"
@@ -673,10 +675,21 @@
 		bp->b_error = 0;
 		biodone(bio);
 	} else {
-		kprintf("xop_strategy_write: error %d loff=%016jx\n",
-			error, (intmax_t)bp->b_loffset);
+		/*
+		 * DF-2633: the backend write failed (typically a
+		 * freemap allocation failure on a full PFS) after the
+		 * frontend write(2) already returned success.  Record
+		 * the errno on the inode so the next write(2)/fsync(2)
+		 * on this file reports the failure instead of losing
+		 * the data silently.  Rate-limit the console flood.
+		 */
+		atomic_swap_int(&ip->error,
+				hammer2_error_to_errno(error));
+		krateprintf(&krate_h2err,
+			    "xop_strategy_write: error %d loff=%016jx\n",
+			    error, (intmax_t)bp->b_loffset);
 		bp->b_flags |= B_ERROR;
-		bp->b_error = EIO;
+		bp->b_error = hammer2_error_to_errno(error);
 		biodone(bio);
 	}
 	hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
--- a/sys/vfs/hammer2/hammer2_vnops.c	2026-08-29 07:17:36.430453409 +0000
+++ b/sys/vfs/hammer2/hammer2_vnops.c	2026-08-29 07:20:52.503973963 +0000
@@ -58,6 +58,8 @@
 
 #include "hammer2.h"
 
+static struct krate krate_h2err = { .freq = 1 };
+
 static int hammer2_read_file(hammer2_inode_t *ip, struct uio *uio,
 				int seqcount);
 static int hammer2_write_file(hammer2_inode_t *ip, struct uio *uio,
@@ -272,6 +274,18 @@
 	hammer2_inode_unlock(ip);
 	hammer2_trans_done(ip->pmp, 0);
 
+	/*
+	 * DF-2633: fsync(2) must report asynchronous backend write
+	 * failures recorded on this inode (strategy allocation
+	 * failures, topology insert failures).  POSIX requires fsync
+	 * to report I/O errors on the data it claims to have flushed;
+	 * previously these were dropped and fsync returned 0 while
+	 * the data was lost.
+	 */
+	error2 = atomic_swap_int(&ip->error, 0);
+	if (error2)
+		error1 = error2;
+
 	return (error1);
 }
 
@@ -851,6 +865,22 @@
 	error = 0;
 	if (ip->pmp->ronly || (ip->pmp->flags & HAMMER2_PMPF_EMERG))
 		return (EROFS);
+
+	/*
+	 * DF-2633: report a deferred backend failure (async write
+	 * allocation failure, inode topology insert failure) recorded
+	 * by the strategy xop or the syncer.  Without this the writer
+	 * keeps seeing write(2) succeed while the data is being lost.
+	 *
+	 * (VM-pager UIO_NOCOPY writes are excluded; the pager cannot
+	 * meaningfully act on a deferred errno here.)
+	 */
+	if (uio->uio_segflg != UIO_NOCOPY) {
+		error = atomic_swap_int(&ip->error, 0);
+		if (error)
+			return (error);
+	}
+
 	switch (hammer2_vfs_enospace(ip, uio->uio_resid, ap->a_cred)) {
 	case 2:
 		return (ENOSPC);