Commit 7f4c9906 authored by Konstantin Komarov's avatar Konstantin Komarov Committed by Yongjian Sun
Browse files

fs/ntfs3: Mark inode as bad as soon as error detected in mi_enum_attr()

mainline inclusion
from mainline-v6.12-rc3
commit 2afd4d267e6dbaec8d3ccd4f5396cb84bc67aa2e
category: bugfix
bugzilla: https://gitee.com/src-openeuler/kernel/issues/IBPC5A
CVE: CVE-2024-52560

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2afd4d267e6dbaec8d3ccd4f5396cb84bc67aa2e



--------------------------------

Extended the `mi_enum_attr()` function interface with an additional
parameter, `struct ntfs_inode *ni`, to allow marking the inode
as bad as soon as an error is detected.

Reported-by: default avatar <syzbot+73d8fc29ec7cba8286fa@syzkaller.appspotmail.com>
Signed-off-by: default avatarKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Conflicts:
		fs/ntfs3/record.c
[Context Changed]
Signed-off-by: default avatarYongjian Sun <sunyongjian1@huawei.com>
parent f030d539
Loading
Loading
Loading
Loading
+6 −5
Original line number Diff line number Diff line
@@ -788,7 +788,8 @@ int attr_set_size(struct ntfs_inode *ni, enum ATTR_TYPE type,
		if (err)
			goto out;

		attr = mi_find_attr(mi, NULL, type, name, name_len, &le->id);
		attr = mi_find_attr(ni, mi, NULL, type, name, name_len,
				    &le->id);
		if (!attr) {
			err = -EINVAL;
			goto bad_inode;
@@ -1182,7 +1183,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
			goto out;
		}

		attr = mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0, &le->id);
		attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0, &le->id);
		if (!attr) {
			err = -EINVAL;
			goto out;
@@ -1809,7 +1810,7 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
				goto out;
			}

			attr = mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0,
			attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0,
					    &le->id);
			if (!attr) {
				err = -EINVAL;
@@ -2054,8 +2055,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
				}

				/* Look for required attribute. */
				attr = mi_find_attr(mi, NULL, ATTR_DATA, NULL,
						    0, &le->id);
				attr = mi_find_attr(ni, mi, NULL, ATTR_DATA,
						    NULL, 0, &le->id);
				if (!attr) {
					err = -EINVAL;
					goto out;
+31 −28
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@ struct ATTR_STD_INFO *ni_std(struct ntfs_inode *ni)
{
	const struct ATTRIB *attr;

	attr = mi_find_attr(&ni->mi, NULL, ATTR_STD, NULL, 0, NULL);
	attr = mi_find_attr(ni, &ni->mi, NULL, ATTR_STD, NULL, 0, NULL);
	return attr ? resident_data_ex(attr, sizeof(struct ATTR_STD_INFO)) :
		      NULL;
}
@@ -89,7 +89,7 @@ struct ATTR_STD_INFO5 *ni_std5(struct ntfs_inode *ni)
{
	const struct ATTRIB *attr;

	attr = mi_find_attr(&ni->mi, NULL, ATTR_STD, NULL, 0, NULL);
	attr = mi_find_attr(ni, &ni->mi, NULL, ATTR_STD, NULL, 0, NULL);

	return attr ? resident_data_ex(attr, sizeof(struct ATTR_STD_INFO5)) :
		      NULL;
@@ -201,7 +201,8 @@ struct ATTRIB *ni_find_attr(struct ntfs_inode *ni, struct ATTRIB *attr,
			*mi = &ni->mi;

		/* Look for required attribute in primary record. */
		return mi_find_attr(&ni->mi, attr, type, name, name_len, NULL);
		return mi_find_attr(ni, &ni->mi, attr, type, name, name_len,
				    NULL);
	}

	/* First look for list entry of required type. */
@@ -217,7 +218,7 @@ struct ATTRIB *ni_find_attr(struct ntfs_inode *ni, struct ATTRIB *attr,
		return NULL;

	/* Look for required attribute. */
	attr = mi_find_attr(m, NULL, type, name, name_len, &le->id);
	attr = mi_find_attr(ni, m, NULL, type, name, name_len, &le->id);

	if (!attr)
		goto out;
@@ -259,7 +260,7 @@ struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr,
		if (mi)
			*mi = &ni->mi;
		/* Enum attributes in primary record. */
		return mi_enum_attr(&ni->mi, attr);
		return mi_enum_attr(ni, &ni->mi, attr);
	}

	/* Get next list entry. */
@@ -275,7 +276,7 @@ struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr,
		*mi = mi2;

	/* Find attribute in loaded record. */
	return rec_find_attr_le(mi2, le2);
	return rec_find_attr_le(ni, mi2, le2);
}

/*
@@ -293,7 +294,8 @@ struct ATTRIB *ni_load_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
	if (!ni->attr_list.size) {
		if (pmi)
			*pmi = &ni->mi;
		return mi_find_attr(&ni->mi, NULL, type, name, name_len, NULL);
		return mi_find_attr(ni, &ni->mi, NULL, type, name, name_len,
				    NULL);
	}

	le = al_find_ex(ni, NULL, type, name, name_len, NULL);
@@ -319,7 +321,7 @@ struct ATTRIB *ni_load_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
	if (pmi)
		*pmi = mi;

	attr = mi_find_attr(mi, NULL, type, name, name_len, &le->id);
	attr = mi_find_attr(ni, mi, NULL, type, name, name_len, &le->id);
	if (!attr)
		return NULL;

@@ -398,7 +400,8 @@ int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
	int diff;

	if (base_only || type == ATTR_LIST || !ni->attr_list.size) {
		attr = mi_find_attr(&ni->mi, NULL, type, name, name_len, id);
		attr = mi_find_attr(ni, &ni->mi, NULL, type, name, name_len,
				    id);
		if (!attr)
			return -ENOENT;

@@ -437,7 +440,7 @@ int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,

		al_remove_le(ni, le);

		attr = mi_find_attr(mi, NULL, type, name, name_len, id);
		attr = mi_find_attr(ni, mi, NULL, type, name, name_len, id);
		if (!attr)
			return -ENOENT;

@@ -485,7 +488,7 @@ ni_ins_new_attr(struct ntfs_inode *ni, struct mft_inode *mi,
		name = le->name;
	}

	attr = mi_insert_attr(mi, type, name, name_len, asize, name_off);
	attr = mi_insert_attr(ni, mi, type, name, name_len, asize, name_off);
	if (!attr) {
		if (le_added)
			al_remove_le(ni, le);
@@ -673,7 +676,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
	if (err)
		return err;

	attr_list = mi_find_attr(&ni->mi, NULL, ATTR_LIST, NULL, 0, NULL);
	attr_list = mi_find_attr(ni, &ni->mi, NULL, ATTR_LIST, NULL, 0, NULL);
	if (!attr_list)
		return 0;

@@ -695,7 +698,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
		if (!mi)
			return 0;

		attr = mi_find_attr(mi, NULL, le->type, le_name(le),
		attr = mi_find_attr(ni, mi, NULL, le->type, le_name(le),
				    le->name_len, &le->id);
		if (!attr)
			return 0;
@@ -731,7 +734,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
			goto out;
		}

		attr = mi_find_attr(mi, NULL, le->type, le_name(le),
		attr = mi_find_attr(ni, mi, NULL, le->type, le_name(le),
				    le->name_len, &le->id);
		if (!attr) {
			/* Should never happened, 'cause already checked. */
@@ -740,7 +743,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
		asize = le32_to_cpu(attr->size);

		/* Insert into primary record. */
		attr_ins = mi_insert_attr(&ni->mi, le->type, le_name(le),
		attr_ins = mi_insert_attr(ni, &ni->mi, le->type, le_name(le),
					  le->name_len, asize,
					  le16_to_cpu(attr->name_off));
		if (!attr_ins) {
@@ -768,7 +771,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
		if (!mi)
			continue;

		attr = mi_find_attr(mi, NULL, le->type, le_name(le),
		attr = mi_find_attr(ni, mi, NULL, le->type, le_name(le),
				    le->name_len, &le->id);
		if (!attr)
			continue;
@@ -831,7 +834,7 @@ int ni_create_attr_list(struct ntfs_inode *ni)
	free_b = 0;
	attr = NULL;

	for (; (attr = mi_enum_attr(&ni->mi, attr)); le = Add2Ptr(le, sz)) {
	for (; (attr = mi_enum_attr(ni, &ni->mi, attr)); le = Add2Ptr(le, sz)) {
		sz = le_size(attr->name_len);
		le->type = attr->type;
		le->size = cpu_to_le16(sz);
@@ -886,7 +889,7 @@ int ni_create_attr_list(struct ntfs_inode *ni)
		u32 asize = le32_to_cpu(b->size);
		u16 name_off = le16_to_cpu(b->name_off);

		attr = mi_insert_attr(mi, b->type, Add2Ptr(b, name_off),
		attr = mi_insert_attr(ni, mi, b->type, Add2Ptr(b, name_off),
				      b->name_len, asize, name_off);
		if (!attr)
			goto out;
@@ -909,7 +912,7 @@ int ni_create_attr_list(struct ntfs_inode *ni)
			goto out;
	}

	attr = mi_insert_attr(&ni->mi, ATTR_LIST, NULL, 0,
	attr = mi_insert_attr(ni, &ni->mi, ATTR_LIST, NULL, 0,
			      lsize + SIZEOF_RESIDENT, SIZEOF_RESIDENT);
	if (!attr)
		goto out;
@@ -993,13 +996,13 @@ static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
		mi = rb_entry(node, struct mft_inode, node);

		if (is_mft_data &&
		    (mi_enum_attr(mi, NULL) ||
		    (mi_enum_attr(ni, mi, NULL) ||
		     vbo <= ((u64)mi->rno << sbi->record_bits))) {
			/* We can't accept this record 'cause MFT's bootstrapping. */
			continue;
		}
		if (is_mft &&
		    mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0, NULL)) {
		    mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0, NULL)) {
			/*
			 * This child record already has a ATTR_DATA.
			 * So it can't accept any other records.
@@ -1008,7 +1011,7 @@ static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
		}

		if ((type != ATTR_NAME || name_len) &&
		    mi_find_attr(mi, NULL, type, name, name_len, NULL)) {
		    mi_find_attr(ni, mi, NULL, type, name, name_len, NULL)) {
			/* Only indexed attributes can share same record. */
			continue;
		}
@@ -1157,7 +1160,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
	/* Estimate the result of moving all possible attributes away. */
	attr = NULL;

	while ((attr = mi_enum_attr(&ni->mi, attr))) {
	while ((attr = mi_enum_attr(ni, &ni->mi, attr))) {
		if (attr->type == ATTR_STD)
			continue;
		if (attr->type == ATTR_LIST)
@@ -1175,7 +1178,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
	attr = NULL;

	for (;;) {
		attr = mi_enum_attr(&ni->mi, attr);
		attr = mi_enum_attr(ni, &ni->mi, attr);
		if (!attr) {
			/* We should never be here 'cause we have already check this case. */
			err = -EINVAL;
@@ -1259,7 +1262,7 @@ static int ni_expand_mft_list(struct ntfs_inode *ni)
	for (node = rb_first(&ni->mi_tree); node; node = rb_next(node)) {
		mi = rb_entry(node, struct mft_inode, node);

		attr = mi_enum_attr(mi, NULL);
		attr = mi_enum_attr(ni, mi, NULL);

		if (!attr) {
			mft_min = mi->rno;
@@ -1280,7 +1283,7 @@ static int ni_expand_mft_list(struct ntfs_inode *ni)
		ni_remove_mi(ni, mi_new);
	}

	attr = mi_find_attr(&ni->mi, NULL, ATTR_DATA, NULL, 0, NULL);
	attr = mi_find_attr(ni, &ni->mi, NULL, ATTR_DATA, NULL, 0, NULL);
	if (!attr) {
		err = -EINVAL;
		goto out;
@@ -1397,7 +1400,7 @@ int ni_expand_list(struct ntfs_inode *ni)
			continue;

		/* Find attribute in primary record. */
		attr = rec_find_attr_le(&ni->mi, le);
		attr = rec_find_attr_le(ni, &ni->mi, le);
		if (!attr) {
			err = -EINVAL;
			goto out;
@@ -3344,7 +3347,7 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint)
		if (!mi->dirty)
			continue;

		is_empty = !mi_enum_attr(mi, NULL);
		is_empty = !mi_enum_attr(ni, mi, NULL);

		if (is_empty)
			clear_rec_inuse(mi->mrec);
+11 −10
Original line number Diff line number Diff line
@@ -739,23 +739,24 @@ int mi_get(struct ntfs_sb_info *sbi, CLST rno, struct mft_inode **mi);
void mi_put(struct mft_inode *mi);
int mi_init(struct mft_inode *mi, struct ntfs_sb_info *sbi, CLST rno);
int mi_read(struct mft_inode *mi, bool is_mft);
struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr);
// TODO: id?
struct ATTRIB *mi_find_attr(struct mft_inode *mi, struct ATTRIB *attr,
			    enum ATTR_TYPE type, const __le16 *name,
			    u8 name_len, const __le16 *id);
static inline struct ATTRIB *rec_find_attr_le(struct mft_inode *rec,
struct ATTRIB *mi_enum_attr(struct ntfs_inode *ni, struct mft_inode *mi,
			    struct ATTRIB *attr);
struct ATTRIB *mi_find_attr(struct ntfs_inode *ni, struct mft_inode *mi,
			    struct ATTRIB *attr, enum ATTR_TYPE type,
			    const __le16 *name, u8 name_len, const __le16 *id);
static inline struct ATTRIB *rec_find_attr_le(struct ntfs_inode *ni,
					      struct mft_inode *rec,
					      struct ATTR_LIST_ENTRY *le)
{
	return mi_find_attr(rec, NULL, le->type, le_name(le), le->name_len,
	return mi_find_attr(ni, rec, NULL, le->type, le_name(le), le->name_len,
			    &le->id);
}
int mi_write(struct mft_inode *mi, int wait);
int mi_format_new(struct mft_inode *mi, struct ntfs_sb_info *sbi, CLST rno,
		  __le16 flags, bool is_mft);
struct ATTRIB *mi_insert_attr(struct mft_inode *mi, enum ATTR_TYPE type,
			      const __le16 *name, u8 name_len, u32 asize,
			      u16 name_off);
struct ATTRIB *mi_insert_attr(struct ntfs_inode *ni, struct mft_inode *mi,
			      enum ATTR_TYPE type, const __le16 *name,
			      u8 name_len, u32 asize, u16 name_off);

bool mi_remove_attr(struct ntfs_inode *ni, struct mft_inode *mi,
		    struct ATTRIB *attr);
+40 −35
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@ static inline int compare_attr(const struct ATTRIB *left, enum ATTR_TYPE type,
 *
 * Return: Unused attribute id that is less than mrec->next_attr_id.
 */
static __le16 mi_new_attt_id(struct mft_inode *mi)
static __le16 mi_new_attt_id(struct ntfs_inode *ni, struct mft_inode *mi)
{
	u16 free_id, max_id, t16;
	struct MFT_REC *rec = mi->mrec;
@@ -52,7 +52,7 @@ static __le16 mi_new_attt_id(struct mft_inode *mi)
	attr = NULL;

	for (;;) {
		attr = mi_enum_attr(mi, attr);
		attr = mi_enum_attr(ni, mi, attr);
		if (!attr) {
			rec->next_attr_id = cpu_to_le16(max_id + 1);
			mi->dirty = true;
@@ -195,7 +195,8 @@ int mi_read(struct mft_inode *mi, bool is_mft)
 * NOTE: mi->mrec - memory of size sbi->record_size
 * here we sure that mi->mrec->total == sbi->record_size (see mi_read)
 */
struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
struct ATTRIB *mi_enum_attr(struct ntfs_inode *ni, struct mft_inode *mi,
			    struct ATTRIB *attr)
{
	const struct MFT_REC *rec = mi->mrec;
	u32 used = le32_to_cpu(rec->used);
@@ -209,11 +210,11 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
		off = le16_to_cpu(rec->attr_off);

		if (used > total)
			return NULL;
			goto out;

		if (off >= used || off < MFTRECORD_FIXUP_OFFSET_1 ||
		    !IS_ALIGNED(off, 4)) {
			return NULL;
			goto out;
		}

		/* Skip non-resident records. */
@@ -240,7 +241,7 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
	/* NOTE: this code also checks attr->size availability. */
	if (off + 8 > used) {
		static_assert(ALIGN(sizeof(enum ATTR_TYPE), 8) == 8);
		return NULL;
		goto out;
	}

	if (attr->type == ATTR_END) {
@@ -251,105 +252,109 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
	/* 0x100 is last known attribute for now. */
	t32 = le32_to_cpu(attr->type);
	if (!t32 || (t32 & 0xf) || (t32 > 0x100))
		return NULL;
		goto out;

	/* attributes in record must be ordered by type */
	if (t32 < prev_type)
		return NULL;
		goto out;

	asize = le32_to_cpu(attr->size);

	/* Check overflow and boundary. */
	if (off + asize < off || off + asize > used)
		return NULL;
		goto out;

	/* Check size of attribute. */
	if (!attr->non_res) {
		/* Check resident fields. */
		if (asize < SIZEOF_RESIDENT)
			return NULL;
			goto out;

		t16 = le16_to_cpu(attr->res.data_off);
		if (t16 > asize)
			return NULL;
			goto out;

		if (le32_to_cpu(attr->res.data_size) > asize - t16)
			return NULL;
			goto out;

		t32 = sizeof(short) * attr->name_len;
		if (t32 && le16_to_cpu(attr->name_off) + t32 > t16)
			return NULL;
			goto out;

		return attr;
	}

	/* Check nonresident fields. */
	if (attr->non_res != 1)
		return NULL;
		goto out;

	/* Can we use memory including attr->nres.valid_size? */
	if (asize < SIZEOF_NONRESIDENT)
		return NULL;
		goto out;

	t16 = le16_to_cpu(attr->nres.run_off);
	if (t16 > asize)
		return NULL;
		goto out;

	t32 = sizeof(short) * attr->name_len;
	if (t32 && le16_to_cpu(attr->name_off) + t32 > t16)
		return NULL;
		goto out;

	/* Check start/end vcn. */
	if (le64_to_cpu(attr->nres.svcn) > le64_to_cpu(attr->nres.evcn) + 1)
		return NULL;
		goto out;

	data_size = le64_to_cpu(attr->nres.data_size);
	if (le64_to_cpu(attr->nres.valid_size) > data_size)
		return NULL;
		goto out;

	alloc_size = le64_to_cpu(attr->nres.alloc_size);
	if (data_size > alloc_size)
		return NULL;
		goto out;

	t32 = mi->sbi->cluster_mask;
	if (alloc_size & t32)
		return NULL;
		goto out;

	if (!attr->nres.svcn && is_attr_ext(attr)) {
		/* First segment of sparse/compressed attribute */
		/* Can we use memory including attr->nres.total_size? */
		if (asize < SIZEOF_NONRESIDENT_EX)
			return NULL;
			goto out;

		tot_size = le64_to_cpu(attr->nres.total_size);
		if (tot_size & t32)
			return NULL;
			goto out;

		if (tot_size > alloc_size)
			return NULL;
			goto out;
	} else {
		if (attr->nres.c_unit)
			return NULL;
			goto out;

		if (alloc_size > mi->sbi->volume.size)
			return NULL;
			goto out;
	}

	return attr;

out:
	_ntfs_bad_inode(&ni->vfs_inode);
	return NULL;
}

/*
 * mi_find_attr - Find the attribute by type and name and id.
 */
struct ATTRIB *mi_find_attr(struct mft_inode *mi, struct ATTRIB *attr,
			    enum ATTR_TYPE type, const __le16 *name,
			    u8 name_len, const __le16 *id)
struct ATTRIB *mi_find_attr(struct ntfs_inode *ni, struct mft_inode *mi,
			    struct ATTRIB *attr, enum ATTR_TYPE type,
			    const __le16 *name, u8 name_len, const __le16 *id)
{
	u32 type_in = le32_to_cpu(type);
	u32 atype;

next_attr:
	attr = mi_enum_attr(mi, attr);
	attr = mi_enum_attr(ni, mi, attr);
	if (!attr)
		return NULL;

@@ -457,9 +462,9 @@ int mi_format_new(struct mft_inode *mi, struct ntfs_sb_info *sbi, CLST rno,
 *
 * Return: Not full constructed attribute or NULL if not possible to create.
 */
struct ATTRIB *mi_insert_attr(struct mft_inode *mi, enum ATTR_TYPE type,
			      const __le16 *name, u8 name_len, u32 asize,
			      u16 name_off)
struct ATTRIB *mi_insert_attr(struct ntfs_inode *ni, struct mft_inode *mi,
			      enum ATTR_TYPE type, const __le16 *name,
			      u8 name_len, u32 asize, u16 name_off)
{
	size_t tail;
	struct ATTRIB *attr;
@@ -478,7 +483,7 @@ struct ATTRIB *mi_insert_attr(struct mft_inode *mi, enum ATTR_TYPE type,
	 * at which we should insert it.
	 */
	attr = NULL;
	while ((attr = mi_enum_attr(mi, attr))) {
	while ((attr = mi_enum_attr(ni, mi, attr))) {
		int diff = compare_attr(attr, type, name, name_len, upcase);

		if (diff < 0)
@@ -498,7 +503,7 @@ struct ATTRIB *mi_insert_attr(struct mft_inode *mi, enum ATTR_TYPE type,
		tail = used - PtrOffset(rec, attr);
	}

	id = mi_new_attt_id(mi);
	id = mi_new_attt_id(ni, mi);

	memmove(Add2Ptr(attr, asize), attr, tail);
	memset(attr, 0, asize);