diff options
author | Darrick J. Wong <darrick.wong@oracle.com> | 2018-03-23 13:06:51 -0400 |
---|---|---|
committer | Darrick J. Wong <darrick.wong@oracle.com> | 2018-03-23 21:05:07 -0400 |
commit | 6915ef35c0350e87a104cb4c4ab2121c81ca7a34 (patch) | |
tree | b69db4b59599ceb73769e1c593326a9707ba9998 | |
parent | a27ba2607e60312554cbcd43fc660b2c7f29dc9c (diff) |
xfs: sanity-check the unused space before trying to use it
In xfs_dir2_data_use_free, we examine on-disk metadata and ASSERT if
it doesn't make sense. Since a carefully crafted fuzzed image can cause
the kernel to crash after blowing a bunch of assertions, let's move
those checks into a validator function and rig everything up to return
EFSCORRUPTED to userspace. Found by lastbit fuzzing ltail.bestcount via
xfs/391.
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2.h | 2 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2_block.c | 59 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2_data.c | 78 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2_leaf.c | 10 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2_node.c | 11 |
5 files changed, 111 insertions, 49 deletions
diff --git a/fs/xfs/libxfs/xfs_dir2.h b/fs/xfs/libxfs/xfs_dir2.h index 388d67c5c903..989e95a53db2 100644 --- a/fs/xfs/libxfs/xfs_dir2.h +++ b/fs/xfs/libxfs/xfs_dir2.h | |||
@@ -173,7 +173,7 @@ extern void xfs_dir2_data_log_unused(struct xfs_da_args *args, | |||
173 | extern void xfs_dir2_data_make_free(struct xfs_da_args *args, | 173 | extern void xfs_dir2_data_make_free(struct xfs_da_args *args, |
174 | struct xfs_buf *bp, xfs_dir2_data_aoff_t offset, | 174 | struct xfs_buf *bp, xfs_dir2_data_aoff_t offset, |
175 | xfs_dir2_data_aoff_t len, int *needlogp, int *needscanp); | 175 | xfs_dir2_data_aoff_t len, int *needlogp, int *needscanp); |
176 | extern void xfs_dir2_data_use_free(struct xfs_da_args *args, | 176 | extern int xfs_dir2_data_use_free(struct xfs_da_args *args, |
177 | struct xfs_buf *bp, struct xfs_dir2_data_unused *dup, | 177 | struct xfs_buf *bp, struct xfs_dir2_data_unused *dup, |
178 | xfs_dir2_data_aoff_t offset, xfs_dir2_data_aoff_t len, | 178 | xfs_dir2_data_aoff_t offset, xfs_dir2_data_aoff_t len, |
179 | int *needlogp, int *needscanp); | 179 | int *needlogp, int *needscanp); |
diff --git a/fs/xfs/libxfs/xfs_dir2_block.c b/fs/xfs/libxfs/xfs_dir2_block.c index 2da86a394bcf..875893ded514 100644 --- a/fs/xfs/libxfs/xfs_dir2_block.c +++ b/fs/xfs/libxfs/xfs_dir2_block.c | |||
@@ -451,15 +451,19 @@ xfs_dir2_block_addname( | |||
451 | * No stale entries, will use enddup space to hold new leaf. | 451 | * No stale entries, will use enddup space to hold new leaf. |
452 | */ | 452 | */ |
453 | if (!btp->stale) { | 453 | if (!btp->stale) { |
454 | xfs_dir2_data_aoff_t aoff; | ||
455 | |||
454 | /* | 456 | /* |
455 | * Mark the space needed for the new leaf entry, now in use. | 457 | * Mark the space needed for the new leaf entry, now in use. |
456 | */ | 458 | */ |
457 | xfs_dir2_data_use_free(args, bp, enddup, | 459 | aoff = (xfs_dir2_data_aoff_t)((char *)enddup - (char *)hdr + |
458 | (xfs_dir2_data_aoff_t) | 460 | be16_to_cpu(enddup->length) - sizeof(*blp)); |
459 | ((char *)enddup - (char *)hdr + be16_to_cpu(enddup->length) - | 461 | error = xfs_dir2_data_use_free(args, bp, enddup, aoff, |
460 | sizeof(*blp)), | 462 | (xfs_dir2_data_aoff_t)sizeof(*blp), &needlog, |
461 | (xfs_dir2_data_aoff_t)sizeof(*blp), | 463 | &needscan); |
462 | &needlog, &needscan); | 464 | if (error) |
465 | return error; | ||
466 | |||
463 | /* | 467 | /* |
464 | * Update the tail (entry count). | 468 | * Update the tail (entry count). |
465 | */ | 469 | */ |
@@ -541,9 +545,11 @@ xfs_dir2_block_addname( | |||
541 | /* | 545 | /* |
542 | * Mark space for the data entry used. | 546 | * Mark space for the data entry used. |
543 | */ | 547 | */ |
544 | xfs_dir2_data_use_free(args, bp, dup, | 548 | error = xfs_dir2_data_use_free(args, bp, dup, |
545 | (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), | 549 | (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), |
546 | (xfs_dir2_data_aoff_t)len, &needlog, &needscan); | 550 | (xfs_dir2_data_aoff_t)len, &needlog, &needscan); |
551 | if (error) | ||
552 | return error; | ||
547 | /* | 553 | /* |
548 | * Create the new data entry. | 554 | * Create the new data entry. |
549 | */ | 555 | */ |
@@ -997,8 +1003,10 @@ xfs_dir2_leaf_to_block( | |||
997 | /* | 1003 | /* |
998 | * Use up the space at the end of the block (blp/btp). | 1004 | * Use up the space at the end of the block (blp/btp). |
999 | */ | 1005 | */ |
1000 | xfs_dir2_data_use_free(args, dbp, dup, args->geo->blksize - size, size, | 1006 | error = xfs_dir2_data_use_free(args, dbp, dup, |
1001 | &needlog, &needscan); | 1007 | args->geo->blksize - size, size, &needlog, &needscan); |
1008 | if (error) | ||
1009 | return error; | ||
1002 | /* | 1010 | /* |
1003 | * Initialize the block tail. | 1011 | * Initialize the block tail. |
1004 | */ | 1012 | */ |
@@ -1110,18 +1118,14 @@ xfs_dir2_sf_to_block( | |||
1110 | * Add block 0 to the inode. | 1118 | * Add block 0 to the inode. |
1111 | */ | 1119 | */ |
1112 | error = xfs_dir2_grow_inode(args, XFS_DIR2_DATA_SPACE, &blkno); | 1120 | error = xfs_dir2_grow_inode(args, XFS_DIR2_DATA_SPACE, &blkno); |
1113 | if (error) { | 1121 | if (error) |
1114 | kmem_free(sfp); | 1122 | goto out_free; |
1115 | return error; | ||
1116 | } | ||
1117 | /* | 1123 | /* |
1118 | * Initialize the data block, then convert it to block format. | 1124 | * Initialize the data block, then convert it to block format. |
1119 | */ | 1125 | */ |
1120 | error = xfs_dir3_data_init(args, blkno, &bp); | 1126 | error = xfs_dir3_data_init(args, blkno, &bp); |
1121 | if (error) { | 1127 | if (error) |
1122 | kmem_free(sfp); | 1128 | goto out_free; |
1123 | return error; | ||
1124 | } | ||
1125 | xfs_dir3_block_init(mp, tp, bp, dp); | 1129 | xfs_dir3_block_init(mp, tp, bp, dp); |
1126 | hdr = bp->b_addr; | 1130 | hdr = bp->b_addr; |
1127 | 1131 | ||
@@ -1136,8 +1140,10 @@ xfs_dir2_sf_to_block( | |||
1136 | */ | 1140 | */ |
1137 | dup = dp->d_ops->data_unused_p(hdr); | 1141 | dup = dp->d_ops->data_unused_p(hdr); |
1138 | needlog = needscan = 0; | 1142 | needlog = needscan = 0; |
1139 | xfs_dir2_data_use_free(args, bp, dup, args->geo->blksize - i, | 1143 | error = xfs_dir2_data_use_free(args, bp, dup, args->geo->blksize - i, |
1140 | i, &needlog, &needscan); | 1144 | i, &needlog, &needscan); |
1145 | if (error) | ||
1146 | goto out_free; | ||
1141 | ASSERT(needscan == 0); | 1147 | ASSERT(needscan == 0); |
1142 | /* | 1148 | /* |
1143 | * Fill in the tail. | 1149 | * Fill in the tail. |
@@ -1150,9 +1156,11 @@ xfs_dir2_sf_to_block( | |||
1150 | /* | 1156 | /* |
1151 | * Remove the freespace, we'll manage it. | 1157 | * Remove the freespace, we'll manage it. |
1152 | */ | 1158 | */ |
1153 | xfs_dir2_data_use_free(args, bp, dup, | 1159 | error = xfs_dir2_data_use_free(args, bp, dup, |
1154 | (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), | 1160 | (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), |
1155 | be16_to_cpu(dup->length), &needlog, &needscan); | 1161 | be16_to_cpu(dup->length), &needlog, &needscan); |
1162 | if (error) | ||
1163 | goto out_free; | ||
1156 | /* | 1164 | /* |
1157 | * Create entry for . | 1165 | * Create entry for . |
1158 | */ | 1166 | */ |
@@ -1256,4 +1264,7 @@ xfs_dir2_sf_to_block( | |||
1256 | xfs_dir2_block_log_tail(tp, bp); | 1264 | xfs_dir2_block_log_tail(tp, bp); |
1257 | xfs_dir3_data_check(dp, bp); | 1265 | xfs_dir3_data_check(dp, bp); |
1258 | return 0; | 1266 | return 0; |
1267 | out_free: | ||
1268 | kmem_free(sfp); | ||
1269 | return error; | ||
1259 | } | 1270 | } |
diff --git a/fs/xfs/libxfs/xfs_dir2_data.c b/fs/xfs/libxfs/xfs_dir2_data.c index 920279485275..cb67ec730b9b 100644 --- a/fs/xfs/libxfs/xfs_dir2_data.c +++ b/fs/xfs/libxfs/xfs_dir2_data.c | |||
@@ -932,10 +932,51 @@ xfs_dir2_data_make_free( | |||
932 | *needscanp = needscan; | 932 | *needscanp = needscan; |
933 | } | 933 | } |
934 | 934 | ||
935 | /* Check our free data for obvious signs of corruption. */ | ||
936 | static inline xfs_failaddr_t | ||
937 | xfs_dir2_data_check_free( | ||
938 | struct xfs_dir2_data_hdr *hdr, | ||
939 | struct xfs_dir2_data_unused *dup, | ||
940 | xfs_dir2_data_aoff_t offset, | ||
941 | xfs_dir2_data_aoff_t len) | ||
942 | { | ||
943 | if (hdr->magic != cpu_to_be32(XFS_DIR2_DATA_MAGIC) && | ||
944 | hdr->magic != cpu_to_be32(XFS_DIR3_DATA_MAGIC) && | ||
945 | hdr->magic != cpu_to_be32(XFS_DIR2_BLOCK_MAGIC) && | ||
946 | hdr->magic != cpu_to_be32(XFS_DIR3_BLOCK_MAGIC)) | ||
947 | return __this_address; | ||
948 | if (be16_to_cpu(dup->freetag) != XFS_DIR2_DATA_FREE_TAG) | ||
949 | return __this_address; | ||
950 | if (offset < (char *)dup - (char *)hdr) | ||
951 | return __this_address; | ||
952 | if (offset + len > (char *)dup + be16_to_cpu(dup->length) - (char *)hdr) | ||
953 | return __this_address; | ||
954 | if ((char *)dup - (char *)hdr != | ||
955 | be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup))) | ||
956 | return __this_address; | ||
957 | return NULL; | ||
958 | } | ||
959 | |||
960 | /* Sanity-check a new bestfree entry. */ | ||
961 | static inline xfs_failaddr_t | ||
962 | xfs_dir2_data_check_new_free( | ||
963 | struct xfs_dir2_data_hdr *hdr, | ||
964 | struct xfs_dir2_data_free *dfp, | ||
965 | struct xfs_dir2_data_unused *newdup) | ||
966 | { | ||
967 | if (dfp == NULL) | ||
968 | return __this_address; | ||
969 | if (dfp->length != newdup->length) | ||
970 | return __this_address; | ||
971 | if (be16_to_cpu(dfp->offset) != (char *)newdup - (char *)hdr) | ||
972 | return __this_address; | ||
973 | return NULL; | ||
974 | } | ||
975 | |||
935 | /* | 976 | /* |
936 | * Take a byte range out of an existing unused space and make it un-free. | 977 | * Take a byte range out of an existing unused space and make it un-free. |
937 | */ | 978 | */ |
938 | void | 979 | int |
939 | xfs_dir2_data_use_free( | 980 | xfs_dir2_data_use_free( |
940 | struct xfs_da_args *args, | 981 | struct xfs_da_args *args, |
941 | struct xfs_buf *bp, | 982 | struct xfs_buf *bp, |
@@ -947,23 +988,19 @@ xfs_dir2_data_use_free( | |||
947 | { | 988 | { |
948 | xfs_dir2_data_hdr_t *hdr; /* data block header */ | 989 | xfs_dir2_data_hdr_t *hdr; /* data block header */ |
949 | xfs_dir2_data_free_t *dfp; /* bestfree pointer */ | 990 | xfs_dir2_data_free_t *dfp; /* bestfree pointer */ |
991 | xfs_dir2_data_unused_t *newdup; /* new unused entry */ | ||
992 | xfs_dir2_data_unused_t *newdup2; /* another new unused entry */ | ||
993 | struct xfs_dir2_data_free *bf; | ||
994 | xfs_failaddr_t fa; | ||
950 | int matchback; /* matches end of freespace */ | 995 | int matchback; /* matches end of freespace */ |
951 | int matchfront; /* matches start of freespace */ | 996 | int matchfront; /* matches start of freespace */ |
952 | int needscan; /* need to regen bestfree */ | 997 | int needscan; /* need to regen bestfree */ |
953 | xfs_dir2_data_unused_t *newdup; /* new unused entry */ | ||
954 | xfs_dir2_data_unused_t *newdup2; /* another new unused entry */ | ||
955 | int oldlen; /* old unused entry's length */ | 998 | int oldlen; /* old unused entry's length */ |
956 | struct xfs_dir2_data_free *bf; | ||
957 | 999 | ||
958 | hdr = bp->b_addr; | 1000 | hdr = bp->b_addr; |
959 | ASSERT(hdr->magic == cpu_to_be32(XFS_DIR2_DATA_MAGIC) || | 1001 | fa = xfs_dir2_data_check_free(hdr, dup, offset, len); |
960 | hdr->magic == cpu_to_be32(XFS_DIR3_DATA_MAGIC) || | 1002 | if (fa) |
961 | hdr->magic == cpu_to_be32(XFS_DIR2_BLOCK_MAGIC) || | 1003 | goto corrupt; |
962 | hdr->magic == cpu_to_be32(XFS_DIR3_BLOCK_MAGIC)); | ||
963 | ASSERT(be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG); | ||
964 | ASSERT(offset >= (char *)dup - (char *)hdr); | ||
965 | ASSERT(offset + len <= (char *)dup + be16_to_cpu(dup->length) - (char *)hdr); | ||
966 | ASSERT((char *)dup - (char *)hdr == be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup))); | ||
967 | /* | 1004 | /* |
968 | * Look up the entry in the bestfree table. | 1005 | * Look up the entry in the bestfree table. |
969 | */ | 1006 | */ |
@@ -1008,9 +1045,9 @@ xfs_dir2_data_use_free( | |||
1008 | xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp); | 1045 | xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp); |
1009 | dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup, | 1046 | dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup, |
1010 | needlogp); | 1047 | needlogp); |
1011 | ASSERT(dfp != NULL); | 1048 | fa = xfs_dir2_data_check_new_free(hdr, dfp, newdup); |
1012 | ASSERT(dfp->length == newdup->length); | 1049 | if (fa) |
1013 | ASSERT(be16_to_cpu(dfp->offset) == (char *)newdup - (char *)hdr); | 1050 | goto corrupt; |
1014 | /* | 1051 | /* |
1015 | * If we got inserted at the last slot, | 1052 | * If we got inserted at the last slot, |
1016 | * that means we don't know if there was a better | 1053 | * that means we don't know if there was a better |
@@ -1036,9 +1073,9 @@ xfs_dir2_data_use_free( | |||
1036 | xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp); | 1073 | xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp); |
1037 | dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup, | 1074 | dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup, |
1038 | needlogp); | 1075 | needlogp); |
1039 | ASSERT(dfp != NULL); | 1076 | fa = xfs_dir2_data_check_new_free(hdr, dfp, newdup); |
1040 | ASSERT(dfp->length == newdup->length); | 1077 | if (fa) |
1041 | ASSERT(be16_to_cpu(dfp->offset) == (char *)newdup - (char *)hdr); | 1078 | goto corrupt; |
1042 | /* | 1079 | /* |
1043 | * If we got inserted at the last slot, | 1080 | * If we got inserted at the last slot, |
1044 | * that means we don't know if there was a better | 1081 | * that means we don't know if there was a better |
@@ -1084,6 +1121,11 @@ xfs_dir2_data_use_free( | |||
1084 | } | 1121 | } |
1085 | } | 1122 | } |
1086 | *needscanp = needscan; | 1123 | *needscanp = needscan; |
1124 | return 0; | ||
1125 | corrupt: | ||
1126 | xfs_corruption_error(__func__, XFS_ERRLEVEL_LOW, args->dp->i_mount, | ||
1127 | hdr, __FILE__, __LINE__, fa); | ||
1128 | return -EFSCORRUPTED; | ||
1087 | } | 1129 | } |
1088 | 1130 | ||
1089 | /* Find the end of the entry data in a data/block format dir block. */ | 1131 | /* Find the end of the entry data in a data/block format dir block. */ |
diff --git a/fs/xfs/libxfs/xfs_dir2_leaf.c b/fs/xfs/libxfs/xfs_dir2_leaf.c index d61d52da95a1..50fc9c0c5e2b 100644 --- a/fs/xfs/libxfs/xfs_dir2_leaf.c +++ b/fs/xfs/libxfs/xfs_dir2_leaf.c | |||
@@ -877,9 +877,13 @@ xfs_dir2_leaf_addname( | |||
877 | /* | 877 | /* |
878 | * Mark the initial part of our freespace in use for the new entry. | 878 | * Mark the initial part of our freespace in use for the new entry. |
879 | */ | 879 | */ |
880 | xfs_dir2_data_use_free(args, dbp, dup, | 880 | error = xfs_dir2_data_use_free(args, dbp, dup, |
881 | (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), length, | 881 | (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), |
882 | &needlog, &needscan); | 882 | length, &needlog, &needscan); |
883 | if (error) { | ||
884 | xfs_trans_brelse(tp, lbp); | ||
885 | return error; | ||
886 | } | ||
883 | /* | 887 | /* |
884 | * Initialize our new entry (at last). | 888 | * Initialize our new entry (at last). |
885 | */ | 889 | */ |
diff --git a/fs/xfs/libxfs/xfs_dir2_node.c b/fs/xfs/libxfs/xfs_dir2_node.c index 0839ffe27e02..9df096cc3c37 100644 --- a/fs/xfs/libxfs/xfs_dir2_node.c +++ b/fs/xfs/libxfs/xfs_dir2_node.c | |||
@@ -1729,6 +1729,7 @@ xfs_dir2_node_addname_int( | |||
1729 | __be16 *bests; | 1729 | __be16 *bests; |
1730 | struct xfs_dir3_icfree_hdr freehdr; | 1730 | struct xfs_dir3_icfree_hdr freehdr; |
1731 | struct xfs_dir2_data_free *bf; | 1731 | struct xfs_dir2_data_free *bf; |
1732 | xfs_dir2_data_aoff_t aoff; | ||
1732 | 1733 | ||
1733 | dp = args->dp; | 1734 | dp = args->dp; |
1734 | mp = dp->i_mount; | 1735 | mp = dp->i_mount; |
@@ -2023,9 +2024,13 @@ xfs_dir2_node_addname_int( | |||
2023 | /* | 2024 | /* |
2024 | * Mark the first part of the unused space, inuse for us. | 2025 | * Mark the first part of the unused space, inuse for us. |
2025 | */ | 2026 | */ |
2026 | xfs_dir2_data_use_free(args, dbp, dup, | 2027 | aoff = (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr); |
2027 | (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), length, | 2028 | error = xfs_dir2_data_use_free(args, dbp, dup, aoff, length, |
2028 | &needlog, &needscan); | 2029 | &needlog, &needscan); |
2030 | if (error) { | ||
2031 | xfs_trans_brelse(tp, dbp); | ||
2032 | return error; | ||
2033 | } | ||
2029 | /* | 2034 | /* |
2030 | * Fill in the new entry and log it. | 2035 | * Fill in the new entry and log it. |
2031 | */ | 2036 | */ |