linux/fs/ntfs3/record.c
Edward Lo 277439e7ca fs/ntfs3: Enhance the attribute size check
commit 4f082a7531 upstream.

This combines the overflow and boundary check so that all attribute size
will be properly examined while enumerating them.

[  169.181521] BUG: KASAN: slab-out-of-bounds in run_unpack+0x2e3/0x570
[  169.183161] Read of size 1 at addr ffff8880094b6240 by task mount/247
[  169.184046]
[  169.184925] CPU: 0 PID: 247 Comm: mount Not tainted 6.0.0-rc7+ #3
[  169.185908] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014
[  169.187066] Call Trace:
[  169.187492]  <TASK>
[  169.188049]  dump_stack_lvl+0x49/0x63
[  169.188495]  print_report.cold+0xf5/0x689
[  169.188964]  ? run_unpack+0x2e3/0x570
[  169.189331]  kasan_report+0xa7/0x130
[  169.189714]  ? run_unpack+0x2e3/0x570
[  169.190079]  __asan_load1+0x51/0x60
[  169.190634]  run_unpack+0x2e3/0x570
[  169.191290]  ? run_pack+0x840/0x840
[  169.191569]  ? run_lookup_entry+0xb3/0x1f0
[  169.192443]  ? mi_enum_attr+0x20a/0x230
[  169.192886]  run_unpack_ex+0xad/0x3e0
[  169.193276]  ? run_unpack+0x570/0x570
[  169.193557]  ? ni_load_mi+0x80/0x80
[  169.193889]  ? debug_smp_processor_id+0x17/0x20
[  169.194236]  ? mi_init+0x4a/0x70
[  169.194496]  attr_load_runs_vcn+0x166/0x1c0
[  169.194851]  ? attr_data_write_resident+0x250/0x250
[  169.195188]  mi_read+0x133/0x2c0
[  169.195481]  ntfs_iget5+0x277/0x1780
[  169.196017]  ? call_rcu+0x1c7/0x330
[  169.196392]  ? ntfs_get_block_bmap+0x70/0x70
[  169.196708]  ? evict+0x223/0x280
[  169.197014]  ? __kmalloc+0x33/0x540
[  169.197305]  ? wnd_init+0x15b/0x1b0
[  169.197599]  ntfs_fill_super+0x1026/0x1ba0
[  169.197994]  ? put_ntfs+0x1d0/0x1d0
[  169.198299]  ? vsprintf+0x20/0x20
[  169.198583]  ? mutex_unlock+0x81/0xd0
[  169.198930]  ? set_blocksize+0x95/0x150
[  169.199269]  get_tree_bdev+0x232/0x370
[  169.199750]  ? put_ntfs+0x1d0/0x1d0
[  169.200094]  ntfs_fs_get_tree+0x15/0x20
[  169.200431]  vfs_get_tree+0x4c/0x130
[  169.200714]  path_mount+0x654/0xfe0
[  169.201067]  ? putname+0x80/0xa0
[  169.201358]  ? finish_automount+0x2e0/0x2e0
[  169.201965]  ? putname+0x80/0xa0
[  169.202445]  ? kmem_cache_free+0x1c4/0x440
[  169.203075]  ? putname+0x80/0xa0
[  169.203414]  do_mount+0xd6/0xf0
[  169.203719]  ? path_mount+0xfe0/0xfe0
[  169.203977]  ? __kasan_check_write+0x14/0x20
[  169.204382]  __x64_sys_mount+0xca/0x110
[  169.204711]  do_syscall_64+0x3b/0x90
[  169.205059]  entry_SYSCALL_64_after_hwframe+0x63/0xcd
[  169.205571] RIP: 0033:0x7f67a80e948a
[  169.206327] Code: 48 8b 0d 11 fa 2a 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 49 89 ca b8 a5 00 00 008
[  169.208296] RSP: 002b:00007ffddf020f58 EFLAGS: 00000202 ORIG_RAX: 00000000000000a5
[  169.209253] RAX: ffffffffffffffda RBX: 000055e2547a6060 RCX: 00007f67a80e948a
[  169.209777] RDX: 000055e2547a6260 RSI: 000055e2547a62e0 RDI: 000055e2547aeaf0
[  169.210342] RBP: 0000000000000000 R08: 000055e2547a6280 R09: 0000000000000020
[  169.210843] R10: 00000000c0ed0000 R11: 0000000000000202 R12: 000055e2547aeaf0
[  169.211307] R13: 000055e2547a6260 R14: 0000000000000000 R15: 00000000ffffffff
[  169.211913]  </TASK>
[  169.212304]
[  169.212680] Allocated by task 0:
[  169.212963] (stack is not available)
[  169.213200]
[  169.213472] The buggy address belongs to the object at ffff8880094b5e00
[  169.213472]  which belongs to the cache UDP of size 1152
[  169.214095] The buggy address is located 1088 bytes inside of
[  169.214095]  1152-byte region [ffff8880094b5e00, ffff8880094b6280)
[  169.214639]
[  169.215004] The buggy address belongs to the physical page:
[  169.215766] page:000000002e324c8c refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x94b4
[  169.218412] head:000000002e324c8c order:2 compound_mapcount:0 compound_pincount:0
[  169.219078] flags: 0xfffffc0010200(slab|head|node=0|zone=1|lastcpupid=0x1fffff)
[  169.220272] raw: 000fffffc0010200 0000000000000000 dead000000000122 ffff888002409b40
[  169.221006] raw: 0000000000000000 00000000800c000c 00000001ffffffff 0000000000000000
[  169.222320] page dumped because: kasan: bad access detected
[  169.222922]
[  169.223119] Memory state around the buggy address:
[  169.224056]  ffff8880094b6100: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  169.224908]  ffff8880094b6180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  169.225677] >ffff8880094b6200: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  169.226445]                                            ^
[  169.227055]  ffff8880094b6280: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  169.227638]  ffff8880094b6300: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

Signed-off-by: Edward Lo <edward.lo@ambergroup.io>
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
Cc: "Doebel, Bjoern" <doebel@amazon.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-03-01 13:26:39 +01:00

603 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
*
* Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
*
*/
#include <linux/fs.h>
#include "debug.h"
#include "ntfs.h"
#include "ntfs_fs.h"
static inline int compare_attr(const struct ATTRIB *left, enum ATTR_TYPE type,
const __le16 *name, u8 name_len,
const u16 *upcase)
{
/* First, compare the type codes. */
int diff = le32_to_cpu(left->type) - le32_to_cpu(type);
if (diff)
return diff;
/* They have the same type code, so we have to compare the names. */
return ntfs_cmp_names(attr_name(left), left->name_len, name, name_len,
upcase, true);
}
/*
* mi_new_attt_id
*
* Return: Unused attribute id that is less than mrec->next_attr_id.
*/
static __le16 mi_new_attt_id(struct mft_inode *mi)
{
u16 free_id, max_id, t16;
struct MFT_REC *rec = mi->mrec;
struct ATTRIB *attr;
__le16 id;
id = rec->next_attr_id;
free_id = le16_to_cpu(id);
if (free_id < 0x7FFF) {
rec->next_attr_id = cpu_to_le16(free_id + 1);
return id;
}
/* One record can store up to 1024/24 ~= 42 attributes. */
free_id = 0;
max_id = 0;
attr = NULL;
for (;;) {
attr = mi_enum_attr(mi, attr);
if (!attr) {
rec->next_attr_id = cpu_to_le16(max_id + 1);
mi->dirty = true;
return cpu_to_le16(free_id);
}
t16 = le16_to_cpu(attr->id);
if (t16 == free_id) {
free_id += 1;
attr = NULL;
} else if (max_id < t16)
max_id = t16;
}
}
int mi_get(struct ntfs_sb_info *sbi, CLST rno, struct mft_inode **mi)
{
int err;
struct mft_inode *m = kzalloc(sizeof(struct mft_inode), GFP_NOFS);
if (!m)
return -ENOMEM;
err = mi_init(m, sbi, rno);
if (err) {
kfree(m);
return err;
}
err = mi_read(m, false);
if (err) {
mi_put(m);
return err;
}
*mi = m;
return 0;
}
void mi_put(struct mft_inode *mi)
{
mi_clear(mi);
kfree(mi);
}
int mi_init(struct mft_inode *mi, struct ntfs_sb_info *sbi, CLST rno)
{
mi->sbi = sbi;
mi->rno = rno;
mi->mrec = kmalloc(sbi->record_size, GFP_NOFS);
if (!mi->mrec)
return -ENOMEM;
return 0;
}
/*
* mi_read - Read MFT data.
*/
int mi_read(struct mft_inode *mi, bool is_mft)
{
int err;
struct MFT_REC *rec = mi->mrec;
struct ntfs_sb_info *sbi = mi->sbi;
u32 bpr = sbi->record_size;
u64 vbo = (u64)mi->rno << sbi->record_bits;
struct ntfs_inode *mft_ni = sbi->mft.ni;
struct runs_tree *run = mft_ni ? &mft_ni->file.run : NULL;
struct rw_semaphore *rw_lock = NULL;
if (is_mounted(sbi)) {
if (!is_mft && mft_ni) {
rw_lock = &mft_ni->file.run_lock;
down_read(rw_lock);
}
}
err = ntfs_read_bh(sbi, run, vbo, &rec->rhdr, bpr, &mi->nb);
if (rw_lock)
up_read(rw_lock);
if (!err)
goto ok;
if (err == -E_NTFS_FIXUP) {
mi->dirty = true;
goto ok;
}
if (err != -ENOENT)
goto out;
if (rw_lock) {
ni_lock(mft_ni);
down_write(rw_lock);
}
err = attr_load_runs_vcn(mft_ni, ATTR_DATA, NULL, 0, run,
vbo >> sbi->cluster_bits);
if (rw_lock) {
up_write(rw_lock);
ni_unlock(mft_ni);
}
if (err)
goto out;
if (rw_lock)
down_read(rw_lock);
err = ntfs_read_bh(sbi, run, vbo, &rec->rhdr, bpr, &mi->nb);
if (rw_lock)
up_read(rw_lock);
if (err == -E_NTFS_FIXUP) {
mi->dirty = true;
goto ok;
}
if (err)
goto out;
ok:
/* Check field 'total' only here. */
if (le32_to_cpu(rec->total) != bpr) {
err = -EINVAL;
goto out;
}
return 0;
out:
if (err == -E_NTFS_CORRUPT) {
ntfs_err(sbi->sb, "mft corrupted");
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
err = -EINVAL;
}
return err;
}
struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
{
const struct MFT_REC *rec = mi->mrec;
u32 used = le32_to_cpu(rec->used);
u32 t32, off, asize;
u16 t16;
if (!attr) {
u32 total = le32_to_cpu(rec->total);
off = le16_to_cpu(rec->attr_off);
if (used > total)
return NULL;
if (off >= used || off < MFTRECORD_FIXUP_OFFSET_1 ||
!IS_ALIGNED(off, 4)) {
return NULL;
}
/* Skip non-resident records. */
if (!is_rec_inuse(rec))
return NULL;
attr = Add2Ptr(rec, off);
} else {
/* Check if input attr inside record. */
off = PtrOffset(rec, attr);
if (off >= used)
return NULL;
asize = le32_to_cpu(attr->size);
if (asize < SIZEOF_RESIDENT) {
/* Impossible 'cause we should not return such attribute. */
return NULL;
}
attr = Add2Ptr(attr, asize);
off += asize;
}
asize = le32_to_cpu(attr->size);
/* Can we use the first field (attr->type). */
if (off + 8 > used) {
static_assert(ALIGN(sizeof(enum ATTR_TYPE), 8) == 8);
return NULL;
}
if (attr->type == ATTR_END) {
/* End of enumeration. */
return NULL;
}
/* 0x100 is last known attribute for now. */
t32 = le32_to_cpu(attr->type);
if ((t32 & 0xf) || (t32 > 0x100))
return NULL;
/* Check overflow and boundary. */
if (off + asize < off || off + asize > used)
return NULL;
/* Check size of attribute. */
if (!attr->non_res) {
if (asize < SIZEOF_RESIDENT)
return NULL;
t16 = le16_to_cpu(attr->res.data_off);
if (t16 > asize)
return NULL;
t32 = le32_to_cpu(attr->res.data_size);
if (t16 + t32 > asize)
return NULL;
if (attr->name_len &&
le16_to_cpu(attr->name_off) + sizeof(short) * attr->name_len > t16) {
return NULL;
}
return attr;
}
/* Check some nonresident fields. */
if (attr->name_len &&
le16_to_cpu(attr->name_off) + sizeof(short) * attr->name_len >
le16_to_cpu(attr->nres.run_off)) {
return NULL;
}
if (attr->nres.svcn || !is_attr_ext(attr)) {
if (asize + 8 < SIZEOF_NONRESIDENT)
return NULL;
if (attr->nres.c_unit)
return NULL;
} else if (asize + 8 < SIZEOF_NONRESIDENT_EX)
return NULL;
return attr;
}
/*
* 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,
size_t name_len, const __le16 *id)
{
u32 type_in = le32_to_cpu(type);
u32 atype;
next_attr:
attr = mi_enum_attr(mi, attr);
if (!attr)
return NULL;
atype = le32_to_cpu(attr->type);
if (atype > type_in)
return NULL;
if (atype < type_in)
goto next_attr;
if (attr->name_len != name_len)
goto next_attr;
if (name_len && memcmp(attr_name(attr), name, name_len * sizeof(short)))
goto next_attr;
if (id && *id != attr->id)
goto next_attr;
return attr;
}
int mi_write(struct mft_inode *mi, int wait)
{
struct MFT_REC *rec;
int err;
struct ntfs_sb_info *sbi;
if (!mi->dirty)
return 0;
sbi = mi->sbi;
rec = mi->mrec;
err = ntfs_write_bh(sbi, &rec->rhdr, &mi->nb, wait);
if (err)
return err;
if (mi->rno < sbi->mft.recs_mirr)
sbi->flags |= NTFS_FLAGS_MFTMIRR;
mi->dirty = false;
return 0;
}
int mi_format_new(struct mft_inode *mi, struct ntfs_sb_info *sbi, CLST rno,
__le16 flags, bool is_mft)
{
int err;
u16 seq = 1;
struct MFT_REC *rec;
u64 vbo = (u64)rno << sbi->record_bits;
err = mi_init(mi, sbi, rno);
if (err)
return err;
rec = mi->mrec;
if (rno == MFT_REC_MFT) {
;
} else if (rno < MFT_REC_FREE) {
seq = rno;
} else if (rno >= sbi->mft.used) {
;
} else if (mi_read(mi, is_mft)) {
;
} else if (rec->rhdr.sign == NTFS_FILE_SIGNATURE) {
/* Record is reused. Update its sequence number. */
seq = le16_to_cpu(rec->seq) + 1;
if (!seq)
seq = 1;
}
memcpy(rec, sbi->new_rec, sbi->record_size);
rec->seq = cpu_to_le16(seq);
rec->flags = RECORD_FLAG_IN_USE | flags;
mi->dirty = true;
if (!mi->nb.nbufs) {
struct ntfs_inode *ni = sbi->mft.ni;
bool lock = false;
if (is_mounted(sbi) && !is_mft) {
down_read(&ni->file.run_lock);
lock = true;
}
err = ntfs_get_bh(sbi, &ni->file.run, vbo, sbi->record_size,
&mi->nb);
if (lock)
up_read(&ni->file.run_lock);
}
return err;
}
/*
* mi_insert_attr - Reserve space for new attribute.
*
* 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)
{
size_t tail;
struct ATTRIB *attr;
__le16 id;
struct MFT_REC *rec = mi->mrec;
struct ntfs_sb_info *sbi = mi->sbi;
u32 used = le32_to_cpu(rec->used);
const u16 *upcase = sbi->upcase;
int diff;
/* Can we insert mi attribute? */
if (used + asize > mi->sbi->record_size)
return NULL;
/*
* Scan through the list of attributes to find the point
* at which we should insert it.
*/
attr = NULL;
while ((attr = mi_enum_attr(mi, attr))) {
diff = compare_attr(attr, type, name, name_len, upcase);
if (diff < 0)
continue;
if (!diff && !is_attr_indexed(attr))
return NULL;
break;
}
if (!attr) {
tail = 8; /* Not used, just to suppress warning. */
attr = Add2Ptr(rec, used - 8);
} else {
tail = used - PtrOffset(rec, attr);
}
id = mi_new_attt_id(mi);
memmove(Add2Ptr(attr, asize), attr, tail);
memset(attr, 0, asize);
attr->type = type;
attr->size = cpu_to_le32(asize);
attr->name_len = name_len;
attr->name_off = cpu_to_le16(name_off);
attr->id = id;
memmove(Add2Ptr(attr, name_off), name, name_len * sizeof(short));
rec->used = cpu_to_le32(used + asize);
mi->dirty = true;
return attr;
}
/*
* mi_remove_attr - Remove the attribute from record.
*
* NOTE: The source attr will point to next attribute.
*/
bool mi_remove_attr(struct ntfs_inode *ni, struct mft_inode *mi,
struct ATTRIB *attr)
{
struct MFT_REC *rec = mi->mrec;
u32 aoff = PtrOffset(rec, attr);
u32 used = le32_to_cpu(rec->used);
u32 asize = le32_to_cpu(attr->size);
if (aoff + asize > used)
return false;
if (ni && is_attr_indexed(attr)) {
u16 links = le16_to_cpu(ni->mi.mrec->hard_links);
struct ATTR_FILE_NAME *fname =
attr->type != ATTR_NAME ?
NULL :
resident_data_ex(attr,
SIZEOF_ATTRIBUTE_FILENAME);
if (fname && fname->type == FILE_NAME_DOS) {
/* Do not decrease links count deleting DOS name. */
} else if (!links) {
/* minor error. Not critical. */
} else {
ni->mi.mrec->hard_links = cpu_to_le16(links - 1);
ni->mi.dirty = true;
}
}
used -= asize;
memmove(attr, Add2Ptr(attr, asize), used - aoff);
rec->used = cpu_to_le32(used);
mi->dirty = true;
return true;
}
/* bytes = "new attribute size" - "old attribute size" */
bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes)
{
struct MFT_REC *rec = mi->mrec;
u32 aoff = PtrOffset(rec, attr);
u32 total, used = le32_to_cpu(rec->used);
u32 nsize, asize = le32_to_cpu(attr->size);
u32 rsize = le32_to_cpu(attr->res.data_size);
int tail = (int)(used - aoff - asize);
int dsize;
char *next;
if (tail < 0 || aoff >= used)
return false;
if (!bytes)
return true;
total = le32_to_cpu(rec->total);
next = Add2Ptr(attr, asize);
if (bytes > 0) {
dsize = ALIGN(bytes, 8);
if (used + dsize > total)
return false;
nsize = asize + dsize;
/* Move tail */
memmove(next + dsize, next, tail);
memset(next, 0, dsize);
used += dsize;
rsize += dsize;
} else {
dsize = ALIGN(-bytes, 8);
if (dsize > asize)
return false;
nsize = asize - dsize;
memmove(next - dsize, next, tail);
used -= dsize;
rsize -= dsize;
}
rec->used = cpu_to_le32(used);
attr->size = cpu_to_le32(nsize);
if (!attr->non_res)
attr->res.data_size = cpu_to_le32(rsize);
mi->dirty = true;
return true;
}
int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr,
struct runs_tree *run, CLST len)
{
int err = 0;
struct ntfs_sb_info *sbi = mi->sbi;
u32 new_run_size;
CLST plen;
struct MFT_REC *rec = mi->mrec;
CLST svcn = le64_to_cpu(attr->nres.svcn);
u32 used = le32_to_cpu(rec->used);
u32 aoff = PtrOffset(rec, attr);
u32 asize = le32_to_cpu(attr->size);
char *next = Add2Ptr(attr, asize);
u16 run_off = le16_to_cpu(attr->nres.run_off);
u32 run_size = asize - run_off;
u32 tail = used - aoff - asize;
u32 dsize = sbi->record_size - used;
/* Make a maximum gap in current record. */
memmove(next + dsize, next, tail);
/* Pack as much as possible. */
err = run_pack(run, svcn, len, Add2Ptr(attr, run_off), run_size + dsize,
&plen);
if (err < 0) {
memmove(next, next + dsize, tail);
return err;
}
new_run_size = ALIGN(err, 8);
memmove(next + new_run_size - run_size, next + dsize, tail);
attr->size = cpu_to_le32(asize + new_run_size - run_size);
attr->nres.evcn = cpu_to_le64(svcn + plen - 1);
rec->used = cpu_to_le32(used + new_run_size - run_size);
mi->dirty = true;
return 0;
}