xfs: fix integer overflows in the fsmap rtbitmap and logdev backends
authorDarrick J. Wong <djwong@kernel.org>
Fri, 30 Jun 2023 00:39:43 +0000 (17:39 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Sun, 2 Jul 2023 16:26:18 +0000 (09:26 -0700)
It's not correct to use the rmap irec structure to hold query key
information to query the rtbitmap because the realtime volume can be
longer than 2^32 fsblocks in length.  Because the rt volume doesn't have
allocation groups, introduce a daddr-based record filtering algorithm
and compute the rtextent values using 64-bit variables.  The same
problem exists in the external log device fsmap implementation, so use
the same solution to fix it too.

After this patch, all the code that touches info->low and info->high
under xfs_getfsmap_logdev and __xfs_getfsmap_rtdev are unnecessary.
Cleaning this up will be done in subsequent patches.

Fixes: 4c934c7dd60c ("xfs: report realtime space information via the rtbitmap")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
fs/xfs/xfs_fsmap.c

index 6ddcda2..9019181 100644 (file)
@@ -160,6 +160,8 @@ struct xfs_getfsmap_info {
        struct xfs_buf          *agf_bp;        /* AGF, for refcount queries */
        struct xfs_perag        *pag;           /* AG info, if applicable */
        xfs_daddr_t             next_daddr;     /* next daddr we expect */
+       /* daddr of low fsmap key when we're using the rtbitmap */
+       xfs_daddr_t             low_daddr;
        u64                     missing_owner;  /* owner of holes */
        u32                     dev;            /* device id */
        /*
@@ -250,6 +252,8 @@ xfs_getfsmap_rec_before_start(
        const struct xfs_rmap_irec      *rec,
        xfs_daddr_t                     rec_daddr)
 {
+       if (info->low_daddr != -1ULL)
+               return rec_daddr < info->low_daddr;
        if (info->low.rm_blockcount)
                return xfs_rmap_compare(rec, &info->low) < 0;
        return false;
@@ -257,14 +261,16 @@ xfs_getfsmap_rec_before_start(
 
 /*
  * Format a reverse mapping for getfsmap, having translated rm_startblock
- * into the appropriate daddr units.
+ * into the appropriate daddr units.  Pass in a nonzero @len_daddr if the
+ * length could be larger than rm_blockcount in struct xfs_rmap_irec.
  */
 STATIC int
 xfs_getfsmap_helper(
        struct xfs_trans                *tp,
        struct xfs_getfsmap_info        *info,
        const struct xfs_rmap_irec      *rec,
-       xfs_daddr_t                     rec_daddr)
+       xfs_daddr_t                     rec_daddr,
+       xfs_daddr_t                     len_daddr)
 {
        struct xfs_fsmap                fmr;
        struct xfs_mount                *mp = tp->t_mountp;
@@ -274,12 +280,15 @@ xfs_getfsmap_helper(
        if (fatal_signal_pending(current))
                return -EINTR;
 
+       if (len_daddr == 0)
+               len_daddr = XFS_FSB_TO_BB(mp, rec->rm_blockcount);
+
        /*
         * Filter out records that start before our startpoint, if the
         * caller requested that.
         */
        if (xfs_getfsmap_rec_before_start(info, rec, rec_daddr)) {
-               rec_daddr += XFS_FSB_TO_BB(mp, rec->rm_blockcount);
+               rec_daddr += len_daddr;
                if (info->next_daddr < rec_daddr)
                        info->next_daddr = rec_daddr;
                return 0;
@@ -298,7 +307,7 @@ xfs_getfsmap_helper(
 
                info->head->fmh_entries++;
 
-               rec_daddr += XFS_FSB_TO_BB(mp, rec->rm_blockcount);
+               rec_daddr += len_daddr;
                if (info->next_daddr < rec_daddr)
                        info->next_daddr = rec_daddr;
                return 0;
@@ -338,7 +347,7 @@ xfs_getfsmap_helper(
        if (error)
                return error;
        fmr.fmr_offset = XFS_FSB_TO_BB(mp, rec->rm_offset);
-       fmr.fmr_length = XFS_FSB_TO_BB(mp, rec->rm_blockcount);
+       fmr.fmr_length = len_daddr;
        if (rec->rm_flags & XFS_RMAP_UNWRITTEN)
                fmr.fmr_flags |= FMR_OF_PREALLOC;
        if (rec->rm_flags & XFS_RMAP_ATTR_FORK)
@@ -355,7 +364,7 @@ xfs_getfsmap_helper(
 
        xfs_getfsmap_format(mp, &fmr, info);
 out:
-       rec_daddr += XFS_FSB_TO_BB(mp, rec->rm_blockcount);
+       rec_daddr += len_daddr;
        if (info->next_daddr < rec_daddr)
                info->next_daddr = rec_daddr;
        return 0;
@@ -376,7 +385,7 @@ xfs_getfsmap_datadev_helper(
        fsb = XFS_AGB_TO_FSB(mp, cur->bc_ag.pag->pag_agno, rec->rm_startblock);
        rec_daddr = XFS_FSB_TO_DADDR(mp, fsb);
 
-       return xfs_getfsmap_helper(cur->bc_tp, info, rec, rec_daddr);
+       return xfs_getfsmap_helper(cur->bc_tp, info, rec, rec_daddr, 0);
 }
 
 /* Transform a bnobt irec into a fsmap */
@@ -400,7 +409,7 @@ xfs_getfsmap_datadev_bnobt_helper(
        irec.rm_offset = 0;
        irec.rm_flags = 0;
 
-       return xfs_getfsmap_helper(cur->bc_tp, info, &irec, rec_daddr);
+       return xfs_getfsmap_helper(cur->bc_tp, info, &irec, rec_daddr, 0);
 }
 
 /* Set rmap flags based on the getfsmap flags */
@@ -427,9 +436,13 @@ xfs_getfsmap_logdev(
 {
        struct xfs_mount                *mp = tp->t_mountp;
        struct xfs_rmap_irec            rmap;
+       xfs_daddr_t                     rec_daddr, len_daddr;
+       xfs_fsblock_t                   start_fsb;
        int                             error;
 
        /* Set up search keys */
+       start_fsb = XFS_BB_TO_FSBT(mp,
+                               keys[0].fmr_physical + keys[0].fmr_length);
        info->low.rm_startblock = XFS_BB_TO_FSBT(mp, keys[0].fmr_physical);
        info->low.rm_offset = XFS_BB_TO_FSBT(mp, keys[0].fmr_offset);
        error = xfs_fsmap_owner_to_rmap(&info->low, keys);
@@ -438,6 +451,10 @@ xfs_getfsmap_logdev(
        info->low.rm_blockcount = 0;
        xfs_getfsmap_set_irec_flags(&info->low, &keys[0]);
 
+       /* Adjust the low key if we are continuing from where we left off. */
+       if (keys[0].fmr_length > 0)
+               info->low_daddr = XFS_FSB_TO_BB(mp, start_fsb);
+
        error = xfs_fsmap_owner_to_rmap(&info->high, keys + 1);
        if (error)
                return error;
@@ -451,7 +468,7 @@ xfs_getfsmap_logdev(
        trace_xfs_fsmap_low_key(mp, info->dev, NULLAGNUMBER, &info->low);
        trace_xfs_fsmap_high_key(mp, info->dev, NULLAGNUMBER, &info->high);
 
-       if (keys[0].fmr_physical > 0)
+       if (start_fsb > 0)
                return 0;
 
        /* Fabricate an rmap entry for the external log device. */
@@ -461,7 +478,9 @@ xfs_getfsmap_logdev(
        rmap.rm_offset = 0;
        rmap.rm_flags = 0;
 
-       return xfs_getfsmap_helper(tp, info, &rmap, 0);
+       rec_daddr = XFS_FSB_TO_BB(mp, rmap.rm_startblock);
+       len_daddr = XFS_FSB_TO_BB(mp, rmap.rm_blockcount);
+       return xfs_getfsmap_helper(tp, info, &rmap, rec_daddr, len_daddr);
 }
 
 #ifdef CONFIG_XFS_RT
@@ -475,16 +494,22 @@ xfs_getfsmap_rtdev_rtbitmap_helper(
 {
        struct xfs_getfsmap_info        *info = priv;
        struct xfs_rmap_irec            irec;
-       xfs_daddr_t                     rec_daddr;
+       xfs_rtblock_t                   rtbno;
+       xfs_daddr_t                     rec_daddr, len_daddr;
+
+       rtbno = rec->ar_startext * mp->m_sb.sb_rextsize;
+       rec_daddr = XFS_FSB_TO_BB(mp, rtbno);
+       irec.rm_startblock = rtbno;
+
+       rtbno = rec->ar_extcount * mp->m_sb.sb_rextsize;
+       len_daddr = XFS_FSB_TO_BB(mp, rtbno);
+       irec.rm_blockcount = rtbno;
 
-       irec.rm_startblock = rec->ar_startext * mp->m_sb.sb_rextsize;
-       rec_daddr = XFS_FSB_TO_BB(mp, irec.rm_startblock);
-       irec.rm_blockcount = rec->ar_extcount * mp->m_sb.sb_rextsize;
        irec.rm_owner = XFS_RMAP_OWN_NULL;      /* "free" */
        irec.rm_offset = 0;
        irec.rm_flags = 0;
 
-       return xfs_getfsmap_helper(tp, info, &irec, rec_daddr);
+       return xfs_getfsmap_helper(tp, info, &irec, rec_daddr, len_daddr);
 }
 
 /* Execute a getfsmap query against the realtime device. */
@@ -493,23 +518,26 @@ __xfs_getfsmap_rtdev(
        struct xfs_trans                *tp,
        const struct xfs_fsmap          *keys,
        int                             (*query_fn)(struct xfs_trans *,
-                                                   struct xfs_getfsmap_info *),
+                                                   struct xfs_getfsmap_info *,
+                                                   xfs_rtblock_t start_rtb,
+                                                   xfs_rtblock_t end_rtb),
        struct xfs_getfsmap_info        *info)
 {
        struct xfs_mount                *mp = tp->t_mountp;
-       xfs_fsblock_t                   start_fsb;
-       xfs_fsblock_t                   end_fsb;
+       xfs_rtblock_t                   start_rtb;
+       xfs_rtblock_t                   end_rtb;
        uint64_t                        eofs;
        int                             error = 0;
 
        eofs = XFS_FSB_TO_BB(mp, mp->m_sb.sb_rblocks);
        if (keys[0].fmr_physical >= eofs)
                return 0;
-       start_fsb = XFS_BB_TO_FSBT(mp, keys[0].fmr_physical);
-       end_fsb = XFS_BB_TO_FSB(mp, min(eofs - 1, keys[1].fmr_physical));
+       start_rtb = XFS_BB_TO_FSBT(mp,
+                               keys[0].fmr_physical + keys[0].fmr_length);
+       end_rtb = XFS_BB_TO_FSB(mp, min(eofs - 1, keys[1].fmr_physical));
 
        /* Set up search keys */
-       info->low.rm_startblock = start_fsb;
+       info->low.rm_startblock = start_rtb;
        error = xfs_fsmap_owner_to_rmap(&info->low, &keys[0]);
        if (error)
                return error;
@@ -517,7 +545,14 @@ __xfs_getfsmap_rtdev(
        info->low.rm_blockcount = 0;
        xfs_getfsmap_set_irec_flags(&info->low, &keys[0]);
 
-       info->high.rm_startblock = end_fsb;
+       /* Adjust the low key if we are continuing from where we left off. */
+       if (keys[0].fmr_length > 0) {
+               info->low_daddr = XFS_FSB_TO_BB(mp, start_rtb);
+               if (info->low_daddr >= eofs)
+                       return 0;
+       }
+
+       info->high.rm_startblock = end_rtb;
        error = xfs_fsmap_owner_to_rmap(&info->high, &keys[1]);
        if (error)
                return error;
@@ -528,14 +563,16 @@ __xfs_getfsmap_rtdev(
        trace_xfs_fsmap_low_key(mp, info->dev, NULLAGNUMBER, &info->low);
        trace_xfs_fsmap_high_key(mp, info->dev, NULLAGNUMBER, &info->high);
 
-       return query_fn(tp, info);
+       return query_fn(tp, info, start_rtb, end_rtb);
 }
 
 /* Actually query the realtime bitmap. */
 STATIC int
 xfs_getfsmap_rtdev_rtbitmap_query(
        struct xfs_trans                *tp,
-       struct xfs_getfsmap_info        *info)
+       struct xfs_getfsmap_info        *info,
+       xfs_rtblock_t                   start_rtb,
+       xfs_rtblock_t                   end_rtb)
 {
        struct xfs_rtalloc_rec          alow = { 0 };
        struct xfs_rtalloc_rec          ahigh = { 0 };
@@ -548,8 +585,8 @@ xfs_getfsmap_rtdev_rtbitmap_query(
         * Set up query parameters to return free rtextents covering the range
         * we want.
         */
-       alow.ar_startext = info->low.rm_startblock;
-       ahigh.ar_startext = info->high.rm_startblock;
+       alow.ar_startext = start_rtb;
+       ahigh.ar_startext = end_rtb;
        do_div(alow.ar_startext, mp->m_sb.sb_rextsize);
        if (do_div(ahigh.ar_startext, mp->m_sb.sb_rextsize))
                ahigh.ar_startext++;
@@ -988,6 +1025,7 @@ xfs_getfsmap(
                info.dev = handlers[i].dev;
                info.last = false;
                info.pag = NULL;
+               info.low_daddr = -1ULL;
                info.low.rm_blockcount = 0;
                error = handlers[i].fn(tp, dkeys, &info);
                if (error)