diff mbox series

[4/4] xfs: pin inodes that would otherwise overflow link count

Message ID 170404998345.1797172.12807191836564310136.stgit@frogsfrogsfrogs (mailing list archive)
State Superseded, archived
Headers show
Series [1/4] xfs: check unused nlink fields in the ondisk inode | expand

Commit Message

Darrick J. Wong Dec. 31, 2023, 10:36 p.m. UTC
From: Darrick J. Wong <djwong@kernel.org>

The VFS inc_nlink function does not explicitly check for integer
overflows in the i_nlink field.  Instead, it checks the link count
against s_max_links in the vfs_{link,create,rename} functions.  XFS
sets the maximum link count to 2.1 billion, so integer overflows should
not be a problem.

However.  It's possible that online repair could find that a file has
more than four billion links, particularly if the link count got
corrupted while creating hardlinks to the file.  The di_nlinkv2 field is
not large enough to store a value larger than 2^32, so we ought to
define a magic pin value of ~0U which means that the inode never gets
deleted.  This will prevent a UAF error if the repair finds this
situation and users begin deleting links to the file.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
 libxfs/util.c       |    3 ++-
 libxfs/xfs_format.h |    6 ++++++
 repair/incore_ino.c |    3 ++-
 3 files changed, 10 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/libxfs/util.c b/libxfs/util.c
index 11978529ed6..03191ebcd08 100644
--- a/libxfs/util.c
+++ b/libxfs/util.c
@@ -252,7 +252,8 @@  libxfs_bumplink(
 
 	xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
 
-	inc_nlink(inode);
+	if (inode->i_nlink != XFS_NLINK_PINNED)
+		inc_nlink(inode);
 
 	xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
 }
diff --git a/libxfs/xfs_format.h b/libxfs/xfs_format.h
index 7861539ab8b..ec25010b577 100644
--- a/libxfs/xfs_format.h
+++ b/libxfs/xfs_format.h
@@ -912,6 +912,12 @@  static inline uint xfs_dinode_size(int version)
  */
 #define	XFS_MAXLINK		((1U << 31) - 1U)
 
+/*
+ * Any file that hits the maximum ondisk link count should be pinned to avoid
+ * a use-after-free situation.
+ */
+#define	XFS_NLINK_PINNED	(~0U)
+
 /*
  * Values for di_format
  *
diff --git a/repair/incore_ino.c b/repair/incore_ino.c
index 0dd7a2f060f..b0b41a2cc5c 100644
--- a/repair/incore_ino.c
+++ b/repair/incore_ino.c
@@ -108,7 +108,8 @@  void add_inode_ref(struct ino_tree_node *irec, int ino_offset)
 		nlink_grow_16_to_32(irec);
 		/*FALLTHRU*/
 	case sizeof(uint32_t):
-		irec->ino_un.ex_data->counted_nlinks.un32[ino_offset]++;
+		if (irec->ino_un.ex_data->counted_nlinks.un32[ino_offset] != XFS_NLINK_PINNED)
+			irec->ino_un.ex_data->counted_nlinks.un32[ino_offset]++;
 		break;
 	default:
 		ASSERT(0);