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

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

 



From: Darrick J. Wong <djwong@xxxxxxxxxx>

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@xxxxxxxxxx>
---
 fs/xfs/libxfs/xfs_format.h |    6 ++++++
 fs/xfs/scrub/nlinks.c      |    8 ++++----
 fs/xfs/scrub/repair.c      |   12 ++++++------
 fs/xfs/xfs_inode.c         |   16 ++++++++++++----
 4 files changed, 28 insertions(+), 14 deletions(-)


diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index 0c457905cce5..977d30519738 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -891,6 +891,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/fs/xfs/scrub/nlinks.c b/fs/xfs/scrub/nlinks.c
index 67e2c167fc36..0507b80458d1 100644
--- a/fs/xfs/scrub/nlinks.c
+++ b/fs/xfs/scrub/nlinks.c
@@ -93,8 +93,8 @@ xchk_setup_nlinks(
 
 /*
  * Add a delta to an nlink counter, being careful about integer overflow.
- * Clamp the value to U32_MAX because the ondisk format does not handle
- * link counts any higher.
+ * Clamp the value to XFS_NLINK_PINNED because the ondisk format does not
+ * handle link counts any higher.
  */
 static inline void
 careful_add(
@@ -103,7 +103,7 @@ careful_add(
 {
 	uint64_t	new_value = (uint64_t)(*nlinkp) + delta;
 
-	*nlinkp = min_t(uint64_t, new_value, U32_MAX);
+	*nlinkp = min_t(uint64_t, new_value, XFS_NLINK_PINNED);
 }
 
 /* Update incore link count information.  Caller must hold the nlinks lock. */
@@ -602,7 +602,7 @@ xchk_nlinks_compare_inode(
 	 * this.  The VFS won't let users increase the link count, but it will
 	 * let them decrease it.
 	 */
-	if (total_links > U32_MAX)
+	if (total_links > XFS_NLINK_PINNED)
 		xchk_ino_set_corrupt(sc, ip->i_ino);
 	else if (total_links > XFS_MAXLINK)
 		xchk_ino_set_warning(sc, ip->i_ino);
diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
index f42ce1995caa..042bd32f5799 100644
--- a/fs/xfs/scrub/repair.c
+++ b/fs/xfs/scrub/repair.c
@@ -1163,15 +1163,15 @@ xrep_set_nlink(
 {
 	bool			ret = false;
 
-	if (nlink > U32_MAX) {
+	if (nlink > XFS_NLINK_PINNED) {
 		/*
 		 * The observed link count will overflow the nlink field.
 		 *
 		 * The VFS won't let users create more hardlinks if the link
 		 * count is larger than XFS_MAXLINK, but it will let them
-		 * delete hardlinks.  XFS_MAXLINK is half of U32_MAX, which
-		 * means that sysadmins could actually fix this situation by
-		 * deleting links and calling us again.
+		 * delete hardlinks.  XFS_MAXLINK is half of XFS_NLINK_PINNED,
+		 * which means that sysadmins could actually fix this situation
+		 * by deleting links and calling us again.
 		 *
 		 * Set the link count to the largest possible value that will
 		 * fit in the field.  This will buy us the most possible time
@@ -1179,9 +1179,9 @@ xrep_set_nlink(
 		 * As long as the link count stays above MAXLINK the undercount
 		 * problem will not get worse.
 		 */
-		BUILD_BUG_ON((uint64_t)XFS_MAXLINK >= U32_MAX);
+		BUILD_BUG_ON((uint64_t)XFS_MAXLINK >= XFS_NLINK_PINNED);
 
-		nlink = U32_MAX;
+		nlink = XFS_NLINK_PINNED;
 		ret = true;
 	}
 
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index f7dfd8e50583..2edc52e747f2 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -911,12 +911,16 @@ xfs_init_new_inode(
  */
 static int			/* error */
 xfs_droplink(
-	xfs_trans_t *tp,
-	xfs_inode_t *ip)
+	struct xfs_trans	*tp,
+	struct xfs_inode	*ip)
 {
+	struct inode		*inode = VFS_I(ip);
+
 	xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
 
-	drop_nlink(VFS_I(ip));
+	if (inode->i_nlink != XFS_NLINK_PINNED)
+		drop_nlink(VFS_I(ip));
+
 	xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
 
 	if (VFS_I(ip)->i_nlink)
@@ -933,9 +937,13 @@ xfs_bumplink(
 	struct xfs_trans	*tp,
 	struct xfs_inode	*ip)
 {
+	struct inode		*inode = VFS_I(ip);
+
 	xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
 
-	inc_nlink(VFS_I(ip));
+	if (inode->i_nlink != XFS_NLINK_PINNED)
+		inc_nlink(VFS_I(ip));
+
 	xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
 }
 




[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux