diff mbox series

[V2,01/10] xfs: Add helper for checking per-inode extent count overflow

Message ID 20200814080833.84760-2-chandanrlinux@gmail.com (mailing list archive)
State Superseded
Headers show
Series Bail out if transaction can cause extent count to overflow | expand

Commit Message

Chandan Babu R Aug. 14, 2020, 8:08 a.m. UTC
XFS does not check for possible overflow of per-inode extent counter
fields when adding extents to either data or attr fork.

For e.g.
1. Insert 5 million xattrs (each having a value size of 255 bytes) and
   then delete 50% of them in an alternating manner.

2. On a 4k block sized XFS filesystem instance, the above causes 98511
   extents to be created in the attr fork of the inode.

   xfsaild/loop0  2035 [003]  9643.390490: probe:xfs_iflush_int: (ffffffffac6225c0) if_nextents=98511 inode=131

3. The incore inode fork extent counter is a signed 32-bit
   quantity. However the on-disk extent counter is an unsigned 16-bit
   quantity and hence cannot hold 98511 extents.

4. The following incorrect value is stored in the attr extent counter,
   # xfs_db -f -c 'inode 131' -c 'print core.naextents' /dev/loop0
   core.naextents = -32561

This commit adds a new helper function (i.e.
xfs_iext_count_may_overflow()) to check for overflow of the per-inode
data and xattr extent counters. Future patches will use this function to
make sure that an FS operation will not cause the extent counter to
overflow.

Suggested-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Chandan Babu R <chandanrlinux@gmail.com>
---
 fs/xfs/libxfs/xfs_trans_resv.c | 33 +++++++++++++++++++++++++++++++++
 fs/xfs/libxfs/xfs_trans_resv.h |  2 ++
 2 files changed, 35 insertions(+)

Comments

Christoph Hellwig Aug. 17, 2020, 6:51 a.m. UTC | #1
> +int
> +xfs_iext_count_may_overflow(
> +	struct xfs_inode	*ip,
> +	int			whichfork,
> +	int			nr_to_add)
> +{
> +	struct xfs_ifork	*ifp;
> +	uint64_t		max_exts = 0;
> +	uint64_t		nr_exts;
> +
> +	switch (whichfork) {
> +	case XFS_DATA_FORK:
> +		max_exts = MAXEXTNUM;
> +		break;
> +
> +	case XFS_ATTR_FORK:
> +		max_exts = MAXAEXTNUM;
> +		break;
> +
> +	default:
> +		ASSERT(0);
> +		break;
> +	}
> +
> +	ifp = XFS_IFORK_PTR(ip, whichfork);
> +	nr_exts = ifp->if_nextents + nr_to_add;
> +
> +	if (nr_exts > max_exts)
> +		return -EFBIG;
> +
> +	return 0;
> +}

Maybe it's just me, but I would structure this very different (just
cosmetic differences, though).  First add a:

static inline uint32_t xfs_max_extents(int whichfork)
{
	return XFS_ATTR_FORK ? MAXAEXTNUM : MAXEXTNUM;
}

to have a single place that determines the max number of extents.

And the simplify the helper down to:

int
xfs_iext_count_may_overflow(
	struct xfs_inode	*ip,
	int			whichfork,
	int			nr_to_add)
{
	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, whichfork);
	uint64_t		max_exts = xfs_max_extents(whichfork);
	uint64_t		nr_exts;

	if (check_add_overflow(ifp->if_nextents, nr_to_add, &nr_exts) ||
	    nr_exts > max_exts))
		return -EFBIG;
	return 0;
}

which actually might be small enough for an inline function now.
Chandan Babu R Aug. 17, 2020, 7:44 a.m. UTC | #2
On Monday 17 August 2020 12:21:23 PM IST Christoph Hellwig wrote:
> > +int
> > +xfs_iext_count_may_overflow(
> > +	struct xfs_inode	*ip,
> > +	int			whichfork,
> > +	int			nr_to_add)
> > +{
> > +	struct xfs_ifork	*ifp;
> > +	uint64_t		max_exts = 0;
> > +	uint64_t		nr_exts;
> > +
> > +	switch (whichfork) {
> > +	case XFS_DATA_FORK:
> > +		max_exts = MAXEXTNUM;
> > +		break;
> > +
> > +	case XFS_ATTR_FORK:
> > +		max_exts = MAXAEXTNUM;
> > +		break;
> > +
> > +	default:
> > +		ASSERT(0);
> > +		break;
> > +	}
> > +
> > +	ifp = XFS_IFORK_PTR(ip, whichfork);
> > +	nr_exts = ifp->if_nextents + nr_to_add;
> > +
> > +	if (nr_exts > max_exts)
> > +		return -EFBIG;
> > +
> > +	return 0;
> > +}
> 
> Maybe it's just me, but I would structure this very different (just
> cosmetic differences, though).  First add a:
> 
> static inline uint32_t xfs_max_extents(int whichfork)
> {
> 	return XFS_ATTR_FORK ? MAXAEXTNUM : MAXEXTNUM;
> }
> 
> to have a single place that determines the max number of extents.
> 
> And the simplify the helper down to:
> 
> int
> xfs_iext_count_may_overflow(
> 	struct xfs_inode	*ip,
> 	int			whichfork,
> 	int			nr_to_add)
> {
> 	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, whichfork);
> 	uint64_t		max_exts = xfs_max_extents(whichfork);
> 	uint64_t		nr_exts;
> 
> 	if (check_add_overflow(ifp->if_nextents, nr_to_add, &nr_exts) ||
> 	    nr_exts > max_exts))
> 		return -EFBIG;
> 	return 0;
> }
> 
> which actually might be small enough for an inline function now.
> 

I agree. I will make the suggested changes in the next version.
diff mbox series

Patch

diff --git a/fs/xfs/libxfs/xfs_trans_resv.c b/fs/xfs/libxfs/xfs_trans_resv.c
index d1a0848cb52e..d21990d9df7a 100644
--- a/fs/xfs/libxfs/xfs_trans_resv.c
+++ b/fs/xfs/libxfs/xfs_trans_resv.c
@@ -832,6 +832,39 @@  xfs_calc_sb_reservation(
 	return xfs_calc_buf_res(1, mp->m_sb.sb_sectsize);
 }
 
+int
+xfs_iext_count_may_overflow(
+	struct xfs_inode	*ip,
+	int			whichfork,
+	int			nr_to_add)
+{
+	struct xfs_ifork	*ifp;
+	uint64_t		max_exts = 0;
+	uint64_t		nr_exts;
+
+	switch (whichfork) {
+	case XFS_DATA_FORK:
+		max_exts = MAXEXTNUM;
+		break;
+
+	case XFS_ATTR_FORK:
+		max_exts = MAXAEXTNUM;
+		break;
+
+	default:
+		ASSERT(0);
+		break;
+	}
+
+	ifp = XFS_IFORK_PTR(ip, whichfork);
+	nr_exts = ifp->if_nextents + nr_to_add;
+
+	if (nr_exts > max_exts)
+		return -EFBIG;
+
+	return 0;
+}
+
 void
 xfs_trans_resv_calc(
 	struct xfs_mount	*mp,
diff --git a/fs/xfs/libxfs/xfs_trans_resv.h b/fs/xfs/libxfs/xfs_trans_resv.h
index 7241ab28cf84..9d71b51990ac 100644
--- a/fs/xfs/libxfs/xfs_trans_resv.h
+++ b/fs/xfs/libxfs/xfs_trans_resv.h
@@ -93,5 +93,7 @@  struct xfs_trans_resv {
 
 void xfs_trans_resv_calc(struct xfs_mount *mp, struct xfs_trans_resv *resp);
 uint xfs_allocfree_log_count(struct xfs_mount *mp, uint num_ops);
+int xfs_iext_count_may_overflow(struct xfs_inode *ip, int whichfork,
+		int nr_exts);
 
 #endif	/* __XFS_TRANS_RESV_H__ */