From 5f204051d998ec3d7306db0d749bddcbf4c97693 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 15 Apr 2024 14:55:05 -0700 Subject: xfs: pin inodes that would otherwise overflow link count 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 Reviewed-by: Christoph Hellwig --- fs/xfs/scrub/dir_repair.c | 11 +++-------- fs/xfs/scrub/nlinks.c | 4 +++- fs/xfs/scrub/nlinks_repair.c | 8 ++------ 3 files changed, 8 insertions(+), 15 deletions(-) (limited to 'fs/xfs/scrub') diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index c150b2efa2c2..38957da26b94 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1145,7 +1145,9 @@ xrep_dir_set_nlink( struct xfs_scrub *sc = rd->sc; struct xfs_inode *dp = sc->ip; struct xfs_perag *pag; - unsigned int new_nlink = rd->subdirs + 2; + unsigned int new_nlink = min_t(unsigned long long, + rd->subdirs + 2, + XFS_NLINK_PINNED); int error; /* @@ -1201,13 +1203,6 @@ xrep_dir_swap( bool ip_local, temp_local; int error = 0; - /* - * If we found enough subdirs to overflow this directory's link count, - * bail out to userspace before we modify anything. - */ - if (rd->subdirs + 2 > XFS_MAXLINK) - return -EFSCORRUPTED; - /* * If we never found the parent for this directory, temporarily assign * the root dir as the parent; we'll move this to the orphanage after diff --git a/fs/xfs/scrub/nlinks.c b/fs/xfs/scrub/nlinks.c index c456523fac9c..fcb9c473f372 100644 --- a/fs/xfs/scrub/nlinks.c +++ b/fs/xfs/scrub/nlinks.c @@ -607,9 +607,11 @@ xchk_nlinks_compare_inode( * this as a corruption. The VFS won't let users increase the link * count, but it will let them decrease it. */ - if (total_links > XFS_MAXLINK) { + if (total_links > XFS_NLINK_PINNED) { xchk_ino_set_corrupt(sc, ip->i_ino); goto out_corrupt; + } else if (total_links > XFS_MAXLINK) { + xchk_ino_set_warning(sc, ip->i_ino); } /* Link counts should match. */ diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c index 0cb67339eac8..83f8637bb08f 100644 --- a/fs/xfs/scrub/nlinks_repair.c +++ b/fs/xfs/scrub/nlinks_repair.c @@ -238,14 +238,10 @@ xrep_nlinks_repair_inode( /* Commit the new link count if it changed. */ if (total_links != actual_nlink) { - if (total_links > XFS_MAXLINK) { - trace_xrep_nlinks_unfixable_inode(mp, ip, &obs); - goto out_trans; - } - trace_xrep_nlinks_update_inode(mp, ip, &obs); - set_nlink(VFS_I(ip), total_links); + set_nlink(VFS_I(ip), min_t(unsigned long long, total_links, + XFS_NLINK_PINNED)); dirty = true; } -- cgit v1.2.3