summaryrefslogtreecommitdiff
path: root/fs
AgeCommit message (Collapse)AuthorFilesLines
2026-06-06ntfs: centalize $INDEX_ROOT header validationHyunchul Lee3-9/+23
Add a dedicated helper to perform stricter validation of $INDEX_ROOT and use it for both directory inodes and named index inodes. This keeps the root size and header geometry checks consistent across both read paths. Cc: stable@vger.kernel.org # v7.1 Tested-by: woot000 <woot000@woot000.com> Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06ntfs: validate index block header more strictlyHyunchul Lee3-61/+81
Modify ntfs_index_block_inconsisent() to perform stricter validation of INDEX_HEADER geometry in INDX blocks, and update ntfs_lookup_inode_by_name() to use that function to validate INDX blocks. Cc: stable@vger.kernel.org # v7.1 Tested-by: woot000 <woot000@woot000.com> Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06ntfs: avoid heap allocation for free-cluster readahead stateDaeMyung Kang1-16/+6
get_nr_free_clusters() allocates a temporary file_ra_state before it publishes the precomputed free cluster count, sets NVolFreeClusterKnown(), and wakes vol->free_waitq. If that allocation fails, the worker returns without setting the flag or waking waiters, so callers waiting for the free count can block indefinitely. The readahead state is only used synchronously while scanning the bitmap. Keep it on the stack and pass it by address to the readahead helper. This eliminates the early allocation failure path instead of adding a special case that publishes a conservative count and wakes the waitqueue. Zero-initialize the on-stack state because file_ra_state_init() only sets ra_pages and prev_pos. Apply the same treatment to __get_nr_free_mft_records(), which scans the MFT bitmap with the same short-lived readahead state. Cc: stable@vger.kernel.org # v7.1 Signed-off-by: DaeMyung Kang <charsyam@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06ntfs: skip extent mft records in writeback to prevent deadlockHyunchul Lee1-125/+4
This patch fixes the ABBA deadlock between extent_lock and extent mrec_lock triggered by xfstests generic/113, that occurs since the commit 6994acf33bae ("ntfs: use base mft_no when looking up base inode for extent record"). Path A (inode writeback): VFS writeback -> ntfs_write_inode() -> __ntfs_write_inode() -> mutex_lock(&ni->extent_lock) -> mutex_lock(&tni->mrec_lock) Path B (MFT folio writeback): VFS writeback of $MFT dirty folios -> ntfs_mft_writepages() -> ntfs_write_mft_block() -> ntfs_may_write_mft_record() -> holds one extent mrec_lock from a previous iteration -> tries to acquire another base inode extent_lock By removing all extent_lock and extent mrec_lock acquisition from the MFT folio writeback path, the ABBA lock ordering is eliminated: Path A: __ntfs_write_inode(): extent_lock -> mrec_lock Path B (removed): ntfs_write_mft_block(): mrec_lock -> extent_lock Path B is always redundant for extent records because: 1. mark_mft_record_dirty(ext_ni) does NOT dirty the MFT folio. It only sets NInoDirty(ext_ni) and marks the base VFS inode dirty via __mark_inode_dirty(I_DIRTY_DATASYNC), which triggers Path A. Therefore, normal extent modifications never create a situation where the MFT folio is dirty and Path B is not scheduled. 2. The MFT folio only gets dirtied via ntfs_mft_mark_dirty() inside ntfs_mft_record_alloc(). But all identified callers in attrib.c (ntfs_attr_add, ntfs_attr_record_move_away, ntfs_attr_make_non_resident, ntfs_attr_record_resize) follow through with mark_mft_record_dirty(), which triggers Path A to write the complete record. 3. ntfs_evict_big_inode() calls ntfs_commit_inode() before freeing extent inodes, ensuring all dirty extents are flushed via Path A before the base inode leaves the icache. Cc: stable@vger.kernel.org # v7.1 Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06ntfs: only alias volume $UpCase to default on exact matchDaeMyung Kang1-9/+3
load_and_init_upcase() currently aliases vol->upcase to the global default upcase whenever the shared prefix matches, and then truncates vol->upcase_len to that shorter prefix. The result is correct only by accident: upcase[] accesses in name collation are gated by upcase_len, so the prefix-equality alias produces the same fold output as keeping the volume's own shorter table. Still, prefix equality is not equality: the volume table is logically distinct from the default and should not be replaced by it unless they are byte-for-byte identical. Use memcmp() to compare the complete table in one expression and drop the now-redundant upcase_len rewrite. No user-visible change is expected for compliant volumes whose $UpCase has exactly default_upcase_len entries; shorter volume tables are no longer aliased to the default. Cc: stable@vger.kernel.org # v7.1 Signed-off-by: DaeMyung Kang <charsyam@gmail.com> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06ntfs: free volume-wide resources on fill_super failureDaeMyung Kang1-2/+3
ntfs_fill_super()'s err_out_now path frees only the volume struct via kfree(vol), leaving several vol-owned allocations behind on every mount failure: - vol->nls_map, loaded by ntfs_init_fs_context() via load_nls_default() (or replaced by an explicit nls= option in ntfs_parse_param()), is never unload_nls()'d. - vol->volume_label, allocated by load_system_files() through ntfs_ucstonls() once the $Volume name attribute has been parsed, is not released by load_system_files()'s own error labels nor by the fill_super() inline cleanup that only runs on d_make_root() failure. Any later failure inside load_system_files() leaks it. - vol->lcn_empty_bits_per_page was kvfree()'d in unl_upcase_iput_tmp_ino_err_out_now without clearing the pointer, so it could not be folded into a single common cleanup. Because the failure paths never call ntfs_volume_free() and never reach the d_make_root() inline cleanup block (it sits above the label and is jumped over by the load_system_files() / kvmalloc failure gotos), these resources accumulate per failed mount attempt with no chance of recovery short of unloading the module. This is a silent leak: the inodes loaded prior to failure remain hashed but generic_shutdown_super() skips evict_inodes() when sb->s_root is unset, so no CHECK_DATA_CORRUPTION warning is emitted either. Move the per-volume frees down to err_out_now and drop the lcn_empty_bits_per_page kvfree() from the upper label so the cleanup is performed exactly once on every failure path. Using unconditional kvfree() / kfree() / unload_nls() is safe because they all accept NULL and the upper labels that previously freed nls_map (the d_make_root() inline cleanup) already clear the pointer. Cc: stable@vger.kernel.org # v7.1 Signed-off-by: DaeMyung Kang <charsyam@gmail.com> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-05fs/read_write: Do not export __kernel_write() to the entire worldAndy Shevchenko1-3/+2
Since we have EXPORT_SYMBOL_FOR_MODULES(), we may narrow the __kernel_write() export to the only which really needs it. With that being done, update the respective comment. Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Link: https://patch.msgid.link/20260604095233.284067-1-andriy.shevchenko@linux.intel.com Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05iomap: Add IOMAP_F_ZERO_TAIL flag to trace event stringsNamjae Jeon1-1/+2
Add IOMAP_F_ZERO_TAIL to the flag string mapping in iomap trace events. This allows the new flag to be properly displayed in ftrace output when iomap operations use it. Signed-off-by: Namjae Jeon <linkinjeon@kernel.org> Link: https://patch.msgid.link/20260603144031.7370-1-linkinjeon@kernel.org Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05iomap: introduce iomap_fsverity_write() for writing fsverity metadataAndrey Albershteyn1-0/+25
This is just a wrapper around iomap_file_buffered_write() to create necessary iterator over metadata. Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org> Link: https://patch.msgid.link/20260520123722.405752-10-aalbersh@kernel.org Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05iomap: teach iomap to read files with fsverityAndrey Albershteyn2-2/+40
Obtain fsverity info for folios with file data and fsverity metadata. Filesystem can pass vi down to ioend and then to fsverity for verification. This is different from other filesystems ext4, f2fs, btrfs supporting fsverity, these filesystems don't need fsverity_info for reading fsverity metadata. While reading merkle tree iomap requires fsverity info to synthesize hashes for zeroed data block. fsverity metadata has two kinds of holes - ones in merkle tree and one after fsverity descriptor. Merkle tree holes are blocks full of hashes of zeroed data blocks. These are not stored on the disk but synthesized on the fly. This saves a bit of space for sparse files. Due to this iomap also need to lookup fsverity_info for folios with fsverity metadata. ->vi has a hash of the zeroed data block which will be used to fill the merkle tree block. The hole past descriptor is interpreted as end of metadata region. As we don't have EOF here we use this hole as an indication that rest of the folio is empty. This patch marks rest of the folio beyond fsverity descriptor as uptodate. For file data, fsverity needs to verify consistency of the whole file against the root hash, hashes of holes are included in the merkle tree. Verify them too. Issue reading of fsverity merkle tree on the fsverity inodes. This way metadata will be available at I/O completion time. Reviewed-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org> Link: https://patch.msgid.link/20260520123722.405752-9-aalbersh@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05iomap: introduce IOMAP_F_FSVERITY and teach writeback to handle fsverityAndrey Albershteyn2-10/+36
This flag indicates that I/O is for fsverity metadata. In the write path skip i_size check and i_size updates as metadata is past EOF. In writeback don't update i_size and continue writeback if even folio is beyond EOF. In read path don't zero fsverity folios, again they are past EOF. The iomap_block_needs_zeroing() is also called from write path. For folios of larger order we don't want to zero out pages in the folio as these could contain other merkle tree blocks. For fsverity, filesystem will request to read PAGE_SIZE memory regions. For data folios, iomap will zero the rest of the folio for anything which is beyond EOF. We don't want this for fsverity folios. Christian Brauner <brauner@kernel.org> says: Changed IOMAP_F_FSVERITY from (1U << 10) to (1U << 11) to avoid colliding with IOMAP_F_ZERO_TAIL, which already uses (1U << 10). Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org> Link: https://patch.msgid.link/20260520123722.405752-8-aalbersh@kernel.org Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05fsverity: generate and store zero-block hashAndrey Albershteyn4-2/+30
Compute the hash of one filesystem block's worth of zeros. A filesystem implementation can decide to elide merkle tree blocks containing only this hash and synthesize the contents at read time. Let's pretend that there's a file containing 131 data block and whose merkle tree looks roughly like this: root +--leaf0 | +--data0 | +--data1 | +--... | `--data128 `--leaf1 +--data129 +--data130 `--data131 If data[0-128] are sparse holes, then leaf0 will contain a repeating sequence of @zero_digest. Therefore, leaf0 need not be written to disk because its contents can be synthesized. A subsequent xfs patch will use this to reduce the size of the merkle tree when dealing with sparse gold master disk images and the like. Note that this works only on the first-level (data holes). fsverity doesn't store/generate zero_digest for any higher levels. Add a helper to pre-fill folio with hashes of empty blocks. This will be used by iomap to synthesize blocks full of zero hashes on the fly. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Acked-by: Eric Biggers <ebiggers@kernel.org> Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org> Link: https://patch.msgid.link/20260520123722.405752-5-aalbersh@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05iomap: introduce IOMAP_F_ZERO_TAIL flagNamjae Jeon1-0/+4
In filesystems that maintain a separate Valid Data Length, such as exFAT and NTFS, a partial write may start at or beyond the current valid_size and extend it. In this case, the region after the previous valid_size but within the same filesystem block is considered unwritten. This patch introduces IOMAP_F_ZERO_TAIL. When this flag is set in iomap, __iomap_write_begin() will zero only the tail portion while preserving any valid data before it in the same block. Without this tail zeroing, stale data in the unwritten portion of the block can remain in the page cache. Subsequent reads can then return incorrect contents from that region. Acked-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org> Link: https://patch.msgid.link/20260518114705.9601-2-linkinjeon@kernel.org Acked-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05proc: protect ptrace_may_access() with exec_update_lock (FD links)Jann Horn3-88/+58
proc_pid_get_link() and proc_pid_readlink() currently look up the task from the pid once, then do the ptrace access check on that task, then look up the task from the pid a second time to do the actual access. That's racy in several ways. To fix it, pass the task to the ->proc_get_link() handler, and instead of proc_fd_access_allowed(), introduce a new helper call_proc_get_link() that looks up and locks the task, does the access check, and calls ->proc_get_link(). Fixes: 778c1144771f ("[PATCH] proc: Use sane permission checks on the /proc/<pid>/fd/ symlinks") Cc: stable@vger.kernel.org Signed-off-by: Jann Horn <jannh@google.com> Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-2-5c3d20e0ac33@google.com Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05proc: protect ptrace_may_access() with exec_update_lock (part 1)Jann Horn3-19/+40
Fix the easy cases where procfs currently calls ptrace_may_access() without exec_update_lock protection, where the fix is to simply add the extra lock or use mm_access(): - do_task_stat(): grab exec_update_lock - proc_pid_wchan(): grab exec_update_lock - proc_map_files_lookup(): use mm_access() instead of get_task_mm() - proc_map_files_readdir(): use mm_access() instead of get_task_mm() - proc_ns_get_link(): grab exec_update_lock - proc_ns_readlink(): grab exec_update_lock Fixes: f83ce3e6b02d ("proc: avoid information leaks to non-privileged processes") Cc: stable@vger.kernel.org Signed-off-by: Jann Horn <jannh@google.com> Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-1-5c3d20e0ac33@google.com Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-06-05simple_lookup(): use d_splice_alias() for ->lookup() return valueAl Viro1-2/+1
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05ecryptfs: use d_splice_alias() for ->lookup() return valueAl Viro1-5/+3
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05configfs_lookup(): switch to d_splice_alias()Al Viro1-2/+1
more idiomatic Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05tracefs: use d_splice_alias() in ->lookup() instancesAl Viro1-4/+2
d_add() is not wrong there (inodes are freshly allocated), but d_splice_alias() is more idiomatic. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05make cursors NORCUAl Viro1-1/+3
All it requires is making sure that d_walk() will skip *all* CURSOR dentries, even if somebody passes it one as an argument. Cursors are negative and unhashed all along, never get added to LRU or to shrink lists and no RCU references via ->d_sib are possible for those - dentry_unlist() makes sure that no killed dentry has ->d_sib.next left pointing to a cursor. Seeing that a cursor is allocated every time we open a directory on autofs, debugfs, devpts, etc., avoiding an RCU delay when such opened files get closed is attractive... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05nfs: get rid of fake root dentriesAl Viro2-39/+4
... just grab the reference to the (real) root we are about to return for the first mount of this superblock and be done with that. Once upon a time dentry tree eviction at fs shutdown used to break if ->s_root had been spliced on top of something; that hadn't been the case for years now, and these fake root dentries violate a bunch of invariants. Let's get rid of them... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05wind ->s_roots via ->d_sib instead of ->d_hashAl Viro2-18/+48
shrink_dcache_for_umount() is supposed to handle the possibility of some of the dentries to be evicted being in other threads shrink lists; it either kills them, leaving an empty husk to be freed by the owner of shrink list whenever it gets around to that, or it waits for the eviction in progress to get completed. That relies upon dentry remaining attached to the tree until the eviction reaches dentry_unlist() and its ->d_sib gets removed from the list. Unfortunately, the secondary roots are linked via ->d_hash, rather than ->d_sib and they become removed from that list before their inode references are dropped. If shrink_dentry_list() from another thread ends up evicting one of the secondary roots and gets to that point in dentry_kill() when shrink_dcache_for_umount() is looking for secondary roots, the latter will *not* notice anything, possibly leading to warnings about busy inodes at umount time and all kinds of breakage after that. Moreover, shrink_dcache_for_umount() walks the list of secondary roots with no protection whatsoever, so it might end up calling dget() on a dentry that already passed through lockref_mark_dead(&dentry->d_lockref); ending up with corrupted refcount and possible UAF. AFAICS, the most straightforward way to deal with that would be to have secondary roots linked via ->d_sib rather than ->d_hash; then they would remain on the list until killed, and we could use d_add_waiter() machinery to wait for eviction in progress. Changes: * secondary roots look the same as ->s_root from d_unhashed() and d_unlinked() POV now. * secondary roots are represented as "no parent, but on ->d_sib" instead of "no parent, but on ->d_hash". * since ->d_sib is a plain hlist, we protect it with per-superblock spinlock (sb->s_roots_lock) instead of the LSB of the head pointer (for non-root dentries it would be protected by ->d_lock of parent). * __d_obtain_alias() uses ->d_sib for linkage when allocating a secondary root. * d_splice_alias_ops() detects splicing of a secondary root and removes it from the list before calling __d_move(). * dentry_unlist() detects eviction of a secondary root and removes it from the list; no need to play the games for d_walk() sake, since the latter is not going to look for the next sibling of those anyway. * ___d_drop() doesn't care about ->s_roots anymore. * shrink_dcache_for_umount() uses proper locking for access to the list of secondary roots and if it runs into one that is in the middle of eviction waits for that to finish. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05shrink_dentry_tree(): unify the calls of shrink_dentry_list()Al Viro1-17/+17
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05shrinking rcu_read_lock() scope in d_alloc_parallel()Al Viro1-17/+12
The current use of rcu_read_lock() uses in d_alloc_parallel() is fairly opaque - the single large scope serves two purposes. We start with lookup in normal hash, and there rcu_read_lock() scope puts __d_lookup_rcu() and subsequent lockref_get_not_dead() into the same RCU read-side critical area. If no match is found, we proceed to lock the hash chain of in-lookup hash and scan that for a match. If we find a match, we want to grab it and wait for lookup in progress to finish. Since the bitlock we use for these hash chains has to nest inside ->d_lock, we need to unlock the chain first and use lockref_get_not_dead() on the match. That has to be done without breaking the RCU read-side critical area, and we use the same rcu_read_lock() scope to bridge over. The thing is, after having grabbed the reference (and it is very unlikely to fail) we proceed to grab ->d_lock - d_wait_lookup() and __d_lookup_unhash()/__d_wake_in_lookup_waiters() are using that for serialization. That makes lockref_get_not_dead() pointless - trying to avoid grabbing ->d_lock for refcount increment, only to grab it anyway immediately after that. If we grab ->d_lock first and replace lockref_get_not_dead() with direct check for sign and increment if non-negative we can move rcu_read_unlock() to immediately after grabbing ->d_lock. Moreover, we don't need the RCU read-side critical area to be contiguous since before earlier __d_lookup_rcu() - we can just as well terminate the earlier one ASAP and call rcu_read_lock() again only after having found a match (if any) in the in-lookup hash chain. That makes the entire thing easier to follow and the purpose of those rcu_read_lock() calls easier to describe - the first scope is for __d_lookup_rcu() + lockref_get_not_dead(), the second one bridges over from the bitlock scope to the ->d_lock scope on the match found in in-lookup hash. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05d_walk(): shrink rcu_read_lock() scopeAl Viro1-4/+2
we only need it to bridge over from ->d_lock scope of child to ->d_lock scope of parent; dropping ->d_lock at rename_retry doesn't need to be in rcu_read_lock() scope. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05document dentry_kill()Al Viro1-0/+38
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05adjust calling conventions of lock_for_kill(), fold __dentry_kill() into ↵Al Viro1-58/+54
dentry_kill() Pull dropping ->d_lock on lock_for_kill() failure into lock_for_kill() itself. That reduces dentry_kill() to if (!lock_for_kill(dentry)) return NULL; return __dentry_kill(dentry); at which point it's easier to move that if (...) into the beginning of __dentry_kill() itself and rename it into dentry_kill(). Document the new calling conventions of lock_for_kill(). Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05Document rcu_read_lock() use in select_collect2()Al Viro1-1/+21
If select_collect2() finds something that is neither busy nor can be moved to shrink list, it needs to return that to caller's caller (shrink_dcache_tree()) ASAP and do so without grabbing references (among other things, it might be already dying, in which case refcount can't be incremented). We are called inside a ->d_lock scope, but that scope is going to be terminated as soon as we return to caller (d_walk()); ->d_lock will be retaken by shrink_dcache_tree(), but we need to bridge between these scopes, turning them into contiguous RCU read-side critical area. We do that with rcu_read_lock() scope - it spans from unbalanced rcu_read_lock() in select_collect2() to unbalanced rcu_read_unlock() in shrink_dcache_tree(). That works, but it really needs to be documented; it's rather unidiomatic and it had caused quite a bit of confusion - some of it in form of patches "fixing" the damn thing. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05Shift rcu_read_{,un}lock() inside fast_dput()Al Viro1-16/+14
Shrink rcu_read_lock() scopes surrounding fast_dput() calls. Both callers are immediately preceded and followed by rcu_read_lock()/rcu_read_unlock() resp. Shrink that down into fast_dput() itself; in case when fast_dput() ends up grabbing ->d_lock, we can pull rcu_read_unlock() up to right after spin_lock(). Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05simplify safety for lock_for_kill() slowpathAl Viro1-15/+32
rcu_read_lock() scopes in dentry eviction machinery are too wide and badly structured; we end up with too many of those, quite a few essentially identical. Worse, quite a few of the function involved are not neutral wrt that, making them harder to reason about. rcu_read_lock() scope is not the only thing establishing an RCU read-side critical area - spin_lock scope does the same and they can be mixed - the sequence rcu_read_lock() ... spin_lock() ... rcu_read_unlock() ... rcu_read_lock() ... spun_unlock() ... rcu_read_unlock() is an unbroken RCU read-side critical area. Use of that observation allows to simplify things. First of all, lock_for_kill() relies upon being in an unbroken RCU read-side critical area. It's always called with ->d_lock held, and normally returns without having ever dropped that spinlock. We would not need rcu_read_lock() at all, if not for the slow path - if trylock of inode->i_lock fails, we need to drop and retake ->d_lock. Having all calls of lock_for_kill() inside an rcu_read_lock() scope takes care of that, but to show that lock_for_kill() slow path is safe, we need to demonstrate such rcu_read_lock() scope for any call chain leading to lock_for_kill(). Which is not fun, seeing that there are 10 such scopes, with 5 distinct beginnings between them. Case 1: opens in dput() proceeds through fast_dput() grabbing ->d_lock, returning false into dput() and there a call of finish_dput() which calls dentry_kill(), which calls lock_for_kill(); ends in dentry_kill(), either right after lock_for_kill() success or right after dropping ->d_lock on lock_for_kill() failure. ->d_lock is held continuously all the way into lock_for_kill(). Case 2: opens in dentry_kill(), where we proceed to the same call of dentry_kill() as in case 1. ->d_lock is held since before the beginning of the scope and all the way into lock_for_kill(). Case 3: opens in select_collect2(), proceeds through the return to d_walk() and to shrink_dcache_tree() where we grab ->d_lock and proceed to call shrink_kill(), which calls dentry_kill(), then as in the previous scopes. Case 4: opens in shrink_dentry_list(), followed by call of shrink_kill(), then same as in case 3. ->d_lock is held since before the beginning of the scope and all the way into lock_for_kill(). Case 5: opens in shrink_kill(), where it's immediately followed by call of dentry_kill(), then same as in the previous scopes. ->d_lock is held since before the beginning of the scope all the way into lock_for_kill(). Note that in cases 2, 4 and 5 the slow path of lock_for_kill() is the only part of rcu_read_lock() scope that is not covered by spinlock scopes. In case 1 we have the area in fast_dput() as well and in case 3 - the return path from select_collect2() and chunk in shrink_dcache_tree() up to grabbing ->d_lock. Seeing that the reasons we need rcu_read_lock() in these additional areas are completely unrelated to lock_for_kill() slow path, the things get much more straightforward with * explicit rcu_read_lock() scope surrounding the area in slow path of lock_for_kill() where ->d_lock is not held * shrink_dentry_list() dropping rcu_read_lock() as soon as it has grabbed ->d_lock. * dput() dropping rcu_read_lock() just before calling finish_dput(). * rcu_read_lock() calls in finish_dput(), shrink_kill() and shrink_dentry_list() are removed, along with rcu_read_unlock() calls in dentry_kill(). RCU read-side critical areas are unchanged by that, safety of lock_for_kill() slow path is trivial to verify and a bunch of rcu_read_lock() scopes either gone or become easier to describe. Update the comments on locking conventions and memory safety considerations, including the NORCU case. Incidentally, all calls of fast_dput() are immediately preceded by rcu_read_lock() and followed by rcu_read_unlock() now, which will allow to simplify those on the next step... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05fold lock_for_kill() and __dentry_kill() into common helperAl Viro1-15/+13
There are two callers of lock_for_kill() and both are followed by the same sequence of actions: * in case of failure, drop ->d_lock, do rcu_read_unlock() and go away * in case of success, do rcu_read_unlock() followed by passing dentry to __dentry_kill(); if the latter returns NULL, go away. All calls of __dentry_kill() are paired with lock_for_kill() now; let's turn that sequence into a new helper (dentry_kill()) and switch to using it. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05fold lock_for_kill() into shrink_kill()Al Viro1-16/+7
Both callers have exact same shape. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05shrink_dentry_list(): start with removing from shrink listAl Viro1-9/+9
Currently we leave dentry on the list until we are done with lock_for_kill(). That guarantees that it won't have been even scheduled for removal until we remove it from the list and drop ->d_lock. We grab ->d_lock and rcu_read_lock() and call lock_for_kill(). There are four possible cases: 1) lock_for_kill() has succeeded; dentry and its inode (if any) are locked, dentry refcount is zero and we can remove it from shrink list and feed it to shrink_kill(). 2) lock_for_kill() fails since dentry has become busy. Nothing to do, rcu_read_unlock(), remove from shrink list, drop ->d_lock and move on. 3) lock_for_kill() fails since dentry is currently being killed - already entered __dentry_kill(), but hasn't reached dentry_unlist() yet. Nothing to do, we should just do rcu_read_unlock(), remove from shrink list so that whoever's executing __dentry_kill() would free it once they are done, drop ->d_lock and move on - same actions as in case (2). 4) lock_for_kill() fails since dentry has been killed (reached dentry_unlist(), DCACHE_DENTRY_KILLED set in ->d_flags). In that case whoever had been killing it had already seen it on our shrink list and skipped freeing it. At that point it's just a passive chunk of memory; rcu_read_unlock(), remove from the list, drop ->d_lock and use dentry_free() to schedule freeing. While that works, there's a simpler way to do it: * grab ->d_lock * remove dentry from our shrink list * if DCACHE_DENTRY_KILLED is already set, drop ->d_lock, call dentry_free() and move on. * otherwise grab rcu_read_lock() and call lock_for_free() * if lock_for_kill() succeeds, feed dentry to shrink_kill(), otherwise drop the locks and move on. The end result is equivalent to the old variant. The only difference arises if at the time we grab ->d_lock dentry had refcount 0 and lock_for_kill() had failed spin_trylock() and had to drop and regain ->d_lock. Otherwise nobody can observe at which point within the unbroken ->d_lock scope dentry had been removed from the shrink list - all accesses to ->d_lru are under ->d_lock. If ->d_lock had been dropped and regained, it is possible for another thread to feed that dentry to __dentry_kill(); if it doesn't get to dentry_unlist() before we regain ->d_lock, behaviour is still identical - it's case (3) and by the time __dentry_kill() would've gotten around to checking if the victim is on shrink list, it would've been already removed from ours. If __dentry_kill() from another thread *does* get to dentry_unlist(), in the old variant we would have __dentry_kill() leave calling dentry_free() to us and in the new one __dentry_kill() would've called dentry_free() itself. Since we are under rcu_read_lock(), we are guaranteed that actual freeing won't happen until we get around to rcu_read_unlock(). IOW, the new variant is still safe wrt UAF, if not for the same reason as the old one, and overall result is the same; the only difference is which threads ends up scheduling the actual freeing of dentry. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05d_prune_aliases(): make sure to skip NORCU aliasesAl Viro1-1/+2
Either they are busy (in which case they won't be moved to shrink list anyway) or they have a zero refcount, in which case we really shouldn't mess with them - whoever had dropped the refcount to zero is on the way to evicting and freeing them. That way we are guaranteed that only the thread that has dropped refcount of NORCU dentry to zero might call lock_for_kill() and __dentry_kill() for those. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05kill d_dispose_if_unused()Al Viro2-25/+28
Rename to_shrink_list() into __move_to_shrink_list(), document and export it. Switch d_dispose_if_unused() users to that and kill d_dispose_if_unused() itself. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05make to_shrink_list() return whether it has moved dentry to listAl Viro1-14/+9
... and make it check the refcount for being zero in addition to dentry not being on a shrink list already. Simplifies the callers... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05select_collect(): ignore dentries on shrink lists if they have positive ↵Al Viro1-3/+1
refcounts If all dentries we find have positive refcounts and some happen to be on shrink lists, there's no point trying to steal them in the select_collect2() phase - we won't be able to evict any of them. Busy on shrink lists is still busy... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05find_acceptable_alias(): skip NORCU aliases with zero refcountAl Viro1-5/+4
similar to d_find_any_alias() situation Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05fix a race between d_find_any_alias() and final dput() of NORCU dentriesAl Viro1-3/+18
Refcount of a NORCU dentry must not be incremented after having dropped to zero. Otherwise we might end up with the following race: CPU1: in fast_dput(d), rcu_read_lock(); CPU1: decrements refcount of d to 0 CPU1: notice that it's unhashed CPU2: grab a reference to d CPU2: dput(d), freeing d CPU1: ... looks like we need to evict d, let's grab ->d_lock, recheck the refcount, etc. and that spin_lock(&d->d_lock) ends up a UAF, despite still being in an RCU read-side critical area started back when the refcount had been positive. If not for DCACHE_NORCU in d->d_flags freeing would've been RCU-delayed, so we'd have grabbed ->d_lock, noticed the negative value stored into refcount by __dentry_kill(), dropped the locks and that would be it. For NORCU dentries freeing is _not_ delayed, though. Most of the non-counting references are excluded for NORCU dentries - they are not allowed to be hashed, they never get placed on LRU, they never get placed into anyone's list of children and while dput_to_list() might put them into a shrink list, nobody bumps refcount of something that had been reached that way. However, inode's list of aliases can be a problem - it does not contribute to dentry refcount (for obvious reasons) and we *do* have places that grab references to something found on that list - that's precisely what d_find_alias() is. In case of d_find_alias() we are safe - it skips unhashed aliases, so all NORCU ones are ignored there. d_find_any_alias() is *not* limited to hashed ones, though, and while it's usually called for directories (which never get NORCU dentries), there are callers that use it to get something for non-directories with no hashed aliases. Having d_find_any_alias() hit a NORCU dentry is not impossible - it can be easily arranged if you have CAP_DAC_READ_SEARCH (memfd_create() + mmap() + name_to_handle_at() for /proc/self/map_files/<...> + munmap() + open_by_handle_at() will do that, and adding a second memfd_create() for mount_fd makes it possible to do that without having memfd pinned). The race window is narrow, and it's probably not feasible on bare hardware, but... It's not hard to fix, fortunately: * separate __d_find_dir_alias() (== current __d_find_any_alias()) to be used for directory inodes. * provide dget_alias_ilocked() that would return false for NORCU dentries with zero refcount and return true incrementing refcount otherwise * make __d_find_any_alias() go over the list of aliases, using dget_alias_ilocked() and returning the alias it succeeds on (normally the first one). Any NORCU alias with zero refcount is going to be evicted by the thread that had dropped the final reference; this makes __d_find_any_alias() pretend it had lost the race with eviction. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05alloc_path_pseudo(): make sure we don't end up with NORCU dentries for ↵Al Viro1-0/+2
directories A lot of places relies upon directories never having NORCU dentries; currently that property holds, but the proof is not straightforward and rather brittle. It's better to have that verified in the sole caller of d_alloc_pseudo(), so that any future bugs in that direction were caught early. That way we can be sure that * current directory of any process is not NORCU * root directory of any process is not NORCU * starting point of any LOOKUP_RCU pathwalk is not NORCU * dget_parent() can rely upon ->d_parent not being NORCU * d_walk() and is_subdir() can rely upon the same * alloc_file_pseudo() won't create multiple aliases for a directory without having to go through a convoluted audit. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-05VFS: use wait_var_event for waiting in d_alloc_parallel()NeilBrown9-58/+56
Parallel lookup starts with a call of d_alloc_parallel(). That primitive either returns a matching hashed dentry or allocates a new one in the in-lookup state and returns it to the caller. Once the caller is done with lookup, it indicates so either by call of d_{splice_alias,add}() or by call of d_done_lookup(); at that point dentry leaves the in-lookup state. If d_alloc_parallel() finds a matching in-lookup dentry, it must wait for that dentry to leave the in-lookup state, one way or another. Currently by supplying wait_queue_head to d_alloc_parallel(). If d_alloc_parallel() creates a new in-lookup dentry, the address of that wait_queue_head is stored in ->d_wait of new dentry and stays there while it's in the in-lookup; subsequent d_alloc_parallel() will wait on the queue found in the matching in-lookup dentry. Transition out of in-lookup state wakes waiters on that queue (if any). That works, but the calling conventions are inconvenient - the caller must supply wait_queue_head and make sure that it survives at least until the new in-lookup dentry leaves the in-lookup state. That amounts to boilerplate in the d_alloc_parallel() callers that are followed by a call of d_lookup_done() in the same function; in cases like nfs asynchronous unlink it gets worse than that. This patch changes d_alloc_parallel() to use wake_up_var_locked() to wake up waiters, and wait_var_event_spinlock() to wait. dentry->d_lock is used for synchronisation as it is already held and the relevant times. That eliminates the need of caller-supplied wait_queue_head, simplifying the calling conventions. Better yet, we only need one bit of information stored in dentry itself: whether there are any waiters to be woken up, and that can be easily stored in ->d_flags; ->d_wait goes away. The reason we need that bit (DCACHE_LOOKUP_WAITERS) is that with wait_var machinery the queues are shared with all kinds of stuff and there's no way tell if any of the waiters have anything to do with our dentry; most of the time none of them will be relevant, so we need to avoid the pointless wakeups. Another benefit of the new scheme comes from the fact that wakeups have to be done outside of write-side critical areas of ->i_dir_seq; with the old scheme we need to carry the value picked from ->d_wait from __d_lookup_unhash() to the place where we actually wake the waiters up. Now we can just leave DCACHE_LOOKUP_WAITERS in ->d_flags until we get to doing wakeups - that's done within the same ->d_lock scope, so we are fine; new bit is accessed only under ->d_lock and it's seen only on dentries with DCACHE_PAR_LOOKUP in ->d_flags. __d_lookup_unhash() no longer needs to re-init ->d_lru. That was previously shared (in a union) with ->d_wait but ->d_wait is now gone so it no longer corrupts ->d_lru. Co-developed-by: Al Viro <viro@zeniv.linux.org.uk> # saner handling of flags Signed-off-by: NeilBrown <neil@brown.name> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-04ocfs2: fix out-of-bounds write in ocfs2_remove_refcount_extentIan Bridges1-2/+7
[BUG] Unlinking a refcounted file whose refcount tree has leaf blocks triggers a fortify panic due to an out-of-bounds write. [CAUSE] When the last leaf block is removed from a refcount tree, ocfs2_remove_refcount_extent() converts the root back to leaf mode with a bulk memset on &rb->rf_records. rf_records sits in an anonymous union with rf_list. rf_list.l_tree_depth aliases rf_records.rl_count, and is 0 for a single-level tree. With rl_count equal to 0, the memset writes past the 16-byte declared size of rf_records, which the fortify checker catches. [FIX] Replace the bulk memset on &rb->rf_records with a correctly-bounded memset on rl_recs[] alone, after setting rl_count to the correct value. Link: https://lore.kernel.org/ah3TESOsEO9j_JLU@dev Fixes: 2f26f58df041 ("ocfs2: annotate flexible array members with __counted_by_le()") Signed-off-by: Ian Bridges <icb@fastmail.org> Reported-by: syzbot+3ef989aae096b30f1663@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3ef989aae096b30f1663 Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2: fix race between ocfs2_control_install_private() and ↵Joseph Qi1-7/+3
ocfs2_control_release() Move atomic_inc(&ocfs2_control_opened) and the handshake state update inside ocfs2_control_lock to close a race window where ocfs2_control_release() can observe ocfs2_control_opened dropping to zero (resetting ocfs2_control_this_node and running_proto) while ocfs2_control_install_private() is about to bump the counter and mark the connection valid. Link: https://lore.kernel.org/20260601121618.1263346-1-joseph.qi@linux.alibaba.com Fixes: 3cfd4ab6b6b4 ("ocfs2: Add the local node id to the handshake.") Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com> Reported-by: Ginger <ginger.jzllee@gmail.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2/dlm: require a ref for locking_state debugfs openZhang Cen1-4/+12
debug_lockres_open() copies inode->i_private into struct debug_lockres and debug_lockres_release() later drops that pointer with dlm_put(). That only works if open successfully pins the struct dlm_ctxt. Today open calls dlm_grab(dlm) but ignores its return value. Once the last domain unregister has removed the context from dlm_domains, dlm_grab() returns NULL, yet open still stores the raw pointer and returns success. The later release path is outside the debugfs removal barrier, so it can call dlm_put() after dlm_free_ctxt_mem() has freed the context. KASAN reports this as a slab-use-after-free in dlm_put() called from debug_lockres_release(). Fail the open when dlm_grab() cannot acquire the reference and unwind the seq_file private state before returning. That keeps locking_state from handing out a file descriptor whose release path does not own the dlm_ctxt. The buggy scenario involves two paths, with each column showing the order within that path: locking_state debugfs open: last domain unregister: 1. debug_lockres_open() reads 1. dlm_unregister_domain() calls inode->i_private. dlm_complete_dlm_shutdown(). 2. debug_lockres_open() calls 2. shutdown removes the dlm_ctxt from dlm_grab(dlm) and gets NULL. dlm_domains. 3. open still stores the raw dlm 3. final teardown reaches pointer in dl->dl_ctxt and dlm_free_ctxt_mem() and frees it. returns success. 4. debug_lockres_release() later calls dlm_put(dl->dl_ctxt). Validation reproduced this kernel report: KASAN slab-use-after-free in dlm_put+0x82/0x200 RIP: 0033:0x7f4d349bc9e0 The buggy address belongs to the object at ffff888103a3c000 which belongs to the cache kmalloc-2k of size 2048 The buggy address is located 816 bytes inside of freed 2048-byte region [ffff888103a3c000, ffff888103a3c800) Write of size 4 Call trace: dump_stack_lvl+0x66/0xa0 (?:?) print_report+0xd0/0x630 (?:?) dlm_put+0x82/0x200 (?:?) srso_alias_return_thunk+0x5/0xfbef5 (?:?) __virt_addr_valid+0x188/0x2f0 (?:?) kasan_report+0xe4/0x120 (?:?) kasan_check_range+0x105/0x1b0 (?:?) debug_lockres_release+0x53/0x80 (fs/ocfs2/dlm/dlmdebug.c:587) dlm_put+0x9/0x200 (?:?) debug_lockres_release+0x5c/0x80 (fs/ocfs2/dlm/dlmdebug.c:587) full_proxy_release+0x67/0x90 (?:?) __fput+0x1df/0x4b0 (?:?) do_raw_spin_lock+0x10f/0x1b0 (?:?) fput_close_sync+0xd2/0x170 (?:?) __x64_sys_close+0x55/0x90 (?:?) do_syscall_64+0x10c/0x640 (arch/x86/entry/syscall_64.c:87) irqentry_exit+0xac/0x6e0 (?:?) entry_SYSCALL_64_after_hwframe+0x77/0x7f (?:?) Freed by task stack: kasan_save_stack+0x33/0x60 (?:?) kasan_save_track+0x14/0x30 (?:?) kasan_save_free_info+0x3b/0x60 (?:?) __kasan_slab_free+0x5f/0x80 (?:?) kfree+0x30f/0x580 (?:?) dlm_put+0x1ce/0x200 (?:?) dlm_unregister_domain+0xf6/0xb30 (?:?) o2cb_cluster_disconnect+0x6b/0x90 (?:?) ocfs2_cluster_disconnect+0x41/0x70 (?:?) ocfs2_dlm_shutdown+0x1c4/0x220 (?:?) ocfs2_dismount_volume+0x38a/0x550 (?:?) generic_shutdown_super+0xc3/0x220 (?:?) kill_block_super+0x29/0x60 (?:?) deactivate_locked_super+0x66/0xe0 (?:?) cleanup_mnt+0x13d/0x210 (?:?) task_work_run+0xfa/0x170 (?:?) exit_to_user_mode_loop+0xd6/0x430 (?:?) do_syscall_64+0x3cb/0x640 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x77/0x7f (?:?) Link: https://lore.kernel.org/20260531044714.1640172-1-rollkingzzc@gmail.com Fixes: 4e3d24ed1a12 ("ocfs2/dlm: Dumps the lockres' into a debugfs file") Assisted-by: Codex:gpt-5.5 Signed-off-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2: reject FITRIM ranges shorter than a clusterZhang Cen1-1/+1
ocfs2_trim_mainbm() trims the global bitmap in cluster units, but its too-short range validation only checks sb->s_blocksize. On filesystems with a cluster size larger than the block size, a FITRIM range that is at least one block but shorter than one cluster is accepted and shifted down to len == 0. The later start + len - 1 and len -= ... arithmetic then underflows and can drive trimming past the requested range. Reject ranges shorter than s_clustersize instead. That preserves the existing -EINVAL behavior for requests that cannot discard even one allocation unit and keeps zero-cluster trims out of the group walk. Link: https://lore.kernel.org/20260528151247.361854-1-rollkingzzc@gmail.com Fixes: aa89762c5480 ("ocfs2: return EINVAL if the given range to discard is less than block size") Assisted-by: Codex:gpt-5.5 Signed-off-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2: validate fast symlink target during inode readZhang Cen1-0/+23
ocfs2_validate_inode_block() already rejects several inconsistent self-contained dinodes before they are exposed to the rest of the filesystem. Fast symlinks need the same treatment. A zero-cluster symlink is treated as a fast symlink and later read through page_get_link() and ocfs2_fast_symlink_read_folio(). That path uses strnlen() on the inline payload and then copies len + 1 bytes into the folio. If a corrupt dinode stores an i_size that does not fit the inline area or omits the terminating NUL at i_size, that copy reads past the end of the inode block buffer. Reject zero-cluster symlink dinodes whose i_size exceeds the inline fast-symlink capacity or whose inline payload is not NUL-terminated exactly at i_size when the inode block is validated. This keeps malformed fast symlinks from reaching the read path. Validation reproduced this kernel report: KASAN use-after-free in ocfs2_fast_symlink_read_folio+0x12c/0x1f0 RIP: 0033:0x7f5c6d859aa7 Read of size 3905 Call trace: dump_stack_lvl+0x66/0xa0 (?:?) print_report+0xce/0x630 (?:?) ocfs2_fast_symlink_read_folio+0x12c/0x1f0 (fs/ocfs2/inode.c:?) srso_alias_return_thunk+0x5/0xfbef5 (?:?) __virt_addr_valid+0x19f/0x330 (?:?) kasan_report+0xe0/0x110 (?:?) kasan_check_range+0x105/0x1b0 (?:?) __asan_memcpy+0x23/0x60 (?:?) filemap_read_folio+0x27/0xe0 (?:?) filemap_read_folio+0x35/0xe0 (?:?) do_read_cache_folio+0x138/0x230 (?:?) __page_get_link+0x26/0x110 (?:?) page_get_link+0x2e/0x70 (?:?) vfs_readlink+0x15e/0x250 (?:?) touch_atime+0x4d/0x370 (?:?) do_readlinkat+0x186/0x200 (?:?) do_user_addr_fault+0x65a/0x890 (?:?) __x64_sys_readlink+0x46/0x60 (?:?) do_syscall_64+0x115/0x6a0 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x77/0x7f (?:?) Link: https://lore.kernel.org/20260528151230.361127-1-rollkingzzc@gmail.com Fixes: ea022dfb3c2a ("ocfs: simplify symlink handling") Assisted-by: Codex:gpt-5.5 Signed-off-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Gui-Dong Han <2045gemini@gmail.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2: add journal NULL check in ocfs2_checkpoint_inode()Joseph Qi1-0/+3
During unmount, ocfs2_journal_shutdown() frees the journal and sets osb->journal to NULL. Later, when VFS evicts remaining cached inodes, ocfs2_evict_inode() -> ocfs2_clear_inode() -> ocfs2_checkpoint_inode() -> ocfs2_ci_fully_checkpointed() dereferences osb->journal, causing a NULL pointer dereference. Fix this by adding a NULL check for osb->journal in ocfs2_checkpoint_inode(). If the journal is NULL, it has already been fully flushed and destroyed during shutdown, so there is nothing to checkpoint. Link: https://lore.kernel.org/20260531131645.3650299-1-joseph.qi@linux.alibaba.com Reported-by: Farhad Alemi <farhad.alemi@berkeley.edu> Fixes: da5e7c87827e ("ocfs2: cleanup journal init and shutdown") Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com> Tested-by: Farhad Alemi <farhad.alemi@berkeley.edu> Reviewed-by: Heming Zhao <heming.zhao@suse.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2: fix buffer head management in ocfs2_read_blocks()Dmitry Antipov1-3/+4
In ocfs2_read_blocks(), caller should't assume that buffer head returned by 'sb_getblk()' is exclusively owned and so 'put_bh()' always drops b_count from 1 to 0. If it is not so, buffer head remains on hold and likely to be returned by the next call to 'sb_getblk()' unchanged - that is, with BH_Uptodate bit set even if it has failed validation previously, thus allowing to insert that buffer head into OCFS2 metadata cache and submit it to upper layers. To avoid such a scenario, BH_Uptodate should be cleared immediately after 'validate()' callback has detected some data inconsistency. Link: https://lore.kernel.org/20260529094128.494293-1-dmantipov@yandex.ru Fixes: cf76c78595ca ("ocfs2: don't put and assigning null to bh allocated outside") Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru> Reported-by: syzbot+caacd220635a9cc3bac9@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=caacd220635a9cc3bac9 Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2: reject oversized group bitmap descriptorsZhang Cen1-0/+22
ocfs2_validate_gd_parent() only bounds bg_bits against the parent allocator's chain geometry. A malicious descriptor can still claim a bg_size/bg_bits pair that exceeds the bitmap bytes that physically fit in the group descriptor block, so later bitmap scans and bit updates can run past bg_bitmap. Add a physical-cap check based on ocfs2_group_bitmap_size() for the parent allocator type and reject descriptors whose bg_size or bg_bits exceed that capacity. Keep the existing chain geometry check so both the on-disk bitmap layout and the allocator metadata must agree before the descriptor is used. Validation reproduced this kernel report: KASAN use-after-free in _find_next_bit+0x7f/0xc0 Read of size 8 Call trace: dump_stack_lvl+0x66/0xa0 (?:?) print_report+0xd0/0x630 (?:?) _find_next_bit+0x7f/0xc0 (?:?) srso_alias_return_thunk+0x5/0xfbef5 (?:?) __virt_addr_valid+0x188/0x2f0 (?:?) kasan_report+0xe4/0x120 (?:?) ocfs2_find_max_contig_free_bits+0x35/0x70 (fs/ocfs2/suballoc.c:1375) ocfs2_block_group_set_bits+0x472/0x4b0 (fs/ocfs2/suballoc.c:1457) ocfs2_cluster_group_search+0x16b/0x440 (fs/ocfs2/suballoc.c:86) ocfs2_bg_discontig_fix_result+0x1ef/0x230 (fs/ocfs2/suballoc.c:1786) ocfs2_search_chain+0x8f8/0x10a0 (fs/ocfs2/suballoc.c:1886) get_page_from_freelist+0x70e/0x2370 (?:?) lock_release+0xc6/0x290 (?:?) do_raw_spin_unlock+0x9a/0x100 (?:?) kasan_unpoison+0x27/0x60 (?:?) __bfs+0x147/0x240 (?:?) get_page_from_freelist+0x83d/0x2370 (?:?) ocfs2_claim_suballoc_bits+0x38c/0xe70 (fs/ocfs2/suballoc.c:96) sched_domains_numa_masks_clear+0x70/0xd0 (?:?) check_irq_usage+0xe8/0xb70 (?:?) __ocfs2_claim_clusters+0x18d/0x4c0 (fs/ocfs2/suballoc.c:2497) check_path+0x24/0x50 (?:?) rcu_is_watching+0x20/0x50 (?:?) check_prev_add+0xfd/0xd00 (?:?) ocfs2_add_clusters_in_btree+0x17d/0x810 (fs/ocfs2/suballoc.c:?) __folio_batch_add_and_move+0x1f5/0x3d0 (?:?) ocfs2_add_inode_data+0xd9/0x120 (fs/ocfs2/suballoc.c:?) filemap_add_folio+0x105/0x1f0 (?:?) ocfs2_write_begin_nolock+0x29f7/0x2f80 (fs/ocfs2/suballoc.c:3043) ocfs2_read_inode_block+0xb5/0x110 (fs/ocfs2/suballoc.c:?) down_write+0xf5/0x180 (?:?) ocfs2_write_begin+0x180/0x240 (fs/ocfs2/suballoc.c:?) __mark_inode_dirty+0x758/0x9a0 (?:?) inode_to_bdi+0x41/0x90 (?:?) balance_dirty_pages_ratelimited_flags+0xf8/0x1d0 (?:?) generic_perform_write+0x252/0x440 (?:?) mnt_put_write_access_file+0x16/0x70 (?:?) file_update_time_flags+0xe4/0x200 (?:?) ocfs2_file_write_iter+0x80a/0x1320 (fs/ocfs2/suballoc.c:?) lock_acquire+0x184/0x2f0 (?:?) ksys_write+0xd2/0x170 (?:?) apparmor_file_permission+0xf5/0x310 (?:?) read_zero+0x8d/0x140 (?:?) lock_is_held_type+0x8f/0x100 (?:?) Link: https://lore.kernel.org/20260524111248.1429884-1-rollkingzzc@gmail.com Fixes: ccd979bdbce9 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Assisted-by: Codex:gpt-5.5 Signed-off-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-04ocfs2: rebase copied fsdlm LVB pointers in locking_stateZhang Cen1-0/+17
The locking_state debugfs iterator snapshots struct ocfs2_lock_res by value under ocfs2_dlm_tracking_lock and later formats that copy in ocfs2_dlm_seq_show(). That is fine for the inline fields, but the userspace fsdlm stack stores the LVB through lksb_fsdlm.sb_lvbptr. Once the iterator drops the tracking lock, a copied non-NULL sb_lvbptr still points into the original lockres owner, so teardown can free that container before the debugfs dump walks the raw LVB bytes. Rebase the copied sb_lvbptr to the copied l_lksb before dumping the raw LVB. The seq snapshot already carries the inline LVB storage reserved in struct ocfs2_dlm_lksb, so the debugfs reader can dump the copied bytes without borrowing the original lockres lifetime. The buggy scenario involves two paths, with each column showing the order within that path: locking_state reader: lockres teardown: 1. ocfs2_dlm_seq_start()/next() 1. file release or another owner copies struct ocfs2_lock_res teardown reaches 2. ocfs2_dlm_seq_show() formats ocfs2_lock_res_free() the copied row 2. the lockres is removed from the 3. ocfs2_dlm_lvb() follows the tracking list copied sb_lvbptr 3. the owner frees the original lockres container Validation reproduced this kernel report: KASAN slab-use-after-free in ocfs2_dlm_seq_show+0x1bd/0x430 RIP: 0033:0x7f8ec4b1e29d The buggy address belongs to the object at ffff88810a1e0800 which belongs to the cache kmalloc-1k of size 1024 The buggy address is located 368 bytes inside of freed 1024-byte region [ffff88810a1e0800, ffff88810a1e0c00) Read of size 1 Call trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 ocfs2_dlm_seq_show+0x1bd/0x430 (fs/ocfs2/dlmglue.c:3137) srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x19f/0x330 kasan_report+0xe0/0x110 seq_read_iter+0x29d/0x790 seq_read+0x20a/0x280 find_held_lock+0x2b/0x80 rcu_read_unlock+0x18/0x70 full_proxy_read+0x9e/0xd0 vfs_read+0x12c/0x590 ksys_read+0xd2/0x170 do_user_addr_fault+0x65a/0x890 do_syscall_64+0x115/0x6a0 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task stack: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 ocfs2_file_open+0x13e/0x300 do_dentry_open+0x233/0x7f0 vfs_open+0x5a/0x1b0 path_openat+0x66d/0x1540 do_file_open+0x186/0x2b0 do_sys_openat2+0xce/0x150 __x64_sys_openat+0xd0/0x140 do_syscall_64+0x115/0x6a0 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task stack: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x313/0x590 ocfs2_file_release+0x138/0x260 __fput+0x1df/0x4b0 fput_close_sync+0xd2/0x170 __x64_sys_close+0x55/0x90 do_syscall_64+0x115/0x6a0 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x77/0x7f Link: https://lore.kernel.org/20260525041726.4112882-1-rollkingzzc@gmail.com Fixes: cf4d8d75d8ab ("ocfs2: add fsdlm to stackglue") Assisted-by: Codex:gpt-5.5 Signed-off-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Jun Piao <piaojun@huawei.com> Cc: Heming Zhao <heming.zhao@suse.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>