xfs: strengthen AGI unlinked inode bucket pointer checks
authorDarrick J. Wong <darrick.wong@oracle.com>
Thu, 7 Feb 2019 18:37:14 +0000 (10:37 -0800)
committerDarrick J. Wong <darrick.wong@oracle.com>
Tue, 12 Feb 2019 00:07:01 +0000 (16:07 -0800)
Strengthen our checking of the AGI unlinked pointers when we start to
use them for updating the metadata.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Brian Foster <bfoster@redhat.com>
fs/xfs/xfs_inode.c

index f1de80e..ba19cfd 100644 (file)
@@ -1963,6 +1963,7 @@ xfs_iunlink(
        struct xfs_dinode       *dip;
        struct xfs_buf          *agibp;
        struct xfs_buf          *ibp;
+       xfs_agino_t             next_agino;
        xfs_agnumber_t          agno = XFS_INO_TO_AGNO(mp, ip->i_ino);
        xfs_agino_t             agino = XFS_INO_TO_AGINO(mp, ip->i_ino);
        short                   bucket_index = agino % XFS_AGI_UNLINKED_BUCKETS;
@@ -1978,13 +1979,16 @@ xfs_iunlink(
        agi = XFS_BUF_TO_AGI(agibp);
 
        /*
-        * Get the index into the agi hash table for the
-        * list this inode will go on.
+        * Get the index into the agi hash table for the list this inode will
+        * go on.  Make sure the pointer isn't garbage and that this inode
+        * isn't already on the list.
         */
-       ASSERT(agi->agi_unlinked[bucket_index]);
-       ASSERT(be32_to_cpu(agi->agi_unlinked[bucket_index]) != agino);
+       next_agino = be32_to_cpu(agi->agi_unlinked[bucket_index]);
+       if (next_agino == agino ||
+           !xfs_verify_agino_or_null(mp, agno, next_agino))
+               return -EFSCORRUPTED;
 
-       if (agi->agi_unlinked[bucket_index] != cpu_to_be32(NULLAGINO)) {
+       if (next_agino != NULLAGINO) {
                /*
                 * There is already another inode in the bucket we need
                 * to add ourselves to.  Add us at the front of the list.
@@ -2045,17 +2049,17 @@ xfs_iunlink_remove(
        agi = XFS_BUF_TO_AGI(agibp);
 
        /*
-        * Get the index into the agi hash table for the
-        * list this inode will go on.
+        * Get the index into the agi hash table for the list this inode will
+        * go on.  Make sure the head pointer isn't garbage.
         */
-       if (!xfs_verify_agino(mp, agno,
-                       be32_to_cpu(agi->agi_unlinked[bucket_index]))) {
+       next_agino = be32_to_cpu(agi->agi_unlinked[bucket_index]);
+       if (!xfs_verify_agino(mp, agno, next_agino)) {
                XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
                                agi, sizeof(*agi));
                return -EFSCORRUPTED;
        }
 
-       if (be32_to_cpu(agi->agi_unlinked[bucket_index]) == agino) {
+       if (next_agino == agino) {
                /*
                 * We're at the head of the list.  Get the inode's on-disk
                 * buffer to see if there is anyone after us on the list.
@@ -2097,7 +2101,6 @@ xfs_iunlink_remove(
                /*
                 * We need to search the list for the inode being freed.
                 */
-               next_agino = be32_to_cpu(agi->agi_unlinked[bucket_index]);
                last_ibp = NULL;
                while (next_agino != agino) {
                        struct xfs_imap imap;