Skip to content

Commit 51cd9a1

Browse files
hclee1Naim
authored andcommitted
ntfs: add bound checking to ntfs_external_attr_find
Add bound validation in ntfs_external_attr_find to prevent out-of-bounds memory accesses. This ensures that the attribute record's length, name offset, and both resident and non-resident value offsets strictly fall within the safe boundaries of the MFT record. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
1 parent 8f1918f commit 51cd9a1

1 file changed

Lines changed: 69 additions & 14 deletions

File tree

fs/ntfs/attrib.c

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ static int ntfs_external_attr_find(const __le32 type,
946946
struct attr_record *a;
947947
__le16 *al_name;
948948
u32 al_name_len;
949+
u32 attr_len, mft_free_len;
949950
bool is_first_search = false;
950951
int err = 0;
951952
static const char *es = " Unmount and run chkdsk.";
@@ -1209,13 +1210,23 @@ static int ntfs_external_attr_find(const __le32 type,
12091210
* with the same meanings as above.
12101211
*/
12111212
do_next_attr_loop:
1212-
if ((u8 *)a < (u8 *)ctx->mrec || (u8 *)a > (u8 *)ctx->mrec +
1213-
le32_to_cpu(ctx->mrec->bytes_allocated))
1213+
if ((u8 *)a < (u8 *)ctx->mrec ||
1214+
(u8 *)a >= (u8 *)ctx->mrec + le32_to_cpu(ctx->mrec->bytes_allocated) ||
1215+
(u8 *)a >= (u8 *)ctx->mrec + le32_to_cpu(ctx->mrec->bytes_in_use))
12141216
break;
1215-
if (a->type == AT_END)
1217+
1218+
mft_free_len = le32_to_cpu(ctx->mrec->bytes_in_use) -
1219+
((u8 *)a - (u8 *)ctx->mrec);
1220+
if (mft_free_len >= sizeof(a->type) && a->type == AT_END)
12161221
continue;
1217-
if (!a->length)
1222+
1223+
attr_len = le32_to_cpu(a->length);
1224+
if (!attr_len ||
1225+
attr_len < offsetof(struct attr_record, data.resident.reserved) +
1226+
sizeof(a->data.resident.reserved) ||
1227+
attr_len > mft_free_len)
12181228
break;
1229+
12191230
if (al_entry->instance != a->instance)
12201231
goto do_next_attr;
12211232
/*
@@ -1225,27 +1236,67 @@ static int ntfs_external_attr_find(const __le32 type,
12251236
*/
12261237
if (al_entry->type != a->type)
12271238
break;
1239+
if (a->name_length && ((le16_to_cpu(a->name_offset) +
1240+
a->name_length * sizeof(__le16)) > attr_len))
1241+
break;
12281242
if (!ntfs_are_names_equal((__le16 *)((u8 *)a +
12291243
le16_to_cpu(a->name_offset)), a->name_length,
12301244
al_name, al_name_len, CASE_SENSITIVE,
12311245
vol->upcase, vol->upcase_len))
12321246
break;
1247+
12331248
ctx->attr = a;
1249+
1250+
if (a->non_resident) {
1251+
u32 min_len;
1252+
u16 mp_offset;
1253+
1254+
min_len = offsetof(struct attr_record,
1255+
data.non_resident.initialized_size) +
1256+
sizeof(a->data.non_resident.initialized_size);
1257+
1258+
if (le32_to_cpu(a->length) < min_len)
1259+
break;
1260+
1261+
mp_offset =
1262+
le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
1263+
if (mp_offset < min_len || mp_offset > attr_len)
1264+
break;
1265+
}
1266+
12341267
/*
12351268
* If no @val specified or @val specified and it matches, we
12361269
* have found it!
12371270
*/
1238-
if ((type == AT_UNUSED) || !val || (!a->non_resident && le32_to_cpu(
1239-
a->data.resident.value_length) == val_len &&
1240-
!memcmp((u8 *)a +
1241-
le16_to_cpu(a->data.resident.value_offset),
1242-
val, val_len))) {
1243-
ntfs_debug("Done, found.");
1244-
return 0;
1271+
if ((type == AT_UNUSED) || !val)
1272+
goto attr_found;
1273+
if (!a->non_resident) {
1274+
u32 value_length = le32_to_cpu(a->data.resident.value_length);
1275+
u16 value_offset = le16_to_cpu(a->data.resident.value_offset);
1276+
1277+
if (attr_len < offsetof(struct attr_record, data.resident.reserved) +
1278+
sizeof(a->data.resident.reserved))
1279+
break;
1280+
if (value_length > attr_len || value_offset > attr_len - value_length)
1281+
break;
1282+
1283+
value_length = ntfs_resident_attr_min_value_length(a->type);
1284+
if (value_length && le32_to_cpu(a->data.resident.value_length) <
1285+
value_length) {
1286+
pr_err("Too small resident attribute value in MFT record %lld, type %#x\n",
1287+
(long long)ctx->ntfs_ino->mft_no, a->type);
1288+
break;
1289+
}
1290+
if (value_length == val_len &&
1291+
!memcmp((u8 *)a + value_offset, val, val_len)) {
1292+
attr_found:
1293+
ntfs_debug("Done, found.");
1294+
return 0;
1295+
}
12451296
}
12461297
do_next_attr:
12471298
/* Proceed to the next attribute in the current mft record. */
1248-
a = (struct attr_record *)((u8 *)a + le32_to_cpu(a->length));
1299+
a = (struct attr_record *)((u8 *)a + attr_len);
12491300
goto do_next_attr_loop;
12501301
}
12511302

@@ -1260,9 +1311,13 @@ static int ntfs_external_attr_find(const __le32 type,
12601311
}
12611312

12621313
if (!err) {
1314+
u64 mft_no = ctx->al_entry ? MREF_LE(ctx->al_entry->mft_reference) : 0;
1315+
u32 type = ctx->al_entry ? le32_to_cpu(ctx->al_entry->type) : 0;
1316+
12631317
ntfs_error(vol->sb,
1264-
"Base inode 0x%llx contains corrupt attribute list attribute.%s",
1265-
base_ni->mft_no, es);
1318+
"Base inode 0x%llx contains corrupt attribute, mft %#llx, type %#x. %s",
1319+
(long long)base_ni->mft_no, (long long)mft_no, type,
1320+
"Unmount and run chkdsk.");
12661321
err = -EIO;
12671322
}
12681323

0 commit comments

Comments
 (0)