From 8df35e16a92c002f046bc410998b89b07cfcd639 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 7 May 2025 15:34:38 +0800 Subject: fs/ntfs3: fix symlinks cannot be handled correctly The symlinks created in windows will be broken in linux by ntfs3, the patch fixes it. Signed-off-by: Rong Zhang Signed-off-by: Konstantin Komarov --- fs/ntfs3/dir.c | 3 +-- fs/ntfs3/frecord.c | 20 +++++++++++++++----- fs/ntfs3/inode.c | 42 ++++++++++++++++++++++-------------------- fs/ntfs3/ntfs.h | 3 +-- fs/ntfs3/ntfs_fs.h | 2 +- fs/ntfs3/xattr.c | 6 +++--- 6 files changed, 43 insertions(+), 33 deletions(-) diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c index b6da80c69ca6..4aab4d54b171 100644 --- a/fs/ntfs3/dir.c +++ b/fs/ntfs3/dir.c @@ -329,8 +329,7 @@ static inline bool ntfs_dir_emit(struct ntfs_sb_info *sbi, * It does additional locks/reads just to get the type of name. * Should we use additional mount option to enable branch below? */ - if (((fname->dup.fa & FILE_ATTRIBUTE_REPARSE_POINT) || - fname->dup.ea_size) && + if (fname->dup.extend_data && ino != ni->mi.rno) { struct inode *inode = ntfs_iget5(sbi->sb, &e->ref, NULL); if (!IS_ERR_OR_NULL(inode)) { diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index 756e1306fe6c..86ba90b4104f 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -3119,11 +3119,21 @@ static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup, } } - /* TODO: Fill reparse info. */ - dup->reparse = 0; - dup->ea_size = 0; + dup->extend_data = 0; - if (ni->ni_flags & NI_FLAG_EA) { + if (dup->fa & FILE_ATTRIBUTE_REPARSE_POINT) { + attr = ni_find_attr(ni, NULL, NULL, ATTR_REPARSE, NULL, 0, NULL, + NULL); + + if (attr) { + const struct REPARSE_POINT *rp; + + rp = resident_data_ex(attr, sizeof(struct REPARSE_POINT)); + /* If ATTR_REPARSE exists 'rp' can't be NULL. */ + if (rp) + dup->extend_data = rp->ReparseTag; + } + } else if (ni->ni_flags & NI_FLAG_EA) { attr = ni_find_attr(ni, attr, &le, ATTR_EA_INFO, NULL, 0, NULL, NULL); if (attr) { @@ -3132,7 +3142,7 @@ static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup, info = resident_data_ex(attr, sizeof(struct EA_INFO)); /* If ATTR_EA_INFO exists 'info' can't be NULL. */ if (info) - dup->ea_size = info->size_pack; + dup->extend_data = info->size; } } diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 0f0d27d4644a..0a653ee7fe08 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1350,7 +1350,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, 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; + fname->dup.extend_data = S_ISLNK(mode) ? IO_REPARSE_TAG_SYMLINK : 0; dsize = le16_to_cpu(new_de->key_size); asize = ALIGN(SIZEOF_RESIDENT + dsize, 8); @@ -1590,27 +1590,29 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, inode->i_flags |= S_NOSEC; } - /* - * ntfs_init_acl and ntfs_save_wsl_perm update extended attribute. - * The packed size of extended attribute is stored in direntry too. - * 'fname' here points to inside new_de. - */ - err = ntfs_save_wsl_perm(inode, &fname->dup.ea_size); - if (err) - goto out6; + if (!S_ISLNK(mode)) { + /* + * ntfs_init_acl and ntfs_save_wsl_perm update extended attribute. + * The packed size of extended attribute is stored in direntry too. + * 'fname' here points to inside new_de. + */ + err = ntfs_save_wsl_perm(inode, &fname->dup.extend_data); + if (err) + goto out6; - /* - * update ea_size in file_name attribute too. - * Use ni_find_attr cause layout of MFT record may be changed - * in ntfs_init_acl and ntfs_save_wsl_perm. - */ - attr = ni_find_attr(ni, NULL, NULL, ATTR_NAME, NULL, 0, NULL, NULL); - if (attr) { - struct ATTR_FILE_NAME *fn; + /* + * update ea_size in file_name attribute too. + * Use ni_find_attr cause layout of MFT record may be changed + * in ntfs_init_acl and ntfs_save_wsl_perm. + */ + attr = ni_find_attr(ni, NULL, NULL, ATTR_NAME, NULL, 0, NULL, NULL); + if (attr) { + struct ATTR_FILE_NAME *fn; - fn = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME); - if (fn) - fn->dup.ea_size = fname->dup.ea_size; + fn = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME); + if (fn) + fn->dup.extend_data = fname->dup.extend_data; + } } /* We do not need to update parent directory later */ diff --git a/fs/ntfs3/ntfs.h b/fs/ntfs3/ntfs.h index 1ff13b6f9613..552b97905813 100644 --- a/fs/ntfs3/ntfs.h +++ b/fs/ntfs3/ntfs.h @@ -561,8 +561,7 @@ struct NTFS_DUP_INFO { __le64 alloc_size; // 0x20: Data attribute allocated size, multiple of cluster size. __le64 data_size; // 0x28: Data attribute size <= Dataalloc_size. enum FILE_ATTRIBUTE fa; // 0x30: Standard DOS attributes & more. - __le16 ea_size; // 0x34: Packed EAs. - __le16 reparse; // 0x36: Used by Reparse. + __le32 extend_data; // 0x34: Extended data. }; // 0x38 diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 36b8052660d5..faf0f067879d 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -874,7 +874,7 @@ int ntfs_acl_chmod(struct mnt_idmap *idmap, struct dentry *dentry); ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); extern const struct xattr_handler *const ntfs_xattr_handlers[]; -int ntfs_save_wsl_perm(struct inode *inode, __le16 *ea_size); +int ntfs_save_wsl_perm(struct inode *inode, __le32 *ea_size); void ntfs_get_wsl_perm(struct inode *inode); /* globals from lznt.c */ diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index e0055dcf8fe3..4bf14cff2683 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -313,7 +313,7 @@ out: static noinline int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len, const void *value, size_t val_size, int flags, bool locked, - __le16 *ea_size) + __le32 *ea_size) { struct ntfs_inode *ni = ntfs_i(inode); struct ntfs_sb_info *sbi = ni->mi.sbi; @@ -522,7 +522,7 @@ update_ea: if (ea_info.size_pack != size_pack) ni->ni_flags |= NI_FLAG_UPDATE_PARENT; if (ea_size) - *ea_size = ea_info.size_pack; + *ea_size = ea_info.size; mark_inode_dirty(&ni->vfs_inode); out: @@ -950,7 +950,7 @@ out: * * save uid/gid/mode in xattr */ -int ntfs_save_wsl_perm(struct inode *inode, __le16 *ea_size) +int ntfs_save_wsl_perm(struct inode *inode, __le32 *ea_size) { int err; __le32 value; -- cgit v1.2.3 From b1e9d89408f402858c00103f9831b25ffa0994d3 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 7 May 2025 15:35:34 +0800 Subject: fs/ntfs3: correctly create symlink for relative path After applying this patch, could correctly create symlink: ln -s "relative/path/to/file" symlink Signed-off-by: Rong Zhang [almaz.alexandrovich@paragon-software.com: added cpu_to_le32 macro to rs->Flags assignment] Signed-off-by: Konstantin Komarov --- fs/ntfs3/inode.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 0a653ee7fe08..8bfe35477654 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1062,10 +1062,10 @@ int inode_read_data(struct inode *inode, void *data, size_t 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) +static inline u32 ntfs_reparse_bytes(u32 uni_len, bool is_absolute) { /* Header + unicode string + decorated unicode string. */ - return sizeof(short) * (2 * uni_len + 4) + + return sizeof(short) * (2 * uni_len + (is_absolute ? 4 : 0)) + offsetof(struct REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer); } @@ -1078,8 +1078,11 @@ ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname, struct REPARSE_DATA_BUFFER *rp; __le16 *rp_name; typeof(rp->SymbolicLinkReparseBuffer) *rs; + bool is_absolute; - rp = kzalloc(ntfs_reparse_bytes(2 * size + 2), GFP_NOFS); + is_absolute = (strlen(symname) > 1 && symname[1] == ':'); + + rp = kzalloc(ntfs_reparse_bytes(2 * size + 2, is_absolute), GFP_NOFS); if (!rp) return ERR_PTR(-ENOMEM); @@ -1094,7 +1097,7 @@ ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname, goto out; /* err = the length of unicode name of symlink. */ - *nsize = ntfs_reparse_bytes(err); + *nsize = ntfs_reparse_bytes(err, is_absolute); if (*nsize > sbi->reparse.max_size) { err = -EFBIG; @@ -1114,7 +1117,7 @@ ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname, /* PrintName + SubstituteName. */ rs->SubstituteNameOffset = cpu_to_le16(sizeof(short) * err); - rs->SubstituteNameLength = cpu_to_le16(sizeof(short) * err + 8); + rs->SubstituteNameLength = cpu_to_le16(sizeof(short) * err + (is_absolute ? 8 : 0)); rs->PrintNameLength = rs->SubstituteNameOffset; /* @@ -1122,16 +1125,18 @@ ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname, * parse this path. * 0-absolute path 1- relative path (SYMLINK_FLAG_RELATIVE). */ - rs->Flags = 0; + rs->Flags = cpu_to_le32(is_absolute ? 0 : SYMLINK_FLAG_RELATIVE); - memmove(rp_name + err + 4, rp_name, sizeof(short) * err); + memmove(rp_name + err + (is_absolute ? 4 : 0), 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('\\'); + if (is_absolute) { + /* 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: -- cgit v1.2.3 From e841ecb139339602bc1853f5f09daa5d1ea920a2 Mon Sep 17 00:00:00 2001 From: Lizhi Xu Date: Fri, 6 Jun 2025 13:16:16 +0800 Subject: fs/ntfs3: Add sanity check for file name The length of the file name should be smaller than the directory entry size. Reported-by: syzbot+598057afa0f49e62bd23@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=598057afa0f49e62bd23 Signed-off-by: Lizhi Xu Signed-off-by: Konstantin Komarov --- fs/ntfs3/dir.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c index 4aab4d54b171..1b5c865a0339 100644 --- a/fs/ntfs3/dir.c +++ b/fs/ntfs3/dir.c @@ -304,6 +304,9 @@ static inline bool ntfs_dir_emit(struct ntfs_sb_info *sbi, if (sbi->options->nohidden && (fname->dup.fa & FILE_ATTRIBUTE_HIDDEN)) return true; + if (fname->name_len + sizeof(struct NTFS_DE) > le16_to_cpu(e->size)) + return true; + name_len = ntfs_utf16_to_nls(sbi, fname->name, fname->name_len, name, PATH_MAX); if (name_len <= 0) { -- cgit v1.2.3 From d99208b91933fd2a58ed9ed321af07dacd06ddc3 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Wed, 18 Jun 2025 15:31:57 +0800 Subject: fs/ntfs3: cancle set bad inode after removing name fails The reproducer uses a file0 on a ntfs3 file system with a corrupted i_link. When renaming, the file0's inode is marked as a bad inode because the file name cannot be deleted. The underlying bug is that make_bad_inode() is called on a live inode. In some cases it's "icache lookup finds a normal inode, d_splice_alias() is called to attach it to dentry, while another thread decides to call make_bad_inode() on it - that would evict it from icache, but we'd already found it there earlier". In some it's outright "we have an inode attached to dentry - that's how we got it in the first place; let's call make_bad_inode() on it just for shits and giggles". Fixes: 78ab59fee07f ("fs/ntfs3: Rework file operations") Reported-by: syzbot+1aa90f0eb1fc3e77d969@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1aa90f0eb1fc3e77d969 Signed-off-by: Edward Adam Davis Signed-off-by: Konstantin Komarov --- fs/ntfs3/frecord.c | 7 +++---- fs/ntfs3/namei.c | 10 +++------- fs/ntfs3/ntfs_fs.h | 3 +-- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index 86ba90b4104f..37826288fbb0 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -3003,8 +3003,7 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni, * ni_rename - Remove one name and insert new name. */ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni, - struct ntfs_inode *ni, struct NTFS_DE *de, struct NTFS_DE *new_de, - bool *is_bad) + struct ntfs_inode *ni, struct NTFS_DE *de, struct NTFS_DE *new_de) { int err; struct NTFS_DE *de2 = NULL; @@ -3027,8 +3026,8 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni, err = ni_add_name(new_dir_ni, ni, new_de); if (!err) { err = ni_remove_name(dir_ni, ni, de, &de2, &undo); - if (err && ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo)) - *is_bad = true; + WARN_ON(err && ni_remove_name(new_dir_ni, ni, new_de, &de2, + &undo)); } /* diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index b807744fc6a9..0db7ca3b64ea 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -244,7 +244,7 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir, struct ntfs_inode *ni = ntfs_i(inode); struct inode *new_inode = d_inode(new_dentry); struct NTFS_DE *de, *new_de; - bool is_same, is_bad; + bool is_same; /* * de - memory of PATH_MAX bytes: * [0-1024) - original name (dentry->d_name) @@ -313,12 +313,8 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir, if (dir_ni != new_dir_ni) ni_lock_dir2(new_dir_ni); - is_bad = false; - err = ni_rename(dir_ni, new_dir_ni, ni, de, new_de, &is_bad); - if (is_bad) { - /* Restore after failed rename failed too. */ - _ntfs_bad_inode(inode); - } else if (!err) { + err = ni_rename(dir_ni, new_dir_ni, ni, de, new_de); + if (!err) { simple_rename_timestamp(dir, dentry, new_dir, new_dentry); mark_inode_dirty(inode); mark_inode_dirty(dir); diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index faf0f067879d..7fde06019ac6 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -577,8 +577,7 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni, struct NTFS_DE *de); int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni, - struct ntfs_inode *ni, struct NTFS_DE *de, struct NTFS_DE *new_de, - bool *is_bad); + struct ntfs_inode *ni, struct NTFS_DE *de, struct NTFS_DE *new_de); bool ni_is_dirty(struct inode *inode); -- cgit v1.2.3 From 519b078998ce6e729f98dccf35505b4756985d11 Mon Sep 17 00:00:00 2001 From: Konstantin Komarov Date: Tue, 24 Jun 2025 16:35:39 +0300 Subject: fs/ntfs3: Exclude call make_bad_inode for live nodes. Use ntfs_inode field 'ni_bad' to mark inode as bad (if something went wrong) and to avoid any operations Suggested-by: Al Viro Signed-off-by: Konstantin Komarov --- fs/ntfs3/file.c | 32 ++++++++++++++++++++++++++++++++ fs/ntfs3/frecord.c | 4 ++++ fs/ntfs3/fsntfs.c | 6 +++++- fs/ntfs3/inode.c | 18 ++++++++++++++++++ fs/ntfs3/namei.c | 16 ++++++++++++++++ fs/ntfs3/ntfs_fs.h | 12 ++++++++++++ fs/ntfs3/xattr.c | 16 ++++++++++++++++ 7 files changed, 103 insertions(+), 1 deletion(-) diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 1e99a35691cd..65fb27d1e17c 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -57,6 +57,10 @@ long ntfs_ioctl(struct file *filp, u32 cmd, unsigned long arg) struct inode *inode = file_inode(filp); struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ntfs_i(inode)))) + return -EINVAL; + switch (cmd) { case FITRIM: return ntfs_ioctl_fitrim(sbi, arg); @@ -81,6 +85,10 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, struct inode *inode = d_inode(path->dentry); struct ntfs_inode *ni = ntfs_i(inode); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + stat->result_mask |= STATX_BTIME; stat->btime = ni->i_crtime; stat->blksize = ni->mi.sbi->cluster_size; /* 512, 1K, ..., 2M */ @@ -271,6 +279,10 @@ static int ntfs_file_mmap(struct file *file, struct vm_area_struct *vma) bool rw = vma->vm_flags & VM_WRITE; int err; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -735,6 +747,10 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode = inode->i_mode; int err; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -795,6 +811,10 @@ static int check_read_restriction(struct inode *inode) { struct ntfs_inode *ni = ntfs_i(inode); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -1130,6 +1150,10 @@ static int check_write_restriction(struct inode *inode) { struct ntfs_inode *ni = ntfs_i(inode); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -1212,6 +1236,10 @@ int ntfs_file_open(struct inode *inode, struct file *file) { struct ntfs_inode *ni = ntfs_i(inode); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -1281,6 +1309,10 @@ int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, int err; struct ntfs_inode *ni = ntfs_i(inode); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + err = fiemap_prep(inode, fieinfo, start, &len, ~FIEMAP_FLAG_XATTR); if (err) return err; diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index 37826288fbb0..8f9fe1d7a690 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -3208,6 +3208,10 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint) if (is_bad_inode(inode) || sb_rdonly(sb)) return 0; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(sb))) return -EIO; diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c index df81f1f7330c..c7a2f191254d 100644 --- a/fs/ntfs3/fsntfs.c +++ b/fs/ntfs3/fsntfs.c @@ -905,9 +905,13 @@ void ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait) void ntfs_bad_inode(struct inode *inode, const char *hint) { struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info; + struct ntfs_inode *ni = ntfs_i(inode); ntfs_inode_err(inode, "%s", hint); - make_bad_inode(inode); + + /* Do not call make_bad_inode()! */ + ni->ni_bad = true; + /* Avoid recursion if bad inode is $Volume. */ if (inode->i_ino != MFT_REC_VOL && !(sbi->flags & NTFS_FLAGS_LOG_REPLAYING)) { diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 8bfe35477654..4f9020df8912 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -878,6 +878,10 @@ static int ntfs_resident_writepage(struct folio *folio, struct ntfs_inode *ni = ntfs_i(inode); int ret; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -896,6 +900,10 @@ static int ntfs_writepages(struct address_space *mapping, { struct inode *inode = mapping->host; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ntfs_i(inode)))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -919,6 +927,10 @@ int ntfs_write_begin(struct file *file, struct address_space *mapping, struct inode *inode = mapping->host; struct ntfs_inode *ni = ntfs_i(inode); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; @@ -1265,6 +1277,12 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, goto out1; } + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(dir_ni))) { + err = -EINVAL; + goto out2; + } + if (unlikely(ntfs3_forced_shutdown(sb))) { err = -EIO; goto out2; diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index 0db7ca3b64ea..82c8ae56beee 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -171,6 +171,10 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry) struct ntfs_inode *ni = ntfs_i(dir); int err; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(dir->i_sb))) return -EIO; @@ -191,6 +195,10 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, { u32 size = strlen(symname); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ntfs_i(dir)))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(dir->i_sb))) return -EIO; @@ -216,6 +224,10 @@ static int ntfs_rmdir(struct inode *dir, struct dentry *dentry) struct ntfs_inode *ni = ntfs_i(dir); int err; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(dir->i_sb))) return -EIO; @@ -256,6 +268,10 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir, 1024); static_assert(PATH_MAX >= 4 * 1024); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(sb))) return -EIO; diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 7fde06019ac6..9a25fec25f01 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -377,6 +377,13 @@ struct ntfs_inode { */ u8 mi_loaded; + /* + * Use this field to avoid any write(s). + * If inode is bad during initialization - use make_bad_inode + * If inode is bad during operations - use this field + */ + u8 ni_bad; + union { struct ntfs_index dir; struct { @@ -1024,6 +1031,11 @@ static inline bool is_compressed(const struct ntfs_inode *ni) (ni->ni_flags & NI_FLAG_COMPRESSED_MASK); } +static inline bool is_bad_ni(const struct ntfs_inode *ni) +{ + return ni->ni_bad; +} + static inline int ni_ext_compress_bits(const struct ntfs_inode *ni) { return 0xb + (ni->ni_flags & NI_FLAG_COMPRESSED_MASK); diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index 4bf14cff2683..e519e21596a7 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -552,6 +552,10 @@ struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int err; void *buf; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return ERR_PTR(-EINVAL); + /* Allocate PATH_MAX bytes. */ buf = __getname(); if (!buf) @@ -600,6 +604,10 @@ static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap, int flags; umode_t mode; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ntfs_i(inode)))) + return -EINVAL; + if (S_ISLNK(inode->i_mode)) return -EOPNOTSUPP; @@ -730,6 +738,10 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) struct ntfs_inode *ni = ntfs_i(inode); ssize_t ret; + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (!(ni->ni_flags & NI_FLAG_EA)) { /* no xattr in file */ return 0; @@ -751,6 +763,10 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de, int err; struct ntfs_inode *ni = ntfs_i(inode); + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ni))) + return -EINVAL; + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) return -EIO; -- cgit v1.2.3 From a49f0abd8959048af18c6c690b065eb0d65b2d21 Mon Sep 17 00:00:00 2001 From: Konstantin Komarov Date: Fri, 4 Jul 2025 15:11:32 +0200 Subject: Revert "fs/ntfs3: Replace inode_trylock with inode_lock" This reverts commit 69505fe98f198ee813898cbcaf6770949636430b. Initially, conditional lock acquisition was removed to fix an xfstest bug that was observed during internal testing. The deadlock reported by syzbot is resolved by reintroducing conditional acquisition. The xfstest bug no longer occurs on kernel version 6.16-rc1 during internal testing. I assume that changes in other modules may have contributed to this. Fixes: 69505fe98f19 ("fs/ntfs3: Replace inode_trylock with inode_lock") Reported-by: syzbot+a91fcdbd2698f99db8f4@syzkaller.appspotmail.com Suggested-by: Lorenzo Stoakes Signed-off-by: Konstantin Komarov --- fs/ntfs3/file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 65fb27d1e17c..2e321b84a1ed 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -322,7 +322,10 @@ static int ntfs_file_mmap(struct file *file, struct vm_area_struct *vma) } if (ni->i_valid < to) { - inode_lock(inode); + if (!inode_trylock(inode)) { + err = -EAGAIN; + goto out; + } err = ntfs_extend_initialized_size(file, ni, ni->i_valid, to); inode_unlock(inode); -- cgit v1.2.3