diff options
| author | Hyunchul Lee <hyc.lee@gmail.com> | 2026-03-12 04:23:46 +0300 |
|---|---|---|
| committer | Namjae Jeon <linkinjeon@kernel.org> | 2026-04-07 12:36:05 +0300 |
| commit | 6ceb4cc81ef3409ff79dcb959771f9110787397a (patch) | |
| tree | 2a69fd7c73c89489bf6810adafef094f8b499341 | |
| parent | 77f58db7391e227f8ff6ef5d83966347d759daed (diff) | |
| download | linux-6ceb4cc81ef3409ff79dcb959771f9110787397a.tar.xz | |
ntfs: add bound checking to ntfs_attr_find
Add bound validations in ntfs_attr_find to ensure
attribute value offsets and lengths are safe to
access. It verifies that resident attributes meet
type-specific minimum length requirements and
check the mapping_pairs_offset boundaries for
non-resident attributes.
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
| -rw-r--r-- | fs/ntfs/attrib.c | 80 | ||||
| -rw-r--r-- | fs/ntfs/reparse.c | 4 |
2 files changed, 72 insertions, 12 deletions
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 1477dbd3af82..8aa889953e2a 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -570,6 +570,35 @@ retry_remap: return ERR_PTR(err); } +static u32 ntfs_resident_attr_min_value_length(const __le32 type) +{ + switch (type) { + case AT_STANDARD_INFORMATION: + return offsetof(struct standard_information, ver) + + sizeof(((struct standard_information *)0)->ver.v1.reserved12); + case AT_ATTRIBUTE_LIST: + return offsetof(struct attr_list_entry, name); + case AT_FILE_NAME: + return offsetof(struct file_name_attr, file_name); + case AT_OBJECT_ID: + return sizeof(struct guid); + case AT_SECURITY_DESCRIPTOR: + return sizeof(struct security_descriptor_relative); + case AT_VOLUME_INFORMATION: + return sizeof(struct volume_information); + case AT_INDEX_ROOT: + return sizeof(struct index_root); + case AT_REPARSE_POINT: + return offsetof(struct reparse_point, reparse_data); + case AT_EA_INFORMATION: + return sizeof(struct ea_information); + case AT_EA: + return offsetof(struct ea_attr, ea_name) + 1; + default: + return 0; + } +} + /* * ntfs_attr_find - find (next) attribute in mft record * @type: attribute type to find @@ -712,38 +741,69 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name, continue; } } + + /* Validate attribute's value offset/length */ + if (!a->non_resident) { + u32 min_len; + u32 value_length = le32_to_cpu(a->data.resident.value_length); + u16 value_offset = le16_to_cpu(a->data.resident.value_offset); + + if (value_length > le32_to_cpu(a->length) || + value_offset > le32_to_cpu(a->length) - value_length) + break; + + min_len = ntfs_resident_attr_min_value_length(a->type); + if (min_len && value_length < min_len) { + ntfs_error(vol->sb, + "Too small %#x resident attribute value in MFT record %lld\n", + le32_to_cpu(a->type), (long long)ctx->ntfs_ino->mft_no); + break; + } + } else { + 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 > le32_to_cpu(a->length)) + break; + } + /* * The names match or @name not present and attribute is * unnamed. If no @val specified, we have found the attribute * and are done. */ - if (!val) + if (!val || a->non_resident) return 0; /* @val is present; compare values. */ else { - register int rc; + u32 value_length = le32_to_cpu(a->data.resident.value_length); + int rc; rc = memcmp(val, (u8 *)a + le16_to_cpu( a->data.resident.value_offset), - min_t(u32, val_len, le32_to_cpu( - a->data.resident.value_length))); + min_t(u32, val_len, value_length)); /* * If @val collates before the current attribute's * value, there is no matching attribute. */ if (!rc) { - register u32 avl; - - avl = le32_to_cpu(a->data.resident.value_length); - if (val_len == avl) + if (val_len == value_length) return 0; - if (val_len < avl) + if (val_len < value_length) return -ENOENT; } else if (rc < 0) return -ENOENT; } } - ntfs_error(vol->sb, "Inode is corrupt. Run chkdsk."); + ntfs_error(vol->sb, "mft %#llx, type %#x is corrupt. Run chkdsk.", + (long long)ctx->ntfs_ino->mft_no, le32_to_cpu(type)); NVolSetErrors(vol); return -EIO; } diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 4cddd918defc..8f60ec6f66c1 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -450,7 +450,7 @@ static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t xrni = xr->idx_ni; if (!ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED, 0)) { - u8 dummy = 0; + struct reparse_point rp = {0, }; /* * no reparse data attribute : add one, @@ -463,7 +463,7 @@ static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t goto out; } - err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, &dummy, 0); + err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, (u8 *)&rp, sizeof(rp)); if (err) { ntfs_index_ctx_put(xr); goto out; |
