linux-stable/fs/ntfs3/attrlist.c

474 lines
9.7 KiB
C
Raw Permalink Normal View History

// 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"
/*
* al_is_valid_le
*
* Return: True if @le is valid.
*/
static inline bool al_is_valid_le(const struct ntfs_inode *ni,
struct ATTR_LIST_ENTRY *le)
{
if (!le || !ni->attr_list.le || !ni->attr_list.size)
return false;
return PtrOffset(ni->attr_list.le, le) + le16_to_cpu(le->size) <=
ni->attr_list.size;
}
void al_destroy(struct ntfs_inode *ni)
{
run_close(&ni->attr_list.run);
kfree(ni->attr_list.le);
ni->attr_list.le = NULL;
ni->attr_list.size = 0;
ni->attr_list.dirty = false;
}
/*
* ntfs_load_attr_list
*
* This method makes sure that the ATTRIB list, if present,
* has been properly set up.
*/
int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
{
int err;
size_t lsize;
void *le = NULL;
if (ni->attr_list.size)
return 0;
if (!attr->non_res) {
lsize = le32_to_cpu(attr->res.data_size);
/* attr is resident: lsize < record_size (1K or 4K) */
le = kvmalloc(al_aligned(lsize), GFP_KERNEL);
if (!le) {
err = -ENOMEM;
goto out;
}
memcpy(le, resident_data(attr), lsize);
} else if (attr->nres.svcn) {
err = -EINVAL;
goto out;
} else {
u16 run_off = le16_to_cpu(attr->nres.run_off);
lsize = le64_to_cpu(attr->nres.data_size);
run_init(&ni->attr_list.run);
fs/ntfs3: Validate data run offset [ Upstream commit 6db620863f8528ed9a9aa5ad323b26554a17881d ] This adds sanity checks for data run offset. We should make sure data run offset is legit before trying to unpack them, otherwise we may encounter use-after-free or some unexpected memory access behaviors. [ 82.940342] BUG: KASAN: use-after-free in run_unpack+0x2e3/0x570 [ 82.941180] Read of size 1 at addr ffff888008a8487f by task mount/240 [ 82.941670] [ 82.942069] CPU: 0 PID: 240 Comm: mount Not tainted 5.19.0+ #15 [ 82.942482] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014 [ 82.943720] Call Trace: [ 82.944204] <TASK> [ 82.944471] dump_stack_lvl+0x49/0x63 [ 82.944908] print_report.cold+0xf5/0x67b [ 82.945141] ? __wait_on_bit+0x106/0x120 [ 82.945750] ? run_unpack+0x2e3/0x570 [ 82.946626] kasan_report+0xa7/0x120 [ 82.947046] ? run_unpack+0x2e3/0x570 [ 82.947280] __asan_load1+0x51/0x60 [ 82.947483] run_unpack+0x2e3/0x570 [ 82.947709] ? memcpy+0x4e/0x70 [ 82.947927] ? run_pack+0x7a0/0x7a0 [ 82.948158] run_unpack_ex+0xad/0x3f0 [ 82.948399] ? mi_enum_attr+0x14a/0x200 [ 82.948717] ? run_unpack+0x570/0x570 [ 82.949072] ? ni_enum_attr_ex+0x1b2/0x1c0 [ 82.949332] ? ni_fname_type.part.0+0xd0/0xd0 [ 82.949611] ? mi_read+0x262/0x2c0 [ 82.949970] ? ntfs_cmp_names_cpu+0x125/0x180 [ 82.950249] ntfs_iget5+0x632/0x1870 [ 82.950621] ? ntfs_get_block_bmap+0x70/0x70 [ 82.951192] ? evict+0x223/0x280 [ 82.951525] ? iput.part.0+0x286/0x320 [ 82.951969] ntfs_fill_super+0x1321/0x1e20 [ 82.952436] ? put_ntfs+0x1d0/0x1d0 [ 82.952822] ? vsprintf+0x20/0x20 [ 82.953188] ? mutex_unlock+0x81/0xd0 [ 82.953379] ? set_blocksize+0x95/0x150 [ 82.954001] get_tree_bdev+0x232/0x370 [ 82.954438] ? put_ntfs+0x1d0/0x1d0 [ 82.954700] ntfs_fs_get_tree+0x15/0x20 [ 82.955049] vfs_get_tree+0x4c/0x130 [ 82.955292] path_mount+0x645/0xfd0 [ 82.955615] ? putname+0x80/0xa0 [ 82.955955] ? finish_automount+0x2e0/0x2e0 [ 82.956310] ? kmem_cache_free+0x110/0x390 [ 82.956723] ? putname+0x80/0xa0 [ 82.957023] do_mount+0xd6/0xf0 [ 82.957411] ? path_mount+0xfd0/0xfd0 [ 82.957638] ? __kasan_check_write+0x14/0x20 [ 82.957948] __x64_sys_mount+0xca/0x110 [ 82.958310] do_syscall_64+0x3b/0x90 [ 82.958719] entry_SYSCALL_64_after_hwframe+0x63/0xcd [ 82.959341] RIP: 0033:0x7fd0d1ce948a [ 82.960193] 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 [ 82.961532] RSP: 002b:00007ffe59ff69a8 EFLAGS: 00000202 ORIG_RAX: 00000000000000a5 [ 82.962527] RAX: ffffffffffffffda RBX: 0000564dcc107060 RCX: 00007fd0d1ce948a [ 82.963266] RDX: 0000564dcc107260 RSI: 0000564dcc1072e0 RDI: 0000564dcc10fce0 [ 82.963686] RBP: 0000000000000000 R08: 0000564dcc107280 R09: 0000000000000020 [ 82.964272] R10: 00000000c0ed0000 R11: 0000000000000202 R12: 0000564dcc10fce0 [ 82.964785] R13: 0000564dcc107260 R14: 0000000000000000 R15: 00000000ffffffff Signed-off-by: Edward Lo <edward.lo@ambergroup.io> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
2022-08-05 16:47:27 +00:00
if (run_off > le32_to_cpu(attr->size)) {
err = -EINVAL;
goto out;
}
err = run_unpack_ex(&ni->attr_list.run, ni->mi.sbi, ni->mi.rno,
0, le64_to_cpu(attr->nres.evcn), 0,
Add2Ptr(attr, run_off),
le32_to_cpu(attr->size) - run_off);
if (err < 0)
goto out;
/* attr is nonresident.
* The worst case:
* 1T (2^40) extremely fragmented file.
* cluster = 4K (2^12) => 2^28 fragments
* 2^9 fragments per one record => 2^19 records
* 2^5 bytes of ATTR_LIST_ENTRY per one record => 2^24 bytes.
*
* the result is 16M bytes per attribute list.
* Use kvmalloc to allocate in range [several Kbytes - dozen Mbytes]
*/
le = kvmalloc(al_aligned(lsize), GFP_KERNEL);
if (!le) {
err = -ENOMEM;
goto out;
}
err = ntfs_read_run_nb(ni->mi.sbi, &ni->attr_list.run, 0, le,
lsize, NULL);
if (err)
goto out;
}
ni->attr_list.size = lsize;
ni->attr_list.le = le;
return 0;
out:
ni->attr_list.le = le;
al_destroy(ni);
return err;
}
/*
* al_enumerate
*
* Return:
* * The next list le.
* * If @le is NULL then return the first le.
*/
struct ATTR_LIST_ENTRY *al_enumerate(struct ntfs_inode *ni,
struct ATTR_LIST_ENTRY *le)
{
size_t off;
u16 sz;
const unsigned le_min_size = le_size(0);
if (!le) {
le = ni->attr_list.le;
} else {
sz = le16_to_cpu(le->size);
if (sz < le_min_size) {
/* Impossible 'cause we should not return such le. */
return NULL;
}
le = Add2Ptr(le, sz);
}
/* Check boundary. */
off = PtrOffset(ni->attr_list.le, le);
if (off + le_min_size > ni->attr_list.size) {
/* The regular end of list. */
return NULL;
}
sz = le16_to_cpu(le->size);
/* Check le for errors. */
if (sz < le_min_size || off + sz > ni->attr_list.size ||
sz < le->name_off + le->name_len * sizeof(short)) {
return NULL;
}
return le;
}
/*
* al_find_le
*
* Find the first le in the list which matches type, name and VCN.
*
* Return: NULL if not found.
*/
struct ATTR_LIST_ENTRY *al_find_le(struct ntfs_inode *ni,
struct ATTR_LIST_ENTRY *le,
const struct ATTRIB *attr)
{
CLST svcn = attr_svcn(attr);
return al_find_ex(ni, le, attr->type, attr_name(attr), attr->name_len,
&svcn);
}
/*
* al_find_ex
*
* Find the first le in the list which matches type, name and VCN.
*
* Return: NULL if not found.
*/
struct ATTR_LIST_ENTRY *al_find_ex(struct ntfs_inode *ni,
struct ATTR_LIST_ENTRY *le,
enum ATTR_TYPE type, const __le16 *name,
u8 name_len, const CLST *vcn)
{
struct ATTR_LIST_ENTRY *ret = NULL;
u32 type_in = le32_to_cpu(type);
while ((le = al_enumerate(ni, le))) {
u64 le_vcn;
int diff = le32_to_cpu(le->type) - type_in;
/* List entries are sorted by type, name and VCN. */
if (diff < 0)
continue;
if (diff > 0)
return ret;
if (le->name_len != name_len)
continue;
le_vcn = le64_to_cpu(le->vcn);
if (!le_vcn) {
/*
* Compare entry names only for entry with vcn == 0.
*/
diff = ntfs_cmp_names(le_name(le), name_len, name,
name_len, ni->mi.sbi->upcase,
true);
if (diff < 0)
continue;
if (diff > 0)
return ret;
}
if (!vcn)
return le;
if (*vcn == le_vcn)
return le;
if (*vcn < le_vcn)
return ret;
ret = le;
}
return ret;
}
/*
* al_find_le_to_insert
*
* Find the first list entry which matches type, name and VCN.
*/
static struct ATTR_LIST_ENTRY *al_find_le_to_insert(struct ntfs_inode *ni,
enum ATTR_TYPE type,
const __le16 *name,
u8 name_len, CLST vcn)
{
struct ATTR_LIST_ENTRY *le = NULL, *prev;
u32 type_in = le32_to_cpu(type);
/* List entries are sorted by type, name and VCN. */
while ((le = al_enumerate(ni, prev = le))) {
int diff = le32_to_cpu(le->type) - type_in;
if (diff < 0)
continue;
if (diff > 0)
return le;
if (!le->vcn) {
/*
* Compare entry names only for entry with vcn == 0.
*/
diff = ntfs_cmp_names(le_name(le), le->name_len, name,
name_len, ni->mi.sbi->upcase,
true);
if (diff < 0)
continue;
if (diff > 0)
return le;
}
if (le64_to_cpu(le->vcn) >= vcn)
return le;
}
return prev ? Add2Ptr(prev, le16_to_cpu(prev->size)) : ni->attr_list.le;
}
/*
* al_add_le
*
* Add an "attribute list entry" to the list.
*/
int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name,
u8 name_len, CLST svcn, __le16 id, const struct MFT_REF *ref,
struct ATTR_LIST_ENTRY **new_le)
{
int err;
struct ATTRIB *attr;
struct ATTR_LIST_ENTRY *le;
size_t off;
u16 sz;
size_t asize, new_asize, old_size;
u64 new_size;
typeof(ni->attr_list) *al = &ni->attr_list;
/*
* Compute the size of the new 'le'
*/
sz = le_size(name_len);
old_size = al->size;
new_size = old_size + sz;
asize = al_aligned(old_size);
new_asize = al_aligned(new_size);
/* Scan forward to the point at which the new 'le' should be inserted. */
le = al_find_le_to_insert(ni, type, name, name_len, svcn);
off = PtrOffset(al->le, le);
if (new_size > asize) {
void *ptr = kmalloc(new_asize, GFP_NOFS);
if (!ptr)
return -ENOMEM;
memcpy(ptr, al->le, off);
memcpy(Add2Ptr(ptr, off + sz), le, old_size - off);
le = Add2Ptr(ptr, off);
kfree(al->le);
al->le = ptr;
} else {
memmove(Add2Ptr(le, sz), le, old_size - off);
}
*new_le = le;
al->size = new_size;
le->type = type;
le->size = cpu_to_le16(sz);
le->name_len = name_len;
le->name_off = offsetof(struct ATTR_LIST_ENTRY, name);
le->vcn = cpu_to_le64(svcn);
le->ref = *ref;
le->id = id;
memcpy(le->name, name, sizeof(short) * name_len);
err = attr_set_size(ni, ATTR_LIST, NULL, 0, &al->run, new_size,
&new_size, true, &attr);
if (err) {
/* Undo memmove above. */
memmove(le, Add2Ptr(le, sz), old_size - off);
al->size = old_size;
return err;
}
al->dirty = true;
if (attr && attr->non_res) {
err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
al->size, 0);
if (err)
return err;
al->dirty = false;
}
return 0;
}
/*
* al_remove_le - Remove @le from attribute list.
*/
bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le)
{
u16 size;
size_t off;
typeof(ni->attr_list) *al = &ni->attr_list;
if (!al_is_valid_le(ni, le))
return false;
/* Save on stack the size of 'le' */
size = le16_to_cpu(le->size);
off = PtrOffset(al->le, le);
memmove(le, Add2Ptr(le, size), al->size - (off + size));
al->size -= size;
al->dirty = true;
return true;
}
/*
* al_delete_le - Delete first le from the list which matches its parameters.
*/
bool al_delete_le(struct ntfs_inode *ni, enum ATTR_TYPE type, CLST vcn,
const __le16 *name, size_t name_len,
const struct MFT_REF *ref)
{
u16 size;
struct ATTR_LIST_ENTRY *le;
size_t off;
typeof(ni->attr_list) *al = &ni->attr_list;
/* Scan forward to the first le that matches the input. */
le = al_find_ex(ni, NULL, type, name, name_len, &vcn);
if (!le)
return false;
off = PtrOffset(al->le, le);
next:
if (off >= al->size)
return false;
if (le->type != type)
return false;
if (le->name_len != name_len)
return false;
if (name_len && ntfs_cmp_names(le_name(le), name_len, name, name_len,
ni->mi.sbi->upcase, true))
return false;
if (le64_to_cpu(le->vcn) != vcn)
return false;
/*
* The caller specified a segment reference, so we have to
* scan through the matching entries until we find that segment
* reference or we run of matching entries.
*/
if (ref && memcmp(ref, &le->ref, sizeof(*ref))) {
off += le16_to_cpu(le->size);
le = Add2Ptr(al->le, off);
goto next;
}
/* Save on stack the size of 'le'. */
size = le16_to_cpu(le->size);
/* Delete the le. */
memmove(le, Add2Ptr(le, size), al->size - (off + size));
al->size -= size;
al->dirty = true;
return true;
}
int al_update(struct ntfs_inode *ni, int sync)
{
int err;
struct ATTRIB *attr;
typeof(ni->attr_list) *al = &ni->attr_list;
if (!al->dirty || !al->size)
return 0;
/*
* Attribute list increased on demand in al_add_le.
* Attribute list decreased here.
*/
err = attr_set_size(ni, ATTR_LIST, NULL, 0, &al->run, al->size, NULL,
false, &attr);
if (err)
goto out;
if (!attr->non_res) {
memcpy(resident_data(attr), al->le, al->size);
} else {
err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
al->size, sync);
if (err)
goto out;
attr->nres.valid_size = attr->nres.data_size;
}
ni->mi.dirty = true;
al->dirty = false;
out:
return err;
}