OpenCloudOS-Kernel/fs/ntfs3/inode.c

2081 lines
49 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
*
* Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
*
*/
#include <linux/buffer_head.h>
#include <linux/fs.h>
#include <linux/mpage.h>
#include <linux/namei.h>
#include <linux/nls.h>
#include <linux/uio.h>
#include <linux/writeback.h>
#include "debug.h"
#include "ntfs.h"
#include "ntfs_fs.h"
/*
* ntfs_read_mft - Read record and parses MFT.
*/
static struct inode *ntfs_read_mft(struct inode *inode,
const struct cpu_str *name,
const struct MFT_REF *ref)
{
int err = 0;
struct ntfs_inode *ni = ntfs_i(inode);
struct super_block *sb = inode->i_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
mode_t mode = 0;
struct ATTR_STD_INFO5 *std5 = NULL;
struct ATTR_LIST_ENTRY *le;
struct ATTRIB *attr;
bool is_match = false;
bool is_root = false;
bool is_dir;
unsigned long ino = inode->i_ino;
u32 rp_fa = 0, asize, t32;
u16 roff, rsize, names = 0;
const struct ATTR_FILE_NAME *fname = NULL;
const struct INDEX_ROOT *root;
struct REPARSE_DATA_BUFFER rp; // 0x18 bytes
u64 t64;
struct MFT_REC *rec;
struct runs_tree *run;
inode->i_op = NULL;
/* Setup 'uid' and 'gid' */
inode->i_uid = sbi->options->fs_uid;
inode->i_gid = sbi->options->fs_gid;
err = mi_init(&ni->mi, sbi, ino);
if (err)
goto out;
if (!sbi->mft.ni && ino == MFT_REC_MFT && !sb->s_root) {
t64 = sbi->mft.lbo >> sbi->cluster_bits;
t32 = bytes_to_cluster(sbi, MFT_REC_VOL * sbi->record_size);
sbi->mft.ni = ni;
init_rwsem(&ni->file.run_lock);
if (!run_add_entry(&ni->file.run, 0, t64, t32, true)) {
err = -ENOMEM;
goto out;
}
}
err = mi_read(&ni->mi, ino == MFT_REC_MFT);
if (err)
goto out;
rec = ni->mi.mrec;
if (sbi->flags & NTFS_FLAGS_LOG_REPLAYING) {
;
} else if (ref->seq != rec->seq) {
err = -EINVAL;
ntfs_err(sb, "MFT: r=%lx, expect seq=%x instead of %x!", ino,
le16_to_cpu(ref->seq), le16_to_cpu(rec->seq));
goto out;
} else if (!is_rec_inuse(rec)) {
err = -ESTALE;
ntfs_err(sb, "Inode r=%x is not in use!", (u32)ino);
goto out;
}
if (le32_to_cpu(rec->total) != sbi->record_size) {
/* Bad inode? */
err = -EINVAL;
goto out;
}
if (!is_rec_base(rec)) {
err = -EINVAL;
goto out;
}
/* Record should contain $I30 root. */
is_dir = rec->flags & RECORD_FLAG_DIR;
inode->i_generation = le16_to_cpu(rec->seq);
/* Enumerate all struct Attributes MFT. */
le = NULL;
attr = NULL;
/*
* To reduce tab pressure use goto instead of
* while( (attr = ni_enum_attr_ex(ni, attr, &le, NULL) ))
*/
next_attr:
run = NULL;
err = -EINVAL;
attr = ni_enum_attr_ex(ni, attr, &le, NULL);
if (!attr)
goto end_enum;
if (le && le->vcn) {
/* This is non primary attribute segment. Ignore if not MFT. */
if (ino != MFT_REC_MFT || attr->type != ATTR_DATA)
goto next_attr;
run = &ni->file.run;
asize = le32_to_cpu(attr->size);
goto attr_unpack_run;
}
roff = attr->non_res ? 0 : le16_to_cpu(attr->res.data_off);
rsize = attr->non_res ? 0 : le32_to_cpu(attr->res.data_size);
asize = le32_to_cpu(attr->size);
fs/ntfs3: Validate attribute name offset Although the attribute name length is checked before comparing it to some common names (e.g., $I30), the offset isn't. This adds a sanity check for the attribute name offset, guarantee the validity and prevent possible out-of-bound memory accesses. [ 191.720056] BUG: unable to handle page fault for address: ffffebde00000008 [ 191.721060] #PF: supervisor read access in kernel mode [ 191.721586] #PF: error_code(0x0000) - not-present page [ 191.722079] PGD 0 P4D 0 [ 191.722571] Oops: 0000 [#1] PREEMPT SMP KASAN NOPTI [ 191.723179] CPU: 0 PID: 244 Comm: mount Not tainted 6.0.0-rc4 #28 [ 191.723749] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014 [ 191.724832] RIP: 0010:kfree+0x56/0x3b0 [ 191.725870] Code: 80 48 01 d8 0f 82 65 03 00 00 48 c7 c2 00 00 00 80 48 2b 15 2c 06 dd 01 48 01 d0 48 c1 e8 0c 48 c1 e0 06 48 03 05 0a 069 [ 191.727375] RSP: 0018:ffff8880076f7878 EFLAGS: 00000286 [ 191.727897] RAX: ffffebde00000000 RBX: 0000000000000040 RCX: ffffffff8528d5b9 [ 191.728531] RDX: 0000777f80000000 RSI: ffffffff8522d49c RDI: 0000000000000040 [ 191.729183] RBP: ffff8880076f78a0 R08: 0000000000000000 R09: 0000000000000000 [ 191.729628] R10: ffff888008949fd8 R11: ffffed10011293fd R12: 0000000000000040 [ 191.730158] R13: ffff888008949f98 R14: ffff888008949ec0 R15: ffff888008949fb0 [ 191.730645] FS: 00007f3520cd7e40(0000) GS:ffff88805ba00000(0000) knlGS:0000000000000000 [ 191.731328] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 191.731667] CR2: ffffebde00000008 CR3: 0000000009704000 CR4: 00000000000006f0 [ 191.732568] Call Trace: [ 191.733231] <TASK> [ 191.733860] kvfree+0x2c/0x40 [ 191.734632] ni_clear+0x180/0x290 [ 191.735085] ntfs_evict_inode+0x45/0x70 [ 191.735495] evict+0x199/0x280 [ 191.735996] iput.part.0+0x286/0x320 [ 191.736438] iput+0x32/0x50 [ 191.736811] iget_failed+0x23/0x30 [ 191.737270] ntfs_iget5+0x337/0x1890 [ 191.737629] ? ntfs_clear_mft_tail+0x20/0x260 [ 191.738201] ? ntfs_get_block_bmap+0x70/0x70 [ 191.738482] ? ntfs_objid_init+0xf6/0x140 [ 191.738779] ? ntfs_reparse_init+0x140/0x140 [ 191.739266] ntfs_fill_super+0x121b/0x1b50 [ 191.739623] ? put_ntfs+0x1d0/0x1d0 [ 191.739984] ? asm_sysvec_apic_timer_interrupt+0x1b/0x20 [ 191.740466] ? put_ntfs+0x1d0/0x1d0 [ 191.740787] ? sb_set_blocksize+0x6a/0x80 [ 191.741272] get_tree_bdev+0x232/0x370 [ 191.741829] ? put_ntfs+0x1d0/0x1d0 [ 191.742669] ntfs_fs_get_tree+0x15/0x20 [ 191.743132] vfs_get_tree+0x4c/0x130 [ 191.743457] path_mount+0x654/0xfe0 [ 191.743938] ? putname+0x80/0xa0 [ 191.744271] ? finish_automount+0x2e0/0x2e0 [ 191.744582] ? putname+0x80/0xa0 [ 191.745053] ? kmem_cache_free+0x1c4/0x440 [ 191.745403] ? putname+0x80/0xa0 [ 191.745616] do_mount+0xd6/0xf0 [ 191.745887] ? path_mount+0xfe0/0xfe0 [ 191.746287] ? __kasan_check_write+0x14/0x20 [ 191.746582] __x64_sys_mount+0xca/0x110 [ 191.746850] do_syscall_64+0x3b/0x90 [ 191.747122] entry_SYSCALL_64_after_hwframe+0x63/0xcd [ 191.747517] RIP: 0033:0x7f351fee948a [ 191.748332] 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 [ 191.749341] RSP: 002b:00007ffd51cf3af8 EFLAGS: 00000202 ORIG_RAX: 00000000000000a5 [ 191.749960] RAX: ffffffffffffffda RBX: 000055b903733060 RCX: 00007f351fee948a [ 191.750589] RDX: 000055b903733260 RSI: 000055b9037332e0 RDI: 000055b90373bce0 [ 191.751115] RBP: 0000000000000000 R08: 000055b903733280 R09: 0000000000000020 [ 191.751537] R10: 00000000c0ed0000 R11: 0000000000000202 R12: 000055b90373bce0 [ 191.751946] R13: 000055b903733260 R14: 0000000000000000 R15: 00000000ffffffff [ 191.752519] </TASK> [ 191.752782] Modules linked in: [ 191.753785] CR2: ffffebde00000008 [ 191.754937] ---[ end trace 0000000000000000 ]--- [ 191.755429] RIP: 0010:kfree+0x56/0x3b0 [ 191.755725] Code: 80 48 01 d8 0f 82 65 03 00 00 48 c7 c2 00 00 00 80 48 2b 15 2c 06 dd 01 48 01 d0 48 c1 e8 0c 48 c1 e0 06 48 03 05 0a 069 [ 191.756744] RSP: 0018:ffff8880076f7878 EFLAGS: 00000286 [ 191.757218] RAX: ffffebde00000000 RBX: 0000000000000040 RCX: ffffffff8528d5b9 [ 191.757580] RDX: 0000777f80000000 RSI: ffffffff8522d49c RDI: 0000000000000040 [ 191.758016] RBP: ffff8880076f78a0 R08: 0000000000000000 R09: 0000000000000000 [ 191.758570] R10: ffff888008949fd8 R11: ffffed10011293fd R12: 0000000000000040 [ 191.758957] R13: ffff888008949f98 R14: ffff888008949ec0 R15: ffff888008949fb0 [ 191.759317] FS: 00007f3520cd7e40(0000) GS:ffff88805ba00000(0000) knlGS:0000000000000000 [ 191.759711] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 191.760118] CR2: ffffebde00000008 CR3: 0000000009704000 CR4: 00000000000006f0 Signed-off-by: Edward Lo <edward.lo@ambergroup.io> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2022-09-09 09:04:00 +08:00
if (le16_to_cpu(attr->name_off) + attr->name_len > asize)
goto out;
if (attr->non_res) {
t64 = le64_to_cpu(attr->nres.alloc_size);
if (le64_to_cpu(attr->nres.data_size) > t64 ||
le64_to_cpu(attr->nres.valid_size) > t64)
goto out;
}
switch (attr->type) {
case ATTR_STD:
if (attr->non_res ||
asize < sizeof(struct ATTR_STD_INFO) + roff ||
rsize < sizeof(struct ATTR_STD_INFO))
goto out;
if (std5)
goto next_attr;
std5 = Add2Ptr(attr, roff);
#ifdef STATX_BTIME
nt2kernel(std5->cr_time, &ni->i_crtime);
#endif
nt2kernel(std5->a_time, &inode->i_atime);
nt2kernel(std5->c_time, &inode->i_ctime);
nt2kernel(std5->m_time, &inode->i_mtime);
ni->std_fa = std5->fa;
if (asize >= sizeof(struct ATTR_STD_INFO5) + roff &&
rsize >= sizeof(struct ATTR_STD_INFO5))
ni->std_security_id = std5->security_id;
goto next_attr;
case ATTR_LIST:
if (attr->name_len || le || ino == MFT_REC_LOG)
goto out;
err = ntfs_load_attr_list(ni, attr);
if (err)
goto out;
le = NULL;
attr = NULL;
goto next_attr;
case ATTR_NAME:
if (attr->non_res || asize < SIZEOF_ATTRIBUTE_FILENAME + roff ||
rsize < SIZEOF_ATTRIBUTE_FILENAME)
goto out;
fname = Add2Ptr(attr, roff);
if (fname->type == FILE_NAME_DOS)
goto next_attr;
names += 1;
if (name && name->len == fname->name_len &&
!ntfs_cmp_names_cpu(name, (struct le_str *)&fname->name_len,
NULL, false))
is_match = true;
goto next_attr;
case ATTR_DATA:
if (is_dir) {
/* Ignore data attribute in dir record. */
goto next_attr;
}
if (ino == MFT_REC_BADCLUST && !attr->non_res)
goto next_attr;
if (attr->name_len &&
((ino != MFT_REC_BADCLUST || !attr->non_res ||
attr->name_len != ARRAY_SIZE(BAD_NAME) ||
memcmp(attr_name(attr), BAD_NAME, sizeof(BAD_NAME))) &&
(ino != MFT_REC_SECURE || !attr->non_res ||
attr->name_len != ARRAY_SIZE(SDS_NAME) ||
memcmp(attr_name(attr), SDS_NAME, sizeof(SDS_NAME))))) {
/* File contains stream attribute. Ignore it. */
goto next_attr;
}
if (is_attr_sparsed(attr))
ni->std_fa |= FILE_ATTRIBUTE_SPARSE_FILE;
else
ni->std_fa &= ~FILE_ATTRIBUTE_SPARSE_FILE;
if (is_attr_compressed(attr))
ni->std_fa |= FILE_ATTRIBUTE_COMPRESSED;
else
ni->std_fa &= ~FILE_ATTRIBUTE_COMPRESSED;
if (is_attr_encrypted(attr))
ni->std_fa |= FILE_ATTRIBUTE_ENCRYPTED;
else
ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED;
if (!attr->non_res) {
ni->i_valid = inode->i_size = rsize;
inode_set_bytes(inode, rsize);
}
mode = S_IFREG | (0777 & sbi->options->fs_fmask_inv);
if (!attr->non_res) {
ni->ni_flags |= NI_FLAG_RESIDENT;
goto next_attr;
}
inode_set_bytes(inode, attr_ondisk_size(attr));
ni->i_valid = le64_to_cpu(attr->nres.valid_size);
inode->i_size = le64_to_cpu(attr->nres.data_size);
if (!attr->nres.alloc_size)
goto next_attr;
run = ino == MFT_REC_BITMAP ? &sbi->used.bitmap.run
: &ni->file.run;
break;
case ATTR_ROOT:
if (attr->non_res)
goto out;
root = Add2Ptr(attr, roff);
is_root = true;
if (attr->name_len != ARRAY_SIZE(I30_NAME) ||
memcmp(attr_name(attr), I30_NAME, sizeof(I30_NAME)))
goto next_attr;
if (root->type != ATTR_NAME ||
root->rule != NTFS_COLLATION_TYPE_FILENAME)
goto out;
if (!is_dir)
goto next_attr;
ni->ni_flags |= NI_FLAG_DIR;
err = indx_init(&ni->dir, sbi, attr, INDEX_MUTEX_I30);
if (err)
goto out;
mode = sb->s_root
? (S_IFDIR | (0777 & sbi->options->fs_dmask_inv))
: (S_IFDIR | 0777);
goto next_attr;
case ATTR_ALLOC:
if (!is_root || attr->name_len != ARRAY_SIZE(I30_NAME) ||
memcmp(attr_name(attr), I30_NAME, sizeof(I30_NAME)))
goto next_attr;
inode->i_size = le64_to_cpu(attr->nres.data_size);
ni->i_valid = le64_to_cpu(attr->nres.valid_size);
inode_set_bytes(inode, le64_to_cpu(attr->nres.alloc_size));
run = &ni->dir.alloc_run;
break;
case ATTR_BITMAP:
if (ino == MFT_REC_MFT) {
if (!attr->non_res)
goto out;
#ifndef CONFIG_NTFS3_64BIT_CLUSTER
/* 0x20000000 = 2^32 / 8 */
if (le64_to_cpu(attr->nres.alloc_size) >= 0x20000000)
goto out;
#endif
run = &sbi->mft.bitmap.run;
break;
} else if (is_dir && attr->name_len == ARRAY_SIZE(I30_NAME) &&
!memcmp(attr_name(attr), I30_NAME,
sizeof(I30_NAME)) &&
attr->non_res) {
run = &ni->dir.bitmap_run;
break;
}
goto next_attr;
case ATTR_REPARSE:
if (attr->name_len)
goto next_attr;
rp_fa = ni_parse_reparse(ni, attr, &rp);
switch (rp_fa) {
case REPARSE_LINK:
/*
* Normal symlink.
* Assume one unicode symbol == one utf8.
*/
inode->i_size = le16_to_cpu(rp.SymbolicLinkReparseBuffer
.PrintNameLength) /
sizeof(u16);
ni->i_valid = inode->i_size;
/* Clear directory bit. */
if (ni->ni_flags & NI_FLAG_DIR) {
indx_clear(&ni->dir);
memset(&ni->dir, 0, sizeof(ni->dir));
ni->ni_flags &= ~NI_FLAG_DIR;
} else {
run_close(&ni->file.run);
}
mode = S_IFLNK | 0777;
is_dir = false;
if (attr->non_res) {
run = &ni->file.run;
goto attr_unpack_run; // Double break.
}
break;
case REPARSE_COMPRESSED:
break;
case REPARSE_DEDUPLICATED:
break;
}
goto next_attr;
case ATTR_EA_INFO:
if (!attr->name_len &&
resident_data_ex(attr, sizeof(struct EA_INFO))) {
ni->ni_flags |= NI_FLAG_EA;
/*
* ntfs_get_wsl_perm updates inode->i_uid, inode->i_gid, inode->i_mode
*/
inode->i_mode = mode;
ntfs_get_wsl_perm(inode);
mode = inode->i_mode;
}
goto next_attr;
default:
goto next_attr;
}
attr_unpack_run:
roff = le16_to_cpu(attr->nres.run_off);
fs/ntfs3: Validate data run offset 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>
2022-08-06 00:47:27 +08:00
if (roff > asize) {
err = -EINVAL;
goto out;
}
t64 = le64_to_cpu(attr->nres.svcn);
fs/ntfs3: Fix slab-out-of-bounds read in run_unpack Syzkaller reports slab-out-of-bounds bug as follows: ================================================================== BUG: KASAN: slab-out-of-bounds in run_unpack+0x8b7/0x970 fs/ntfs3/run.c:944 Read of size 1 at addr ffff88801bbdff02 by task syz-executor131/3611 [...] Call Trace: <TASK> __dump_stack lib/dump_stack.c:88 [inline] dump_stack_lvl+0xcd/0x134 lib/dump_stack.c:106 print_address_description mm/kasan/report.c:317 [inline] print_report.cold+0x2ba/0x719 mm/kasan/report.c:433 kasan_report+0xb1/0x1e0 mm/kasan/report.c:495 run_unpack+0x8b7/0x970 fs/ntfs3/run.c:944 run_unpack_ex+0xb0/0x7c0 fs/ntfs3/run.c:1057 ntfs_read_mft fs/ntfs3/inode.c:368 [inline] ntfs_iget5+0xc20/0x3280 fs/ntfs3/inode.c:501 ntfs_loadlog_and_replay+0x124/0x5d0 fs/ntfs3/fsntfs.c:272 ntfs_fill_super+0x1eff/0x37f0 fs/ntfs3/super.c:1018 get_tree_bdev+0x440/0x760 fs/super.c:1323 vfs_get_tree+0x89/0x2f0 fs/super.c:1530 do_new_mount fs/namespace.c:3040 [inline] path_mount+0x1326/0x1e20 fs/namespace.c:3370 do_mount fs/namespace.c:3383 [inline] __do_sys_mount fs/namespace.c:3591 [inline] __se_sys_mount fs/namespace.c:3568 [inline] __x64_sys_mount+0x27f/0x300 fs/namespace.c:3568 do_syscall_x64 arch/x86/entry/common.c:50 [inline] do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80 entry_SYSCALL_64_after_hwframe+0x63/0xcd [...] </TASK> The buggy address belongs to the physical page: page:ffffea00006ef600 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1bbd8 head:ffffea00006ef600 order:3 compound_mapcount:0 compound_pincount:0 flags: 0xfff00000010200(slab|head|node=0|zone=1|lastcpupid=0x7ff) page dumped because: kasan: bad access detected Memory state around the buggy address: ffff88801bbdfe00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffff88801bbdfe80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc >ffff88801bbdff00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ^ ffff88801bbdff80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffff88801bbe0000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ================================================================== Kernel will tries to read record and parse MFT from disk in ntfs_read_mft(). Yet the problem is that during enumerating attributes in record, kernel doesn't check whether run_off field loading from the disk is a valid value. To be more specific, if attr->nres.run_off is larger than attr->size, kernel will passes an invalid argument run_buf_size in run_unpack_ex(), which having an integer overflow. Then this invalid argument will triggers the slab-out-of-bounds Read bug as above. This patch solves it by adding the sanity check between the offset to packed runs and attribute size. link: https://lore.kernel.org/all/0000000000009145fc05e94bd5c3@google.com/#t Reported-and-tested-by: syzbot+8d6fbb27a6aded64b25b@syzkaller.appspotmail.com Signed-off-by: Hawkins Jiawei <yin31149@gmail.com> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2022-09-23 19:09:04 +08:00
err = run_unpack_ex(run, sbi, ino, t64, le64_to_cpu(attr->nres.evcn),
t64, Add2Ptr(attr, roff), asize - roff);
if (err < 0)
goto out;
err = 0;
goto next_attr;
end_enum:
if (!std5)
goto out;
if (!is_match && name) {
/* Reuse rec as buffer for ascii name. */
err = -ENOENT;
goto out;
}
if (std5->fa & FILE_ATTRIBUTE_READONLY)
mode &= ~0222;
if (!names) {
err = -EINVAL;
goto out;
}
if (names != le16_to_cpu(rec->hard_links)) {
/* Correct minor error on the fly. Do not mark inode as dirty. */
rec->hard_links = cpu_to_le16(names);
ni->mi.dirty = true;
}
set_nlink(inode, names);
if (S_ISDIR(mode)) {
ni->std_fa |= FILE_ATTRIBUTE_DIRECTORY;
/*
* Dot and dot-dot should be included in count but was not
* included in enumeration.
* Usually a hard links to directories are disabled.
*/
inode->i_op = &ntfs_dir_inode_operations;
inode->i_fop = &ntfs_dir_operations;
ni->i_valid = 0;
} else if (S_ISLNK(mode)) {
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
inode->i_op = &ntfs_link_inode_operations;
inode->i_fop = NULL;
inode_nohighmem(inode);
} else if (S_ISREG(mode)) {
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
inode->i_op = &ntfs_file_inode_operations;
inode->i_fop = &ntfs_file_operations;
inode->i_mapping->a_ops =
is_compressed(ni) ? &ntfs_aops_cmpr : &ntfs_aops;
if (ino != MFT_REC_MFT)
init_rwsem(&ni->file.run_lock);
} else if (S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) ||
S_ISSOCK(mode)) {
inode->i_op = &ntfs_special_inode_operations;
init_special_inode(inode, mode, inode->i_rdev);
} else if (fname && fname->home.low == cpu_to_le32(MFT_REC_EXTEND) &&
fname->home.seq == cpu_to_le16(MFT_REC_EXTEND)) {
/* Records in $Extend are not a files or general directories. */
inode->i_op = &ntfs_file_inode_operations;
} else {
err = -EINVAL;
goto out;
}
if ((sbi->options->sys_immutable &&
(std5->fa & FILE_ATTRIBUTE_SYSTEM)) &&
!S_ISFIFO(mode) && !S_ISSOCK(mode) && !S_ISLNK(mode)) {
inode->i_flags |= S_IMMUTABLE;
} else {
inode->i_flags &= ~S_IMMUTABLE;
}
inode->i_mode = mode;
if (!(ni->ni_flags & NI_FLAG_EA)) {
/* If no xattr then no security (stored in xattr). */
inode->i_flags |= S_NOSEC;
}
if (ino == MFT_REC_MFT && !sb->s_root)
sbi->mft.ni = NULL;
unlock_new_inode(inode);
return inode;
out:
if (ino == MFT_REC_MFT && !sb->s_root)
sbi->mft.ni = NULL;
iget_failed(inode);
return ERR_PTR(err);
}
/*
* ntfs_test_inode
*
* Return: 1 if match.
*/
static int ntfs_test_inode(struct inode *inode, void *data)
{
struct MFT_REF *ref = data;
return ino_get(ref) == inode->i_ino;
}
static int ntfs_set_inode(struct inode *inode, void *data)
{
const struct MFT_REF *ref = data;
inode->i_ino = ino_get(ref);
return 0;
}
struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
const struct cpu_str *name)
{
struct inode *inode;
inode = iget5_locked(sb, ino_get(ref), ntfs_test_inode, ntfs_set_inode,
(void *)ref);
if (unlikely(!inode))
return ERR_PTR(-ENOMEM);
/* If this is a freshly allocated inode, need to read it now. */
if (inode->i_state & I_NEW)
inode = ntfs_read_mft(inode, name, ref);
else if (ref->seq != ntfs_i(inode)->mi.mrec->seq) {
/* Inode overlaps? */
_ntfs_bad_inode(inode);
}
if (IS_ERR(inode) && name)
ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR);
return inode;
}
enum get_block_ctx {
GET_BLOCK_GENERAL = 0,
GET_BLOCK_WRITE_BEGIN = 1,
GET_BLOCK_DIRECT_IO_R = 2,
GET_BLOCK_DIRECT_IO_W = 3,
GET_BLOCK_BMAP = 4,
};
static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
struct buffer_head *bh, int create,
enum get_block_ctx ctx)
{
struct super_block *sb = inode->i_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
struct ntfs_inode *ni = ntfs_i(inode);
struct page *page = bh->b_page;
u8 cluster_bits = sbi->cluster_bits;
u32 block_size = sb->s_blocksize;
u64 bytes, lbo, valid;
u32 off;
int err;
CLST vcn, lcn, len;
bool new;
/* Clear previous state. */
clear_buffer_new(bh);
clear_buffer_uptodate(bh);
if (is_resident(ni)) {
ni_lock(ni);
err = attr_data_read_resident(ni, page);
ni_unlock(ni);
if (!err)
set_buffer_uptodate(bh);
bh->b_size = block_size;
return err;
}
vcn = vbo >> cluster_bits;
off = vbo & sbi->cluster_mask;
new = false;
err = attr_data_get_block(ni, vcn, 1, &lcn, &len, create ? &new : NULL,
create && sbi->cluster_size > PAGE_SIZE);
if (err)
goto out;
if (!len)
return 0;
bytes = ((u64)len << cluster_bits) - off;
if (lcn == SPARSE_LCN) {
if (!create) {
if (bh->b_size > bytes)
bh->b_size = bytes;
return 0;
}
WARN_ON(1);
}
if (new)
set_buffer_new(bh);
lbo = ((u64)lcn << cluster_bits) + off;
set_buffer_mapped(bh);
bh->b_bdev = sb->s_bdev;
bh->b_blocknr = lbo >> sb->s_blocksize_bits;
valid = ni->i_valid;
if (ctx == GET_BLOCK_DIRECT_IO_W) {
/* ntfs_direct_IO will update ni->i_valid. */
if (vbo >= valid)
set_buffer_new(bh);
} else if (create) {
/* Normal write. */
if (bytes > bh->b_size)
bytes = bh->b_size;
if (vbo >= valid)
set_buffer_new(bh);
if (vbo + bytes > valid) {
ni->i_valid = vbo + bytes;
mark_inode_dirty(inode);
}
} else if (vbo >= valid) {
/* Read out of valid data. */
clear_buffer_mapped(bh);
} else if (vbo + bytes <= valid) {
/* Normal read. */
} else if (vbo + block_size <= valid) {
/* Normal short read. */
bytes = block_size;
} else {
/*
* Read across valid size: vbo < valid && valid < vbo + block_size
*/
bytes = block_size;
if (page) {
u32 voff = valid - vbo;
bh->b_size = block_size;
off = vbo & (PAGE_SIZE - 1);
set_bh_page(bh, page, off);
err = bh_read(bh, 0);
if (err < 0)
goto out;
zero_user_segment(page, off + voff, off + block_size);
}
}
if (bh->b_size > bytes)
bh->b_size = bytes;
#ifndef __LP64__
if (ctx == GET_BLOCK_DIRECT_IO_W || ctx == GET_BLOCK_DIRECT_IO_R) {
static_assert(sizeof(size_t) < sizeof(loff_t));
if (bytes > 0x40000000u)
bh->b_size = 0x40000000u;
}
#endif
return 0;
out:
return err;
}
int ntfs_get_block(struct inode *inode, sector_t vbn,
struct buffer_head *bh_result, int create)
{
return ntfs_get_block_vbo(inode, (u64)vbn << inode->i_blkbits,
bh_result, create, GET_BLOCK_GENERAL);
}
static int ntfs_get_block_bmap(struct inode *inode, sector_t vsn,
struct buffer_head *bh_result, int create)
{
return ntfs_get_block_vbo(inode,
(u64)vsn << inode->i_sb->s_blocksize_bits,
bh_result, create, GET_BLOCK_BMAP);
}
static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
{
return generic_block_bmap(mapping, block, ntfs_get_block_bmap);
}
static int ntfs_read_folio(struct file *file, struct folio *folio)
{
struct page *page = &folio->page;
int err;
struct address_space *mapping = page->mapping;
struct inode *inode = mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
if (is_resident(ni)) {
ni_lock(ni);
err = attr_data_read_resident(ni, page);
ni_unlock(ni);
if (err != E_NTFS_NONRESIDENT) {
unlock_page(page);
return err;
}
}
if (is_compressed(ni)) {
ni_lock(ni);
err = ni_readpage_cmpr(ni, page);
ni_unlock(ni);
return err;
}
/* Normal + sparse files. */
return mpage_read_folio(folio, ntfs_get_block);
}
static void ntfs_readahead(struct readahead_control *rac)
{
struct address_space *mapping = rac->mapping;
struct inode *inode = mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
u64 valid;
loff_t pos;
if (is_resident(ni)) {
/* No readahead for resident. */
return;
}
if (is_compressed(ni)) {
/* No readahead for compressed. */
return;
}
valid = ni->i_valid;
pos = readahead_pos(rac);
if (valid < i_size_read(inode) && pos <= valid &&
valid < pos + readahead_length(rac)) {
/* Range cross 'valid'. Read it page by page. */
return;
}
mpage_readahead(rac, ntfs_get_block);
}
static int ntfs_get_block_direct_IO_R(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create)
{
return ntfs_get_block_vbo(inode, (u64)iblock << inode->i_blkbits,
bh_result, create, GET_BLOCK_DIRECT_IO_R);
}
static int ntfs_get_block_direct_IO_W(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create)
{
return ntfs_get_block_vbo(inode, (u64)iblock << inode->i_blkbits,
bh_result, create, GET_BLOCK_DIRECT_IO_W);
}
static ssize_t ntfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
{
struct file *file = iocb->ki_filp;
struct address_space *mapping = file->f_mapping;
struct inode *inode = mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
loff_t vbo = iocb->ki_pos;
loff_t end;
int wr = iov_iter_rw(iter) & WRITE;
size_t iter_count = iov_iter_count(iter);
loff_t valid;
ssize_t ret;
if (is_resident(ni)) {
/* Switch to buffered write. */
ret = 0;
goto out;
}
ret = blockdev_direct_IO(iocb, inode, iter,
wr ? ntfs_get_block_direct_IO_W
: ntfs_get_block_direct_IO_R);
if (ret > 0)
end = vbo + ret;
else if (wr && ret == -EIOCBQUEUED)
end = vbo + iter_count;
else
goto out;
valid = ni->i_valid;
if (wr) {
if (end > valid && !S_ISBLK(inode->i_mode)) {
ni->i_valid = end;
mark_inode_dirty(inode);
}
} else if (vbo < valid && valid < end) {
/* Fix page. */
iov_iter_revert(iter, end - valid);
iov_iter_zero(end - valid, iter);
}
out:
return ret;
}
int ntfs_set_size(struct inode *inode, u64 new_size)
{
struct super_block *sb = inode->i_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
struct ntfs_inode *ni = ntfs_i(inode);
int err;
/* Check for maximum file size. */
if (is_sparsed(ni) || is_compressed(ni)) {
if (new_size > sbi->maxbytes_sparse) {
err = -EFBIG;
goto out;
}
} else if (new_size > sbi->maxbytes) {
err = -EFBIG;
goto out;
}
ni_lock(ni);
down_write(&ni->file.run_lock);
err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
&ni->i_valid, true, NULL);
up_write(&ni->file.run_lock);
ni_unlock(ni);
mark_inode_dirty(inode);
out:
return err;
}
static int ntfs_resident_writepage(struct folio *folio,
struct writeback_control *wbc, void *data)
{
struct address_space *mapping = data;
struct ntfs_inode *ni = ntfs_i(mapping->host);
int ret;
ni_lock(ni);
ret = attr_data_write_resident(ni, &folio->page);
ni_unlock(ni);
if (ret != E_NTFS_NONRESIDENT)
folio_unlock(folio);
mapping_set_error(mapping, ret);
return ret;
}
static int ntfs_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
if (is_resident(ntfs_i(mapping->host)))
return write_cache_pages(mapping, wbc, ntfs_resident_writepage,
mapping);
return mpage_writepages(mapping, wbc, ntfs_get_block);
}
static int ntfs_get_block_write_begin(struct inode *inode, sector_t vbn,
struct buffer_head *bh_result, int create)
{
return ntfs_get_block_vbo(inode, (u64)vbn << inode->i_blkbits,
bh_result, create, GET_BLOCK_WRITE_BEGIN);
}
int ntfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, u32 len, struct page **pagep, void **fsdata)
{
int err;
struct inode *inode = mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
*pagep = NULL;
if (is_resident(ni)) {
struct page *page = grab_cache_page_write_begin(
mapping, pos >> PAGE_SHIFT);
if (!page) {
err = -ENOMEM;
goto out;
}
ni_lock(ni);
err = attr_data_read_resident(ni, page);
ni_unlock(ni);
if (!err) {
*pagep = page;
goto out;
}
unlock_page(page);
put_page(page);
if (err != E_NTFS_NONRESIDENT)
goto out;
}
err = block_write_begin(mapping, pos, len, pagep,
ntfs_get_block_write_begin);
out:
return err;
}
/*
* ntfs_write_end - Address_space_operations::write_end.
*/
int ntfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, u32 len, u32 copied, struct page *page,
void *fsdata)
{
struct inode *inode = mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
u64 valid = ni->i_valid;
bool dirty = false;
int err;
if (is_resident(ni)) {
ni_lock(ni);
err = attr_data_write_resident(ni, page);
ni_unlock(ni);
if (!err) {
dirty = true;
/* Clear any buffers in page. */
if (page_has_buffers(page)) {
struct buffer_head *head, *bh;
bh = head = page_buffers(page);
do {
clear_buffer_dirty(bh);
clear_buffer_mapped(bh);
set_buffer_uptodate(bh);
} while (head != (bh = bh->b_this_page));
}
SetPageUptodate(page);
err = copied;
}
unlock_page(page);
put_page(page);
} else {
err = generic_write_end(file, mapping, pos, len, copied, page,
fsdata);
}
if (err >= 0) {
if (!(ni->std_fa & FILE_ATTRIBUTE_ARCHIVE)) {
inode->i_ctime = inode->i_mtime = current_time(inode);
ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
dirty = true;
}
if (valid != ni->i_valid) {
/* ni->i_valid is changed in ntfs_get_block_vbo. */
dirty = true;
}
if (pos + err > inode->i_size) {
inode->i_size = pos + err;
dirty = true;
}
if (dirty)
mark_inode_dirty(inode);
}
return err;
}
int reset_log_file(struct inode *inode)
{
int err;
loff_t pos = 0;
u32 log_size = inode->i_size;
struct address_space *mapping = inode->i_mapping;
for (;;) {
u32 len;
void *kaddr;
struct page *page;
len = pos + PAGE_SIZE > log_size ? (log_size - pos) : PAGE_SIZE;
err = block_write_begin(mapping, pos, len, &page,
ntfs_get_block_write_begin);
if (err)
goto out;
kaddr = kmap_atomic(page);
memset(kaddr, -1, len);
kunmap_atomic(kaddr);
flush_dcache_page(page);
err = block_write_end(NULL, mapping, pos, len, len, page, NULL);
if (err < 0)
goto out;
pos += len;
if (pos >= log_size)
break;
balance_dirty_pages_ratelimited(mapping);
}
out:
mark_inode_dirty_sync(inode);
return err;
}
int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc)
{
return _ni_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
}
int ntfs_sync_inode(struct inode *inode)
{
return _ni_write_inode(inode, 1);
}
/*
* writeback_inode - Helper function for ntfs_flush_inodes().
*
* This writes both the inode and the file data blocks, waiting
* for in flight data blocks before the start of the call. It
* does not wait for any io started during the call.
*/
static int writeback_inode(struct inode *inode)
{
int ret = sync_inode_metadata(inode, 0);
if (!ret)
ret = filemap_fdatawrite(inode->i_mapping);
return ret;
}
/*
* ntfs_flush_inodes
*
* Write data and metadata corresponding to i1 and i2. The io is
* started but we do not wait for any of it to finish.
*
* filemap_flush() is used for the block device, so if there is a dirty
* page for a block already in flight, we will not wait and start the
* io over again.
*/
int ntfs_flush_inodes(struct super_block *sb, struct inode *i1,
struct inode *i2)
{
int ret = 0;
if (i1)
ret = writeback_inode(i1);
if (!ret && i2)
ret = writeback_inode(i2);
if (!ret)
ret = sync_blockdev_nowait(sb->s_bdev);
return ret;
}
int inode_write_data(struct inode *inode, const void *data, size_t bytes)
{
pgoff_t idx;
/* Write non resident data. */
for (idx = 0; bytes; idx++) {
size_t op = bytes > PAGE_SIZE ? PAGE_SIZE : bytes;
struct page *page = ntfs_map_page(inode->i_mapping, idx);
if (IS_ERR(page))
return PTR_ERR(page);
lock_page(page);
WARN_ON(!PageUptodate(page));
ClearPageUptodate(page);
memcpy(page_address(page), data, op);
flush_dcache_page(page);
SetPageUptodate(page);
unlock_page(page);
ntfs_unmap_page(page);
bytes -= op;
data = Add2Ptr(data, PAGE_SIZE);
}
return 0;
}
/*
* ntfs_reparse_bytes
*
* Number of bytes for REPARSE_DATA_BUFFER(IO_REPARSE_TAG_SYMLINK)
* for unicode string of @uni_len length.
*/
static inline u32 ntfs_reparse_bytes(u32 uni_len)
{
/* Header + unicode string + decorated unicode string. */
return sizeof(short) * (2 * uni_len + 4) +
offsetof(struct REPARSE_DATA_BUFFER,
SymbolicLinkReparseBuffer.PathBuffer);
}
static struct REPARSE_DATA_BUFFER *
ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname,
u32 size, u16 *nsize)
{
int i, err;
struct REPARSE_DATA_BUFFER *rp;
__le16 *rp_name;
typeof(rp->SymbolicLinkReparseBuffer) *rs;
rp = kzalloc(ntfs_reparse_bytes(2 * size + 2), GFP_NOFS);
if (!rp)
return ERR_PTR(-ENOMEM);
rs = &rp->SymbolicLinkReparseBuffer;
rp_name = rs->PathBuffer;
/* Convert link name to UTF-16. */
err = ntfs_nls_to_utf16(sbi, symname, size,
(struct cpu_str *)(rp_name - 1), 2 * size,
UTF16_LITTLE_ENDIAN);
if (err < 0)
goto out;
/* err = the length of unicode name of symlink. */
*nsize = ntfs_reparse_bytes(err);
if (*nsize > sbi->reparse.max_size) {
err = -EFBIG;
goto out;
}
/* Translate Linux '/' into Windows '\'. */
for (i = 0; i < err; i++) {
if (rp_name[i] == cpu_to_le16('/'))
rp_name[i] = cpu_to_le16('\\');
}
rp->ReparseTag = IO_REPARSE_TAG_SYMLINK;
rp->ReparseDataLength =
cpu_to_le16(*nsize - offsetof(struct REPARSE_DATA_BUFFER,
SymbolicLinkReparseBuffer));
/* PrintName + SubstituteName. */
rs->SubstituteNameOffset = cpu_to_le16(sizeof(short) * err);
rs->SubstituteNameLength = cpu_to_le16(sizeof(short) * err + 8);
rs->PrintNameLength = rs->SubstituteNameOffset;
/*
* TODO: Use relative path if possible to allow Windows to
* parse this path.
* 0-absolute path 1- relative path (SYMLINK_FLAG_RELATIVE).
*/
rs->Flags = 0;
memmove(rp_name + err + 4, rp_name, sizeof(short) * err);
/* Decorate SubstituteName. */
rp_name += err;
rp_name[0] = cpu_to_le16('\\');
rp_name[1] = cpu_to_le16('?');
rp_name[2] = cpu_to_le16('?');
rp_name[3] = cpu_to_le16('\\');
return rp;
out:
kfree(rp);
return ERR_PTR(err);
}
/*
* ntfs_create_inode
*
* Helper function for:
* - ntfs_create
* - ntfs_mknod
* - ntfs_symlink
* - ntfs_mkdir
* - ntfs_atomic_open
*
* NOTE: if fnd != NULL (ntfs_atomic_open) then @dir is locked
*/
struct inode *ntfs_create_inode(struct mnt_idmap *idmap,
struct inode *dir, struct dentry *dentry,
const struct cpu_str *uni, umode_t mode,
dev_t dev, const char *symname, u32 size,
struct ntfs_fnd *fnd)
{
int err;
struct super_block *sb = dir->i_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
const struct qstr *name = &dentry->d_name;
CLST ino = 0;
struct ntfs_inode *dir_ni = ntfs_i(dir);
struct ntfs_inode *ni = NULL;
struct inode *inode = NULL;
struct ATTRIB *attr;
struct ATTR_STD_INFO5 *std5;
struct ATTR_FILE_NAME *fname;
struct MFT_REC *rec;
u32 asize, dsize, sd_size;
enum FILE_ATTRIBUTE fa;
__le32 security_id = SECURITY_ID_INVALID;
CLST vcn;
const void *sd;
u16 t16, nsize = 0, aid = 0;
struct INDEX_ROOT *root, *dir_root;
struct NTFS_DE *e, *new_de = NULL;
struct REPARSE_DATA_BUFFER *rp = NULL;
bool rp_inserted = false;
if (!fnd)
ni_lock_dir(dir_ni);
dir_root = indx_get_root(&dir_ni->dir, dir_ni, NULL, NULL);
if (!dir_root) {
err = -EINVAL;
goto out1;
}
if (S_ISDIR(mode)) {
/* Use parent's directory attributes. */
fa = dir_ni->std_fa | FILE_ATTRIBUTE_DIRECTORY |
FILE_ATTRIBUTE_ARCHIVE;
/*
* By default child directory inherits parent attributes.
* Root directory is hidden + system.
* Make an exception for children in root.
*/
if (dir->i_ino == MFT_REC_ROOT)
fa &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
} else if (S_ISLNK(mode)) {
/* It is good idea that link should be the same type (file/dir) as target */
fa = FILE_ATTRIBUTE_REPARSE_POINT;
/*
* Linux: there are dir/file/symlink and so on.
* NTFS: symlinks are "dir + reparse" or "file + reparse"
* It is good idea to create:
* dir + reparse if 'symname' points to directory
* or
* file + reparse if 'symname' points to file
* Unfortunately kern_path hangs if symname contains 'dir'.
*/
/*
* struct path path;
*
* if (!kern_path(symname, LOOKUP_FOLLOW, &path)){
* struct inode *target = d_inode(path.dentry);
*
* if (S_ISDIR(target->i_mode))
* fa |= FILE_ATTRIBUTE_DIRECTORY;
* // if ( target->i_sb == sb ){
* // use relative path?
* // }
* path_put(&path);
* }
*/
} else if (S_ISREG(mode)) {
if (sbi->options->sparse) {
/* Sparsed regular file, cause option 'sparse'. */
fa = FILE_ATTRIBUTE_SPARSE_FILE |
FILE_ATTRIBUTE_ARCHIVE;
} else if (dir_ni->std_fa & FILE_ATTRIBUTE_COMPRESSED) {
/* Compressed regular file, if parent is compressed. */
fa = FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_ARCHIVE;
} else {
/* Regular file, default attributes. */
fa = FILE_ATTRIBUTE_ARCHIVE;
}
} else {
fa = FILE_ATTRIBUTE_ARCHIVE;
}
/* If option "hide_dot_files" then set hidden attribute for dot files. */
if (sbi->options->hide_dot_files && name->name[0] == '.')
fa |= FILE_ATTRIBUTE_HIDDEN;
if (!(mode & 0222))
fa |= FILE_ATTRIBUTE_READONLY;
/* Allocate PATH_MAX bytes. */
new_de = __getname();
if (!new_de) {
err = -ENOMEM;
goto out1;
}
/* Mark rw ntfs as dirty. it will be cleared at umount. */
ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
/* Step 1: allocate and fill new mft record. */
err = ntfs_look_free_mft(sbi, &ino, false, NULL, NULL);
if (err)
goto out2;
ni = ntfs_new_inode(sbi, ino, fa & FILE_ATTRIBUTE_DIRECTORY);
if (IS_ERR(ni)) {
err = PTR_ERR(ni);
ni = NULL;
goto out3;
}
inode = &ni->vfs_inode;
inode_init_owner(idmap, inode, dir, mode);
mode = inode->i_mode;
inode->i_atime = inode->i_mtime = inode->i_ctime = ni->i_crtime =
current_time(inode);
rec = ni->mi.mrec;
rec->hard_links = cpu_to_le16(1);
attr = Add2Ptr(rec, le16_to_cpu(rec->attr_off));
/* Get default security id. */
sd = s_default_security;
sd_size = sizeof(s_default_security);
if (is_ntfs3(sbi)) {
security_id = dir_ni->std_security_id;
if (le32_to_cpu(security_id) < SECURITY_ID_FIRST) {
security_id = sbi->security.def_security_id;
if (security_id == SECURITY_ID_INVALID &&
!ntfs_insert_security(sbi, sd, sd_size,
&security_id, NULL))
sbi->security.def_security_id = security_id;
}
}
/* Insert standard info. */
std5 = Add2Ptr(attr, SIZEOF_RESIDENT);
if (security_id == SECURITY_ID_INVALID) {
dsize = sizeof(struct ATTR_STD_INFO);
} else {
dsize = sizeof(struct ATTR_STD_INFO5);
std5->security_id = security_id;
ni->std_security_id = security_id;
}
asize = SIZEOF_RESIDENT + dsize;
attr->type = ATTR_STD;
attr->size = cpu_to_le32(asize);
attr->id = cpu_to_le16(aid++);
attr->res.data_off = SIZEOF_RESIDENT_LE;
attr->res.data_size = cpu_to_le32(dsize);
std5->cr_time = std5->m_time = std5->c_time = std5->a_time =
kernel2nt(&inode->i_atime);
ni->std_fa = fa;
std5->fa = fa;
attr = Add2Ptr(attr, asize);
/* Insert file name. */
err = fill_name_de(sbi, new_de, name, uni);
if (err)
goto out4;
mi_get_ref(&ni->mi, &new_de->ref);
fname = (struct ATTR_FILE_NAME *)(new_de + 1);
if (sbi->options->windows_names &&
!valid_windows_name(sbi, (struct le_str *)&fname->name_len)) {
err = -EINVAL;
goto out4;
}
mi_get_ref(&dir_ni->mi, &fname->home);
fname->dup.cr_time = fname->dup.m_time = fname->dup.c_time =
fname->dup.a_time = std5->cr_time;
fname->dup.alloc_size = fname->dup.data_size = 0;
fname->dup.fa = std5->fa;
fname->dup.ea_size = fname->dup.reparse = 0;
dsize = le16_to_cpu(new_de->key_size);
fs/ntfs3: Use kernel ALIGN macros over driver specific The static checkers (Smatch) were complaining because QuadAlign() was buggy. If you try to align something higher than UINT_MAX it got truncated to a u32. Smatch warning was: fs/ntfs3/attrib.c:383 attr_set_size_res() warn: was expecting a 64 bit value instead of '~7' So that this will not happen again we will change all these macros to kernel made ones. This can also help some other static analyzing tools to give us better warnings. Patch was generated with Coccinelle script and after that some style issue was hand fixed. Coccinelle script: virtual patch @alloc depends on patch@ expression x; @@ ( - #define QuadAlign(n) (((n) + 7u) & (~7u)) | - QuadAlign(x) + ALIGN(x, 8) | - #define IsQuadAligned(n) (!((size_t)(n)&7u)) | - IsQuadAligned(x) + IS_ALIGNED(x, 8) | - #define Quad2Align(n) (((n) + 15u) & (~15u)) | - Quad2Align(x) + ALIGN(x, 16) | - #define IsQuad2Aligned(n) (!((size_t)(n)&15u)) | - IsQuad2Aligned(x) + IS_ALIGNED(x, 16) | - #define Quad4Align(n) (((n) + 31u) & (~31u)) | - Quad4Align(x) + ALIGN(x, 32) | - #define IsSizeTAligned(n) (!((size_t)(n) & (sizeof(size_t) - 1))) | - IsSizeTAligned(x) + IS_ALIGNED(x, sizeof(size_t)) | - #define DwordAlign(n) (((n) + 3u) & (~3u)) | - DwordAlign(x) + ALIGN(x, 4) | - #define IsDwordAligned(n) (!((size_t)(n)&3u)) | - IsDwordAligned(x) + IS_ALIGNED(x, 4) | - #define WordAlign(n) (((n) + 1u) & (~1u)) | - WordAlign(x) + ALIGN(x, 2) | - #define IsWordAligned(n) (!((size_t)(n)&1u)) | - IsWordAligned(x) + IS_ALIGNED(x, 2) | ) Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Kari Argillander <kari.argillander@gmail.com> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2021-08-26 16:56:29 +08:00
asize = ALIGN(SIZEOF_RESIDENT + dsize, 8);
attr->type = ATTR_NAME;
attr->size = cpu_to_le32(asize);
attr->res.data_off = SIZEOF_RESIDENT_LE;
attr->res.flags = RESIDENT_FLAG_INDEXED;
attr->id = cpu_to_le16(aid++);
attr->res.data_size = cpu_to_le32(dsize);
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), fname, dsize);
attr = Add2Ptr(attr, asize);
if (security_id == SECURITY_ID_INVALID) {
/* Insert security attribute. */
fs/ntfs3: Use kernel ALIGN macros over driver specific The static checkers (Smatch) were complaining because QuadAlign() was buggy. If you try to align something higher than UINT_MAX it got truncated to a u32. Smatch warning was: fs/ntfs3/attrib.c:383 attr_set_size_res() warn: was expecting a 64 bit value instead of '~7' So that this will not happen again we will change all these macros to kernel made ones. This can also help some other static analyzing tools to give us better warnings. Patch was generated with Coccinelle script and after that some style issue was hand fixed. Coccinelle script: virtual patch @alloc depends on patch@ expression x; @@ ( - #define QuadAlign(n) (((n) + 7u) & (~7u)) | - QuadAlign(x) + ALIGN(x, 8) | - #define IsQuadAligned(n) (!((size_t)(n)&7u)) | - IsQuadAligned(x) + IS_ALIGNED(x, 8) | - #define Quad2Align(n) (((n) + 15u) & (~15u)) | - Quad2Align(x) + ALIGN(x, 16) | - #define IsQuad2Aligned(n) (!((size_t)(n)&15u)) | - IsQuad2Aligned(x) + IS_ALIGNED(x, 16) | - #define Quad4Align(n) (((n) + 31u) & (~31u)) | - Quad4Align(x) + ALIGN(x, 32) | - #define IsSizeTAligned(n) (!((size_t)(n) & (sizeof(size_t) - 1))) | - IsSizeTAligned(x) + IS_ALIGNED(x, sizeof(size_t)) | - #define DwordAlign(n) (((n) + 3u) & (~3u)) | - DwordAlign(x) + ALIGN(x, 4) | - #define IsDwordAligned(n) (!((size_t)(n)&3u)) | - IsDwordAligned(x) + IS_ALIGNED(x, 4) | - #define WordAlign(n) (((n) + 1u) & (~1u)) | - WordAlign(x) + ALIGN(x, 2) | - #define IsWordAligned(n) (!((size_t)(n)&1u)) | - IsWordAligned(x) + IS_ALIGNED(x, 2) | ) Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Kari Argillander <kari.argillander@gmail.com> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2021-08-26 16:56:29 +08:00
asize = SIZEOF_RESIDENT + ALIGN(sd_size, 8);
attr->type = ATTR_SECURE;
attr->size = cpu_to_le32(asize);
attr->id = cpu_to_le16(aid++);
attr->res.data_off = SIZEOF_RESIDENT_LE;
attr->res.data_size = cpu_to_le32(sd_size);
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), sd, sd_size);
attr = Add2Ptr(attr, asize);
}
attr->id = cpu_to_le16(aid++);
if (fa & FILE_ATTRIBUTE_DIRECTORY) {
/*
* Regular directory or symlink to directory.
* Create root attribute.
*/
dsize = sizeof(struct INDEX_ROOT) + sizeof(struct NTFS_DE);
asize = sizeof(I30_NAME) + SIZEOF_RESIDENT + dsize;
attr->type = ATTR_ROOT;
attr->size = cpu_to_le32(asize);
attr->name_len = ARRAY_SIZE(I30_NAME);
attr->name_off = SIZEOF_RESIDENT_LE;
attr->res.data_off =
cpu_to_le16(sizeof(I30_NAME) + SIZEOF_RESIDENT);
attr->res.data_size = cpu_to_le32(dsize);
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), I30_NAME,
sizeof(I30_NAME));
root = Add2Ptr(attr, sizeof(I30_NAME) + SIZEOF_RESIDENT);
memcpy(root, dir_root, offsetof(struct INDEX_ROOT, ihdr));
root->ihdr.de_off =
cpu_to_le32(sizeof(struct INDEX_HDR)); // 0x10
root->ihdr.used = cpu_to_le32(sizeof(struct INDEX_HDR) +
sizeof(struct NTFS_DE));
root->ihdr.total = root->ihdr.used;
e = Add2Ptr(root, sizeof(struct INDEX_ROOT));
e->size = cpu_to_le16(sizeof(struct NTFS_DE));
e->flags = NTFS_IE_LAST;
} else if (S_ISLNK(mode)) {
/*
* Symlink to file.
* Create empty resident data attribute.
*/
asize = SIZEOF_RESIDENT;
/* Insert empty ATTR_DATA */
attr->type = ATTR_DATA;
attr->size = cpu_to_le32(SIZEOF_RESIDENT);
attr->name_off = SIZEOF_RESIDENT_LE;
attr->res.data_off = SIZEOF_RESIDENT_LE;
} else if (S_ISREG(mode)) {
/*
* Regular file. Create empty non resident data attribute.
*/
attr->type = ATTR_DATA;
attr->non_res = 1;
attr->nres.evcn = cpu_to_le64(-1ll);
if (fa & FILE_ATTRIBUTE_SPARSE_FILE) {
attr->size = cpu_to_le32(SIZEOF_NONRESIDENT_EX + 8);
attr->name_off = SIZEOF_NONRESIDENT_EX_LE;
attr->flags = ATTR_FLAG_SPARSED;
asize = SIZEOF_NONRESIDENT_EX + 8;
} else if (fa & FILE_ATTRIBUTE_COMPRESSED) {
attr->size = cpu_to_le32(SIZEOF_NONRESIDENT_EX + 8);
attr->name_off = SIZEOF_NONRESIDENT_EX_LE;
attr->flags = ATTR_FLAG_COMPRESSED;
attr->nres.c_unit = COMPRESSION_UNIT;
asize = SIZEOF_NONRESIDENT_EX + 8;
} else {
attr->size = cpu_to_le32(SIZEOF_NONRESIDENT + 8);
attr->name_off = SIZEOF_NONRESIDENT_LE;
asize = SIZEOF_NONRESIDENT + 8;
}
attr->nres.run_off = attr->name_off;
} else {
/*
* Node. Create empty resident data attribute.
*/
attr->type = ATTR_DATA;
attr->size = cpu_to_le32(SIZEOF_RESIDENT);
attr->name_off = SIZEOF_RESIDENT_LE;
if (fa & FILE_ATTRIBUTE_SPARSE_FILE)
attr->flags = ATTR_FLAG_SPARSED;
else if (fa & FILE_ATTRIBUTE_COMPRESSED)
attr->flags = ATTR_FLAG_COMPRESSED;
attr->res.data_off = SIZEOF_RESIDENT_LE;
asize = SIZEOF_RESIDENT;
ni->ni_flags |= NI_FLAG_RESIDENT;
}
if (S_ISDIR(mode)) {
ni->ni_flags |= NI_FLAG_DIR;
err = indx_init(&ni->dir, sbi, attr, INDEX_MUTEX_I30);
if (err)
goto out4;
} else if (S_ISLNK(mode)) {
rp = ntfs_create_reparse_buffer(sbi, symname, size, &nsize);
if (IS_ERR(rp)) {
err = PTR_ERR(rp);
rp = NULL;
goto out4;
}
/*
* Insert ATTR_REPARSE.
*/
attr = Add2Ptr(attr, asize);
attr->type = ATTR_REPARSE;
attr->id = cpu_to_le16(aid++);
/* Resident or non resident? */
fs/ntfs3: Use kernel ALIGN macros over driver specific The static checkers (Smatch) were complaining because QuadAlign() was buggy. If you try to align something higher than UINT_MAX it got truncated to a u32. Smatch warning was: fs/ntfs3/attrib.c:383 attr_set_size_res() warn: was expecting a 64 bit value instead of '~7' So that this will not happen again we will change all these macros to kernel made ones. This can also help some other static analyzing tools to give us better warnings. Patch was generated with Coccinelle script and after that some style issue was hand fixed. Coccinelle script: virtual patch @alloc depends on patch@ expression x; @@ ( - #define QuadAlign(n) (((n) + 7u) & (~7u)) | - QuadAlign(x) + ALIGN(x, 8) | - #define IsQuadAligned(n) (!((size_t)(n)&7u)) | - IsQuadAligned(x) + IS_ALIGNED(x, 8) | - #define Quad2Align(n) (((n) + 15u) & (~15u)) | - Quad2Align(x) + ALIGN(x, 16) | - #define IsQuad2Aligned(n) (!((size_t)(n)&15u)) | - IsQuad2Aligned(x) + IS_ALIGNED(x, 16) | - #define Quad4Align(n) (((n) + 31u) & (~31u)) | - Quad4Align(x) + ALIGN(x, 32) | - #define IsSizeTAligned(n) (!((size_t)(n) & (sizeof(size_t) - 1))) | - IsSizeTAligned(x) + IS_ALIGNED(x, sizeof(size_t)) | - #define DwordAlign(n) (((n) + 3u) & (~3u)) | - DwordAlign(x) + ALIGN(x, 4) | - #define IsDwordAligned(n) (!((size_t)(n)&3u)) | - IsDwordAligned(x) + IS_ALIGNED(x, 4) | - #define WordAlign(n) (((n) + 1u) & (~1u)) | - WordAlign(x) + ALIGN(x, 2) | - #define IsWordAligned(n) (!((size_t)(n)&1u)) | - IsWordAligned(x) + IS_ALIGNED(x, 2) | ) Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Kari Argillander <kari.argillander@gmail.com> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2021-08-26 16:56:29 +08:00
asize = ALIGN(SIZEOF_RESIDENT + nsize, 8);
t16 = PtrOffset(rec, attr);
/*
* Below function 'ntfs_save_wsl_perm' requires 0x78 bytes.
* It is good idea to keep extened attributes resident.
*/
if (asize + t16 + 0x78 + 8 > sbi->record_size) {
CLST alen;
CLST clst = bytes_to_cluster(sbi, nsize);
/* Bytes per runs. */
t16 = sbi->record_size - t16 - SIZEOF_NONRESIDENT;
attr->non_res = 1;
attr->nres.evcn = cpu_to_le64(clst - 1);
attr->name_off = SIZEOF_NONRESIDENT_LE;
attr->nres.run_off = attr->name_off;
attr->nres.data_size = cpu_to_le64(nsize);
attr->nres.valid_size = attr->nres.data_size;
attr->nres.alloc_size =
cpu_to_le64(ntfs_up_cluster(sbi, nsize));
err = attr_allocate_clusters(sbi, &ni->file.run, 0, 0,
clst, NULL, ALLOCATE_DEF,
&alen, 0, NULL, NULL);
if (err)
goto out5;
err = run_pack(&ni->file.run, 0, clst,
Add2Ptr(attr, SIZEOF_NONRESIDENT), t16,
&vcn);
if (err < 0)
goto out5;
if (vcn != clst) {
err = -EINVAL;
goto out5;
}
fs/ntfs3: Use kernel ALIGN macros over driver specific The static checkers (Smatch) were complaining because QuadAlign() was buggy. If you try to align something higher than UINT_MAX it got truncated to a u32. Smatch warning was: fs/ntfs3/attrib.c:383 attr_set_size_res() warn: was expecting a 64 bit value instead of '~7' So that this will not happen again we will change all these macros to kernel made ones. This can also help some other static analyzing tools to give us better warnings. Patch was generated with Coccinelle script and after that some style issue was hand fixed. Coccinelle script: virtual patch @alloc depends on patch@ expression x; @@ ( - #define QuadAlign(n) (((n) + 7u) & (~7u)) | - QuadAlign(x) + ALIGN(x, 8) | - #define IsQuadAligned(n) (!((size_t)(n)&7u)) | - IsQuadAligned(x) + IS_ALIGNED(x, 8) | - #define Quad2Align(n) (((n) + 15u) & (~15u)) | - Quad2Align(x) + ALIGN(x, 16) | - #define IsQuad2Aligned(n) (!((size_t)(n)&15u)) | - IsQuad2Aligned(x) + IS_ALIGNED(x, 16) | - #define Quad4Align(n) (((n) + 31u) & (~31u)) | - Quad4Align(x) + ALIGN(x, 32) | - #define IsSizeTAligned(n) (!((size_t)(n) & (sizeof(size_t) - 1))) | - IsSizeTAligned(x) + IS_ALIGNED(x, sizeof(size_t)) | - #define DwordAlign(n) (((n) + 3u) & (~3u)) | - DwordAlign(x) + ALIGN(x, 4) | - #define IsDwordAligned(n) (!((size_t)(n)&3u)) | - IsDwordAligned(x) + IS_ALIGNED(x, 4) | - #define WordAlign(n) (((n) + 1u) & (~1u)) | - WordAlign(x) + ALIGN(x, 2) | - #define IsWordAligned(n) (!((size_t)(n)&1u)) | - IsWordAligned(x) + IS_ALIGNED(x, 2) | ) Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Kari Argillander <kari.argillander@gmail.com> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2021-08-26 16:56:29 +08:00
asize = SIZEOF_NONRESIDENT + ALIGN(err, 8);
} else {
attr->res.data_off = SIZEOF_RESIDENT_LE;
attr->res.data_size = cpu_to_le32(nsize);
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), rp, nsize);
nsize = 0;
}
/* Size of symlink equals the length of input string. */
inode->i_size = size;
attr->size = cpu_to_le32(asize);
err = ntfs_insert_reparse(sbi, IO_REPARSE_TAG_SYMLINK,
&new_de->ref);
if (err)
goto out5;
rp_inserted = true;
}
attr = Add2Ptr(attr, asize);
attr->type = ATTR_END;
rec->used = cpu_to_le32(PtrOffset(rec, attr) + 8);
rec->next_attr_id = cpu_to_le16(aid);
/* Step 2: Add new name in index. */
err = indx_insert_entry(&dir_ni->dir, dir_ni, new_de, sbi, fnd, 0);
if (err)
goto out6;
/* Unlock parent directory before ntfs_init_acl. */
if (!fnd)
ni_unlock(dir_ni);
inode->i_generation = le16_to_cpu(rec->seq);
dir->i_mtime = dir->i_ctime = inode->i_atime;
if (S_ISDIR(mode)) {
inode->i_op = &ntfs_dir_inode_operations;
inode->i_fop = &ntfs_dir_operations;
} else if (S_ISLNK(mode)) {
inode->i_op = &ntfs_link_inode_operations;
inode->i_fop = NULL;
inode->i_mapping->a_ops = &ntfs_aops;
inode->i_size = size;
inode_nohighmem(inode);
} else if (S_ISREG(mode)) {
inode->i_op = &ntfs_file_inode_operations;
inode->i_fop = &ntfs_file_operations;
inode->i_mapping->a_ops =
is_compressed(ni) ? &ntfs_aops_cmpr : &ntfs_aops;
init_rwsem(&ni->file.run_lock);
} else {
inode->i_op = &ntfs_special_inode_operations;
init_special_inode(inode, mode, dev);
}
#ifdef CONFIG_NTFS3_FS_POSIX_ACL
if (!S_ISLNK(mode) && (sb->s_flags & SB_POSIXACL)) {
err = ntfs_init_acl(idmap, inode, dir);
if (err)
goto out7;
} else
#endif
{
inode->i_flags |= S_NOSEC;
}
/* Write non resident data. */
if (nsize) {
err = ntfs_sb_write_run(sbi, &ni->file.run, 0, rp, nsize, 0);
if (err)
goto out7;
}
/*
* Call 'd_instantiate' after inode->i_op is set
* but before finish_open.
*/
d_instantiate(dentry, inode);
ntfs_save_wsl_perm(inode);
mark_inode_dirty(dir);
mark_inode_dirty(inode);
/* Normal exit. */
goto out2;
out7:
/* Undo 'indx_insert_entry'. */
if (!fnd)
ni_lock_dir(dir_ni);
indx_delete_entry(&dir_ni->dir, dir_ni, new_de + 1,
le16_to_cpu(new_de->key_size), sbi);
/* ni_unlock(dir_ni); will be called later. */
out6:
if (rp_inserted)
ntfs_remove_reparse(sbi, IO_REPARSE_TAG_SYMLINK, &new_de->ref);
out5:
if (!S_ISDIR(mode))
run_deallocate(sbi, &ni->file.run, false);
out4:
clear_rec_inuse(rec);
clear_nlink(inode);
ni->mi.dirty = false;
discard_new_inode(inode);
out3:
ntfs_mark_rec_free(sbi, ino, false);
out2:
__putname(new_de);
kfree(rp);
out1:
if (err) {
if (!fnd)
ni_unlock(dir_ni);
return ERR_PTR(err);
}
unlock_new_inode(inode);
return inode;
}
int ntfs_link_inode(struct inode *inode, struct dentry *dentry)
{
int err;
struct ntfs_inode *ni = ntfs_i(inode);
struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
struct NTFS_DE *de;
/* Allocate PATH_MAX bytes. */
de = __getname();
if (!de)
return -ENOMEM;
/* Mark rw ntfs as dirty. It will be cleared at umount. */
ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
/* Construct 'de'. */
err = fill_name_de(sbi, de, &dentry->d_name, NULL);
if (err)
goto out;
err = ni_add_name(ntfs_i(d_inode(dentry->d_parent)), ni, de);
out:
__putname(de);
return err;
}
/*
* ntfs_unlink_inode
*
* inode_operations::unlink
* inode_operations::rmdir
*/
int ntfs_unlink_inode(struct inode *dir, const struct dentry *dentry)
{
int err;
struct ntfs_sb_info *sbi = dir->i_sb->s_fs_info;
struct inode *inode = d_inode(dentry);
struct ntfs_inode *ni = ntfs_i(inode);
struct ntfs_inode *dir_ni = ntfs_i(dir);
struct NTFS_DE *de, *de2 = NULL;
int undo_remove;
if (ntfs_is_meta_file(sbi, ni->mi.rno))
return -EINVAL;
/* Allocate PATH_MAX bytes. */
de = __getname();
if (!de)
return -ENOMEM;
ni_lock(ni);
if (S_ISDIR(inode->i_mode) && !dir_is_empty(inode)) {
err = -ENOTEMPTY;
goto out;
}
err = fill_name_de(sbi, de, &dentry->d_name, NULL);
if (err < 0)
goto out;
undo_remove = 0;
err = ni_remove_name(dir_ni, ni, de, &de2, &undo_remove);
if (!err) {
drop_nlink(inode);
dir->i_mtime = dir->i_ctime = current_time(dir);
mark_inode_dirty(dir);
inode->i_ctime = dir->i_ctime;
if (inode->i_nlink)
mark_inode_dirty(inode);
} else if (!ni_remove_name_undo(dir_ni, ni, de, de2, undo_remove)) {
_ntfs_bad_inode(inode);
} else {
if (ni_is_dirty(dir))
mark_inode_dirty(dir);
if (ni_is_dirty(inode))
mark_inode_dirty(inode);
}
out:
ni_unlock(ni);
__putname(de);
return err;
}
void ntfs_evict_inode(struct inode *inode)
{
truncate_inode_pages_final(&inode->i_data);
if (inode->i_nlink)
_ni_write_inode(inode, inode_needs_sync(inode));
invalidate_inode_buffers(inode);
clear_inode(inode);
ni_clear(ntfs_i(inode));
}
/*
* ntfs_translate_junction
*
* Translate a Windows junction target to the Linux equivalent.
* On junctions, targets are always absolute (they include the drive
* letter). We have no way of knowing if the target is for the current
* mounted device or not so we just assume it is.
*/
static int ntfs_translate_junction(const struct super_block *sb,
const struct dentry *link_de, char *target,
int target_len, int target_max)
{
int tl_len, err = target_len;
char *link_path_buffer = NULL, *link_path;
char *translated = NULL;
char *target_start;
int copy_len;
link_path_buffer = kmalloc(PATH_MAX, GFP_NOFS);
if (!link_path_buffer) {
err = -ENOMEM;
goto out;
}
/* Get link path, relative to mount point */
link_path = dentry_path_raw(link_de, link_path_buffer, PATH_MAX);
if (IS_ERR(link_path)) {
ntfs_err(sb, "Error getting link path");
err = -EINVAL;
goto out;
}
translated = kmalloc(PATH_MAX, GFP_NOFS);
if (!translated) {
err = -ENOMEM;
goto out;
}
/* Make translated path a relative path to mount point */
strcpy(translated, "./");
++link_path; /* Skip leading / */
for (tl_len = sizeof("./") - 1; *link_path; ++link_path) {
if (*link_path == '/') {
if (PATH_MAX - tl_len < sizeof("../")) {
ntfs_err(sb,
"Link path %s has too many components",
link_path);
err = -EINVAL;
goto out;
}
strcpy(translated + tl_len, "../");
tl_len += sizeof("../") - 1;
}
}
/* Skip drive letter */
target_start = target;
while (*target_start && *target_start != ':')
++target_start;
if (!*target_start) {
ntfs_err(sb, "Link target (%s) missing drive separator",
target);
err = -EINVAL;
goto out;
}
/* Skip drive separator and leading /, if exists */
target_start += 1 + (target_start[1] == '/');
copy_len = target_len - (target_start - target);
if (PATH_MAX - tl_len <= copy_len) {
ntfs_err(sb, "Link target %s too large for buffer (%d <= %d)",
target_start, PATH_MAX - tl_len, copy_len);
err = -EINVAL;
goto out;
}
/* translated path has a trailing / and target_start does not */
strcpy(translated + tl_len, target_start);
tl_len += copy_len;
if (target_max <= tl_len) {
ntfs_err(sb, "Target path %s too large for buffer (%d <= %d)",
translated, target_max, tl_len);
err = -EINVAL;
goto out;
}
strcpy(target, translated);
err = tl_len;
out:
kfree(link_path_buffer);
kfree(translated);
return err;
}
static noinline int ntfs_readlink_hlp(const struct dentry *link_de,
struct inode *inode, char *buffer,
int buflen)
{
int i, err = -EINVAL;
struct ntfs_inode *ni = ntfs_i(inode);
struct super_block *sb = inode->i_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
u64 size;
u16 ulen = 0;
void *to_free = NULL;
struct REPARSE_DATA_BUFFER *rp;
const __le16 *uname;
struct ATTRIB *attr;
/* Reparse data present. Try to parse it. */
static_assert(!offsetof(struct REPARSE_DATA_BUFFER, ReparseTag));
static_assert(sizeof(u32) == sizeof(rp->ReparseTag));
*buffer = 0;
attr = ni_find_attr(ni, NULL, NULL, ATTR_REPARSE, NULL, 0, NULL, NULL);
if (!attr)
goto out;
if (!attr->non_res) {
rp = resident_data_ex(attr, sizeof(struct REPARSE_DATA_BUFFER));
if (!rp)
goto out;
size = le32_to_cpu(attr->res.data_size);
} else {
size = le64_to_cpu(attr->nres.data_size);
rp = NULL;
}
if (size > sbi->reparse.max_size || size <= sizeof(u32))
goto out;
if (!rp) {
rp = kmalloc(size, GFP_NOFS);
if (!rp) {
err = -ENOMEM;
goto out;
}
to_free = rp;
/* Read into temporal buffer. */
err = ntfs_read_run_nb(sbi, &ni->file.run, 0, rp, size, NULL);
if (err)
goto out;
}
/* Microsoft Tag. */
switch (rp->ReparseTag) {
case IO_REPARSE_TAG_MOUNT_POINT:
/* Mount points and junctions. */
/* Can we use 'Rp->MountPointReparseBuffer.PrintNameLength'? */
if (size <= offsetof(struct REPARSE_DATA_BUFFER,
MountPointReparseBuffer.PathBuffer))
goto out;
uname = Add2Ptr(rp,
offsetof(struct REPARSE_DATA_BUFFER,
MountPointReparseBuffer.PathBuffer) +
le16_to_cpu(rp->MountPointReparseBuffer
.PrintNameOffset));
ulen = le16_to_cpu(rp->MountPointReparseBuffer.PrintNameLength);
break;
case IO_REPARSE_TAG_SYMLINK:
/* FolderSymbolicLink */
/* Can we use 'Rp->SymbolicLinkReparseBuffer.PrintNameLength'? */
if (size <= offsetof(struct REPARSE_DATA_BUFFER,
SymbolicLinkReparseBuffer.PathBuffer))
goto out;
uname = Add2Ptr(
rp, offsetof(struct REPARSE_DATA_BUFFER,
SymbolicLinkReparseBuffer.PathBuffer) +
le16_to_cpu(rp->SymbolicLinkReparseBuffer
.PrintNameOffset));
ulen = le16_to_cpu(
rp->SymbolicLinkReparseBuffer.PrintNameLength);
break;
case IO_REPARSE_TAG_CLOUD:
case IO_REPARSE_TAG_CLOUD_1:
case IO_REPARSE_TAG_CLOUD_2:
case IO_REPARSE_TAG_CLOUD_3:
case IO_REPARSE_TAG_CLOUD_4:
case IO_REPARSE_TAG_CLOUD_5:
case IO_REPARSE_TAG_CLOUD_6:
case IO_REPARSE_TAG_CLOUD_7:
case IO_REPARSE_TAG_CLOUD_8:
case IO_REPARSE_TAG_CLOUD_9:
case IO_REPARSE_TAG_CLOUD_A:
case IO_REPARSE_TAG_CLOUD_B:
case IO_REPARSE_TAG_CLOUD_C:
case IO_REPARSE_TAG_CLOUD_D:
case IO_REPARSE_TAG_CLOUD_E:
case IO_REPARSE_TAG_CLOUD_F:
err = sizeof("OneDrive") - 1;
if (err > buflen)
err = buflen;
memcpy(buffer, "OneDrive", err);
goto out;
default:
if (IsReparseTagMicrosoft(rp->ReparseTag)) {
/* Unknown Microsoft Tag. */
goto out;
}
if (!IsReparseTagNameSurrogate(rp->ReparseTag) ||
size <= sizeof(struct REPARSE_POINT)) {
goto out;
}
/* Users tag. */
uname = Add2Ptr(rp, sizeof(struct REPARSE_POINT));
ulen = le16_to_cpu(rp->ReparseDataLength) -
sizeof(struct REPARSE_POINT);
}
/* Convert nlen from bytes to UNICODE chars. */
ulen >>= 1;
/* Check that name is available. */
if (!ulen || uname + ulen > (__le16 *)Add2Ptr(rp, size))
goto out;
/* If name is already zero terminated then truncate it now. */
if (!uname[ulen - 1])
ulen -= 1;
err = ntfs_utf16_to_nls(sbi, uname, ulen, buffer, buflen);
if (err < 0)
goto out;
/* Translate Windows '\' into Linux '/'. */
for (i = 0; i < err; i++) {
if (buffer[i] == '\\')
buffer[i] = '/';
}
/* Always set last zero. */
buffer[err] = 0;
/* If this is a junction, translate the link target. */
if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
err = ntfs_translate_junction(sb, link_de, buffer, err, buflen);
out:
kfree(to_free);
return err;
}
static const char *ntfs_get_link(struct dentry *de, struct inode *inode,
struct delayed_call *done)
{
int err;
char *ret;
if (!de)
return ERR_PTR(-ECHILD);
ret = kmalloc(PAGE_SIZE, GFP_NOFS);
if (!ret)
return ERR_PTR(-ENOMEM);
err = ntfs_readlink_hlp(de, inode, ret, PAGE_SIZE);
if (err < 0) {
kfree(ret);
return ERR_PTR(err);
}
set_delayed_call(done, kfree_link, ret);
return ret;
}
// clang-format off
const struct inode_operations ntfs_link_inode_operations = {
.get_link = ntfs_get_link,
.setattr = ntfs3_setattr,
.listxattr = ntfs_listxattr,
.permission = ntfs_permission,
};
const struct address_space_operations ntfs_aops = {
.read_folio = ntfs_read_folio,
.readahead = ntfs_readahead,
.writepages = ntfs_writepages,
.write_begin = ntfs_write_begin,
.write_end = ntfs_write_end,
.direct_IO = ntfs_direct_IO,
.bmap = ntfs_bmap,
.dirty_folio = block_dirty_folio,
.migrate_folio = buffer_migrate_folio,
.invalidate_folio = block_invalidate_folio,
};
const struct address_space_operations ntfs_aops_cmpr = {
.read_folio = ntfs_read_folio,
.readahead = ntfs_readahead,
};
// clang-format on