From: Hyunchul Lee Date: Mon, 16 Mar 2026 04:28:34 +0000 (+0900) Subject: ntfs: add bound checking to ntfs_external_attr_find X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a198a0c4b898f7c62f240a5b6baef93e456fb033;p=thirdparty%2Fkernel%2Flinux.git 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 Signed-off-by: Namjae Jeon --- diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 8aa889953e2a..78915c1d5128 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -946,6 +946,7 @@ static int ntfs_external_attr_find(const __le32 type, struct attr_record *a; __le16 *al_name; u32 al_name_len; + u32 attr_len, mft_free_len; bool is_first_search = false; int err = 0; static const char *es = " Unmount and run chkdsk."; @@ -1209,13 +1210,23 @@ is_enumeration: * with the same meanings as above. */ do_next_attr_loop: - if ((u8 *)a < (u8 *)ctx->mrec || (u8 *)a > (u8 *)ctx->mrec + - le32_to_cpu(ctx->mrec->bytes_allocated)) + if ((u8 *)a < (u8 *)ctx->mrec || + (u8 *)a >= (u8 *)ctx->mrec + le32_to_cpu(ctx->mrec->bytes_allocated) || + (u8 *)a >= (u8 *)ctx->mrec + le32_to_cpu(ctx->mrec->bytes_in_use)) break; - if (a->type == AT_END) + + mft_free_len = le32_to_cpu(ctx->mrec->bytes_in_use) - + ((u8 *)a - (u8 *)ctx->mrec); + if (mft_free_len >= sizeof(a->type) && a->type == AT_END) continue; - if (!a->length) + + attr_len = le32_to_cpu(a->length); + if (!attr_len || + attr_len < offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved) || + attr_len > mft_free_len) break; + if (al_entry->instance != a->instance) goto do_next_attr; /* @@ -1225,27 +1236,67 @@ do_next_attr_loop: */ if (al_entry->type != a->type) break; + if (a->name_length && ((le16_to_cpu(a->name_offset) + + a->name_length * sizeof(__le16)) > attr_len)) + break; if (!ntfs_are_names_equal((__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), a->name_length, al_name, al_name_len, CASE_SENSITIVE, vol->upcase, vol->upcase_len)) break; + ctx->attr = a; + + if (a->non_resident) { + u32 min_len; + u16 mp_offset; + + min_len = offsetof(struct attr_record, + data.non_resident.initialized_size) + + sizeof(a->data.non_resident.initialized_size); + + if (le32_to_cpu(a->length) < min_len) + break; + + mp_offset = + le16_to_cpu(a->data.non_resident.mapping_pairs_offset); + if (mp_offset < min_len || mp_offset > attr_len) + break; + } + /* * If no @val specified or @val specified and it matches, we * have found it! */ - if ((type == AT_UNUSED) || !val || (!a->non_resident && le32_to_cpu( - a->data.resident.value_length) == val_len && - !memcmp((u8 *)a + - le16_to_cpu(a->data.resident.value_offset), - val, val_len))) { - ntfs_debug("Done, found."); - return 0; + if ((type == AT_UNUSED) || !val) + goto attr_found; + if (!a->non_resident) { + u32 value_length = le32_to_cpu(a->data.resident.value_length); + u16 value_offset = le16_to_cpu(a->data.resident.value_offset); + + if (attr_len < offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved)) + break; + if (value_length > attr_len || value_offset > attr_len - value_length) + break; + + value_length = ntfs_resident_attr_min_value_length(a->type); + if (value_length && le32_to_cpu(a->data.resident.value_length) < + value_length) { + pr_err("Too small resident attribute value in MFT record %lld, type %#x\n", + (long long)ctx->ntfs_ino->mft_no, a->type); + break; + } + if (value_length == val_len && + !memcmp((u8 *)a + value_offset, val, val_len)) { +attr_found: + ntfs_debug("Done, found."); + return 0; + } } do_next_attr: /* Proceed to the next attribute in the current mft record. */ - a = (struct attr_record *)((u8 *)a + le32_to_cpu(a->length)); + a = (struct attr_record *)((u8 *)a + attr_len); goto do_next_attr_loop; } @@ -1260,9 +1311,13 @@ corrupt: } if (!err) { + u64 mft_no = ctx->al_entry ? MREF_LE(ctx->al_entry->mft_reference) : 0; + u32 type = ctx->al_entry ? le32_to_cpu(ctx->al_entry->type) : 0; + ntfs_error(vol->sb, - "Base inode 0x%llx contains corrupt attribute list attribute.%s", - base_ni->mft_no, es); + "Base inode 0x%llx contains corrupt attribute, mft %#llx, type %#x. %s", + (long long)base_ni->mft_no, (long long)mft_no, type, + "Unmount and run chkdsk."); err = -EIO; }