summaryrefslogtreecommitdiff
path: root/fs
AgeCommit message (Collapse)Author
26 hoursMerge tag 'v7.2-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6HEADmasterLinus Torvalds
Pull smb client fixes from Steve French: - Fix leak in cifs_close_deferred_file() - Fix resolving MacOS symlinks - Fix stale file size in readdir - Update git branches in MAINTAINERS file - Fix bounds check in cifs_filldir - Fix checks in parse_dfs_referrals() - Fix DFS referral checks for malformed packet * tag 'v7.2-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6: cifs: fix cifsFileInfo leak on kmalloc failure in deferred close drain paths cifs: prevent readdir from changing file size due to stale directory metadata smb: client: handle STATUS_STOPPED_ON_SYMLINK responses without a symlink target Add missing git branch info for cifs and ksmbd to MAINTAINERS file smb: client: bound dirent name against end of SMB response in cifs_filldir smb: client: validate DFS referral PathConsumed
3 dayscifs: fix cifsFileInfo leak on kmalloc failure in deferred close drain pathsFrank Sorenson
In cifs_close_deferred_file(), cifs_close_all_deferred_files(), and cifs_close_deferred_file_under_dentry(), when a pending deferred close is cancelled via cancel_delayed_work(), the subsequent kmalloc_obj() to add the file to the local processing list may fail under memory pressure. The loop breaks immediately, but the cancelled work is no longer pending (it would have called _cifsFileInfo_put()), and the cfile is never added to file_head for processing. The cifsFileInfo reference and the open server handle both leak. Fix by saving the cfile that failed allocation in a local variable, breaking as before, and calling _cifsFileInfo_put() on it after releasing the lock. Any files later in the iteration are unaffected since their deferred work is still pending and will fire normally. Fixes: e3fc065682eb ("cifs: Deferred close performance improvements") Signed-off-by: Frank Sorenson <sorenson@redhat.com> Signed-off-by: Steve French <stfrench@microsoft.com>
3 dayscifs: prevent readdir from changing file size due to stale directory metadataFrank Sorenson
Windows Server's directory enumeration metadata lags behind the actual file size after a write+close or rename. A concurrent readdir() in the window between close() returning to userspace and stat() being called overwrites the correct cached i_size with the stale server value, causing stat() to return the wrong size. Once _cifsFileInfo_put() removes the last writable handle from openFileList, is_size_safe_to_change() permits readdir to overwrite i_size. smb2_close_getattr() then stamps cifs_i->time = jiffies, making the corrupt cached value appear fresh to the next stat(). The existing check (see Fixes:) only blocked stale size updates while an active RW lease was held, not after the last writable handle closes. Add cifsInodeInfo->time_last_write, written via smp_store_release() at writable close and on setattr/truncate. is_size_safe_to_change() checks is_inode_writable() first (acquiring open_file_lock), then rejects a readdir size update if time_last_write falls within acregmax jiffies. The spinlock release in _cifsFileInfo_put() forms a store-release barrier that pairs with the spin_lock() (load-acquire) in is_inode_writable(), ensuring the subsequent smp_load_acquire() on time_last_write observes any update from a concurrent close(). When a size update is rejected and the server value differs from the cached one, cifs_i->time is cleared to force a fresh QUERY_INFO on the next stat(). readdir is also blocked from changing i_size while writable handles are open or an RW lease is held, even on direct-IO mounts. For deferred close (closetimeo > 0), time_last_write is refreshed at the actual server close in smb2_deferred_work_close() and in the cifs_close_deferred_file*() drain paths invoked by lease/oplock breaks and tcon teardown, anchoring the protection window to the real close time rather than the earlier userspace close. time_last_write == 0 skips the time_before() check to avoid false positives near boot on 32-bit systems where jiffies starts close to INITIAL_JIFFIES. Does not reproduce against Samba or with actimeo=0. Fixes: e4b61f3b1c67 ("cifs: prevent updating file size from server if we have a read/write lease") Signed-off-by: Frank Sorenson <sorenson@redhat.com> Signed-off-by: Steve French <stfrench@microsoft.com>
3 dayssmb: client: handle STATUS_STOPPED_ON_SYMLINK responses without a symlink targetCarl Johnson
The macOS built-in SMB server returns STATUS_STOPPED_ON_SYMLINK for a CREATE on a path whose final component is a symlink, but it does not include a Symbolic Link Error Response in the error data: both ErrorContextCount and ByteCount are zero, so the symlink target is not present in the response at all. Per [MS-SMB2] section 2.2.2 such a response should carry a valid Symbolic Link Error Response, so this is a server bug, but the target can still be retrieved with FSCTL_GET_REPARSE_POINT. Frame from a capture against macOS 26.5.2 (build 25F84): SMB2 hdr : Status=0x8000002d STATUS_STOPPED_ON_SYMLINK, Cmd=Create Error Rsp: StructureSize=0x0009 Error Context Count: 0 Byte Count: 0 Error Data: 00 symlink_data() cannot find a struct smb2_symlink_err_rsp in such a response and returns -EINVAL, which parse_create_response() propagates, so smb2_query_path_info() bails out at if (rc || !data->reparse_point) goto out; before it can retry with SMB2_OP_GET_REPARSE. stat(), readlink() and ls of any server-side symlink then fail with -EINVAL: $ ls -la Config l????????? ? ? ? ? ? Config.json $ stat Config/Config.json stat: cannot statx 'Config/Config.json': Invalid argument A 5.10 client resolves these symlinks correctly against the same server and share, so this is a regression for Apple SMB servers. Handle it in several places: - symlink_data() detects the empty response (ErrorContextCount and ByteCount both zero) and returns a distinct -ENODATA, so that "server did not send the target" can be told apart from a genuinely malformed response and only this case is worked around. - parse_create_response() treats -ENODATA like STATUS_IO_REPARSE_TAG_NOT_HANDLED, which does not carry the target either: leave the reparse tag unset and clear rc, so the existing SMB2_OP_GET_REPARSE path retrieves the target. - smb2_query_path_info() only fixes up the symlink target type when the target is already known. SMB2_OP_GET_REPARSE sets data->reparse.tag but does not parse the target out of the reparse buffer; that happens later, in reparse_info_to_fattr(). Without this check smb2_fix_symlink_target_type() is called with a NULL target and returns -EIO. This could not happen with servers that send the target inline and therefore skip SMB2_OP_GET_REPARSE. - smb2_open_file() maps -ENODATA to -EIO, matching STATUS_IO_REPARSE_TAG_NOT_HANDLED, so its callers retrieve the target with SMB2_OP_GET_REPARSE as well. Tested on Debian 13, kernel 6.18.38 (armv7), against macOS 26.5.2: symlinks now resolve, including relative, parent-traversing and directory symlinks, and reads through symlinks succeed. Cc: stable@vger.kernel.org Co-developed-by: Pali Rohár <pali@kernel.org> Signed-off-by: Pali Rohár <pali@kernel.org> Signed-off-by: Carl Johnson <carl@jpartners.org> Signed-off-by: Steve French <stfrench@microsoft.com>
3 daysMerge tag 'nfsd-7.2-2' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux Pull nfsd fix from Chuck Lever: - Fix issue with NLMv3 GRANTED_MSG introduced in v7.2 * tag 'nfsd-7.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux: lockd: fix NLMv3 GRANTED_MSG handling
3 daysMerge tag 'for-7.2-rc4-tag' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux Pull btrfs fixes from David Sterba: "I'm catching up with the fix backlog in the development branch, so here's a number of them and will probably send one more for this or the next rc: - relocation fixes: - skip attempting compression on reloc inodes - exclude inline extents from file extent offset checks - fix minor memory leak after error when adding reloc root - fix root cleanup after inserting and merging - fix clearing folio tags after writeback - clear logging flag of extent map before splitting - fix unsigned 32/64 type conversions when accounting dirty metadata, leading to continually exceeding threshold - fix regression in 32bit compat ioctl for subvolume info - fix type of SEARCH_TREE ioctl buffer in UAPI header - fix expression in ASSERT expression which can be unconditionally evaluated on some compilers - only account delalloc bytes for regular inodes" * tag 'for-7.2-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux: btrfs: fix GET_SUBVOL_INFO after compat refactor btrfs: free mapping node on duplicate reloc root insert btrfs: fix a regression where PAGECACHE_TAG_DIRTY is never cleared btrfs: don't propagate EXTENT_FLAG_LOGGING to split extent maps btrfs: fix u32 to s64 type conversion in dirty_metadata_bytes accounting btrfs: fix NULL pointer deref during assertion in btrfs_backref_free_node() btrfs: only account delalloc bytes for regular file inodes in btrfs_getattr() btrfs: reject inline file extents item in get_new_location() btrfs: do not try compression for data reloc inodes btrfs: declare btrfs_ioctl_search_args_v2::buf as __u8 btrfs: fix reloc root cleanup in merge_reloc_roots() btrfs: fix use-after-free on reloc root after error in insert_dirty_subvol()
4 daysMerge tag 'mm-hotfixes-stable-2026-07-20-11-37' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm Pull misc fixes from Andrew Morton: "12 hotfixes. 8 are cc:stable and the remainder address post-7.1 issues or aren't considered appropriate for backporting. 10 are for MM. All are singletons - please see the relevant changelogs for details" * tag 'mm-hotfixes-stable-2026-07-20-11-37' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: mm/memory-failure: trace: change memory_failure_event to ras subsystem mm: page_reporting: allow driver to set batch capacity mm/kmemleak: fix checksum computation for per-cpu objects mm/damon/core: disallow overlapping input ranges for damon_set_regions() MAINTAINERS: add Usama as a THP reviewer fat: avoid stack overflow warning mm/damon/core: validate ranges in damon_set_regions() m68k: avoid -Wunused-but-set-parameter in clear_user_page() mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established mm/page_vma_mapped: fix device-private PMD handling MAINTAINERS: s/SeongJae/SJ/ userfaultfd: prevent registration of special VMAs
5 dayssmb: client: bound dirent name against end of SMB response in cifs_filldirJay Vadayath
cifs_filldir() copies the entry name out of an SMB1 TRANS2_FIND_FIRST / FIND_NEXT response using a length (de.namelen) supplied by the server. The kmalloc'd SMB response buffer is bounded, but nothing checks that de.name + de.namelen still lies inside that buffer before the eventual filldir64() -> verify_dirent_name() -> memchr() reads namelen bytes. A hostile SMB1 server that returns an oversized FileNameLength in a directory entry therefore causes memchr() to read past the end of the response slab buffer. Reachable from any user who can list a directory on a CIFS mount served by an attacker-controlled server (getdents64() on the mounted directory): BUG: KASAN: slab-out-of-bounds in memchr+0x71/0x80 Read of size 1 at addr ffff88800e0640cc by task poc/115 Call Trace: dump_stack_lvl+0x64/0x80 print_report+0xce/0x620 kasan_report+0xec/0x120 memchr+0x71/0x80 filldir64+0x4c/0x6a0 cifs_filldir.constprop.0+0x9bb/0x1e00 cifs_readdir+0x2101/0x3380 iterate_dir+0x19c/0x520 __x64_sys_getdents64+0x126/0x210 do_syscall_64+0x107/0x5a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Pass the end-of-response pointer down to cifs_filldir() and reject entries whose name would extend past that boundary. This bug was discovered by Artiphishell's vTriage pipeline, which generated a userspace reproducer (an emulated hostile SMB1 server plus a getdents64() client) that reliably triggers the KASAN report on an unpatched kernel. The fix below was drafted with the Claude coding assistant; a userspace reproducer is available on request. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Jay Vadayath <jay@artiphishell.com> Signed-off-by: Steve French <stfrench@microsoft.com>
5 dayssmb: client: validate DFS referral PathConsumedYichong Chen
parse_dfs_referrals() validates that the response contains the fixed referral entry array and, on for-next, the per-referral string offsets. However, the response also contains a PathConsumed value that is later used for DFS path parsing. If a malformed response provides a PathConsumed value larger than the search name, later DFS parsing can advance beyond the end of the path. Validate PathConsumed against the search name length before storing it in the parsed referral. Fixes: 4ecce920e13a ("CIFS: move DFS response parsing out of SMB1 code") Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> Signed-off-by: Yichong Chen <chenyichong@uniontech.com> Signed-off-by: Steve French <stfrench@microsoft.com>
7 daysMerge tag 'v7.2-rc3-smb3-server-fixes' of git://git.samba.org/ksmbdLinus Torvalds
Pull smb server fixes from Steve French: "ksmbd server fixes, mostly addressing malformed SMB request handling and connection/session lifetime issues, including two information-disclosure or memory-safety bugs in the SMB2 request/response paths. - validate FILE_ALLOCATION_INFORMATION before block rounding to prevent a client-controlled overflow from truncating a file. - pin connections while asynchronous oplock and lease-break notifications are pending. - initialize compound SMB2 READ alignment padding, preventing disclosure of uninitialized heap bytes. - release the allocated alternate-stream xattr name after rename. - size multichannel binding session-key buffers for the largest permitted key, avoiding a stack buffer overflow. - remove a disconnecting connection's channels from every session, including channels whose binding state has since changed. - serialize binding preauthentication-session lookup and update against its teardown. - check that every compound request element contains StructureSize2 before reading it" * tag 'v7.2-rc3-smb3-server-fixes' of git://git.samba.org/ksmbd: ksmbd: validate compound request size before reading StructureSize2 ksmbd: lock the binding preauth session in smb3_preauth_hash_rsp ksmbd: remove stale channels from all sessions on teardown ksmbd: fix stack buffer overflow in multichannel session-key copy ksmbd: fix memory leak of xattr_stream_name in smb2_rename() ksmbd: zero the smb2_read alignment tail to avoid an infoleak ksmbd: pin conn during async oplock break notification ksmbd: fix integer overflow in set_file_allocation_info()
7 dayslockd: fix NLMv3 GRANTED_MSG handlingOlga Kornievskaia
GRANTED_MSG is a server-to-client callback, so it runs on the client, where nfsd never registers nlmsvc_ops. The nlm3svc_lookup_host() helper is for the server-side request handlers (TEST/LOCK/CANCEL/UNLOCK), which reach nlmsvc_ops->fopen and must reject requests when nfsd isn't running. GRANTED_MSG only calls nlmclnt_grant(). Instead, of calling nlm3svc_lookup_host(), which results in a client failing a GRANTED_MSG call, call nlmsvc_lookup_host(). Fixes: 6c534ad999b6 ("lockd: Use xdrgen XDR functions for the NLMv3 GRANTED_MSG procedure") Cc: stable@vger.kernel.org Signed-off-by: Olga Kornievskaia <okorniev@redhat.com> Reviewed-by: NeilBrown <neil@brown.name> Link: https://patch.msgid.link/20260625211852.31972-1-okorniev@redhat.com Signed-off-by: Chuck Lever <cel@kernel.org>
8 daysMerge tag 'v7.2-rc3-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6Linus Torvalds
Pull smb client fixes from Steve French: - fallocate fixes - unit test fixes - fix allocation size after duplicate extents - fix check for overlapping data areas * tag 'v7.2-rc3-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6: smb/client: flush dirty data before punching a hole smb/client: Use EXPORT_SYMBOL_IF_KUNIT() to export symbols in SMB2 smb/client: Use EXPORT_SYMBOL_IF_KUNIT() to export symbols smb: client: reject overlapping data areas in SMB2 responses smb/client: refresh allocation after EOF-extending fallocate smb/client: emulate small EOF-extending mode 0 fallocate ranges smb/client: reduce fallocate zero buffer allocation smb/client: handle overlapping allocated ranges in fallocate smb/client: refresh allocation size after duplicate extents smb: client: use kvzalloc() for megabyte buffer in simple fallocate
8 daysMerge tag 'xfs-fixes-7.2-rc4' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linuxLinus Torvalds
Pull xfs fixes from Carlos Maiolino: "This contains mostly a series of bug fixes found by different LLM models" * tag 'xfs-fixes-7.2-rc4' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux: (21 commits) xfs: don't zap bmbt forks if they are MAXLEVELS tall xfs: clamp timestamp nanoseconds correctly xfs: fully check the parent handle when it points to the rootdir xfs: handle non-inode owners for rtrmap record checking xfs: fix off-by-one error when calling xchk_xref_has_rt_owner xfs: set xfarray killable sort correctly xfs: grab rtrmap btree when checking rgsuper xfs: write the rg superblock when fixing it xfs: use the rt version of the cow staging checker xfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_staging xfs: don't wrap around quota ids in dqiterate xfs: move cow_replace_mapping to xfs_bmap_util.c xfs: make cow repair somewhat flaky when debugging knob enabled xfs: don't replace the wrong part of the cow fork xfs: resample the data fork mapping after cycling ILOCK xfs: fix null pointer dereference in tracepoint xfs: use xfs_csn_t for xlog_cil_push_now() push_seq parameter xfs: tie zoned sysfs lifetime to zone info xfs: fail recovery on a committed log item with no regions xfs: splice unsorted log items back to the transaction after the loop ...
8 daysMerge tag 'erofs-for-7.2-rc4-fixes' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs Pull erofs fixes from Gao Xiang: - Fix sanity checks for ztailpacking tail pclusters to avoid false corruption reports - Use more informative s_id for file-backed mounts - Hide the meaningless "cache_strategy=" mount option on plain (uncompressed) filesystems - Remove the unneeded erofs_is_ishare_inode() helper * tag 'erofs-for-7.2-rc4-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs: erofs: hide "cache_strategy=" for plain filesystems erofs: get rid of erofs_is_ishare_inode() helper erofs: relax sanity check for tail pclusters due to ztailpacking erofs: use more informative s_id for file-backed mounts
8 daysksmbd: validate compound request size before reading StructureSize2Xiang Mei (Microsoft)
When ksmbd validates a compound (chained) SMB2 request, ksmbd_smb2_check_message() reads pdu->StructureSize2 without first checking that the compound element is large enough to contain it. StructureSize2 is a 2-byte field at offset 64 (__SMB2_HEADER_STRUCTURE_SIZE) from the start of each element. The compound-walking logic only guarantees that a full 64-byte SMB2 header is present for the trailing element: when NextCommand is 0, len is reduced to the number of bytes remaining after next_smb2_rcv_hdr_off. A remote client can craft a compound request whose last element has exactly 64 bytes, so the 2-byte StructureSize2 read at offset 64 extends one byte past the receive buffer, producing a slab-out-of-bounds read. BUG: KASAN: slab-out-of-bounds in ksmbd_smb2_check_message (fs/smb/server/smb2misc.c:402) Read of size 2 at addr ffff888012ae31ac by task kworker/0:1/14 The buggy address is located 172 bytes inside of allocated 173-byte region Workqueue: ksmbd-io handle_ksmbd_work Call Trace: ... kasan_report (mm/kasan/report.c:595) ksmbd_smb2_check_message (fs/smb/server/smb2misc.c:402) handle_ksmbd_work (fs/smb/server/server.c:119) process_one_work (kernel/workqueue.c:3314) worker_thread (kernel/workqueue.c:3397) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Reject any compound element that is too small to hold StructureSize2 before dereferencing it. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
8 daysksmbd: lock the binding preauth session in smb3_preauth_hash_rspGil Portnoy
smb3_preauth_hash_rsp() computes the SMB3.1.1 preauth integrity hash on the response path. For a binding SESSION_SETUP it looks up the per-connection preauth_session and reads its Preauth_HashValue. smb2_sess_setup() frees that preauth_session under ksmbd_conn_lock(). Two SMB2 requests on one connection can run concurrently, so an unlocked lookup and hash can use a preauth_session after another worker frees it. Take ksmbd_conn_lock() before selecting conn->binding and hold it across the selected preauth hash lookup and update. This preserves the existing hash selection while preventing the lookup-to-use lifetime race. Fixes: 1c5daa2ea924 ("ksmbd: handle channel binding with a different user") Signed-off-by: Gil Portnoy <dddhkts1@gmail.com> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
8 daysksmbd: remove stale channels from all sessions on teardownGil Portnoy
ksmbd_sessions_deregister() removes a connection's channels from other sessions' channel lists only while conn->binding is still set: if (conn->binding) { hash_for_each_safe(sessions_table, ...) ksmbd_chann_del(conn, sess); } conn->binding is a transient flag: it is cleared once a binding SESSION_SETUP completes, and also by a subsequent non-binding SESSION_SETUP on the same connection (a reauthentication on a bound channel, or a new SessionId==0 setup). A connection that has bound a channel into another session's ksmbd_chann_list and then clears conn->binding leaves that channel behind when it disconnects: the channel, whose chann->conn points at the now freed struct ksmbd_conn, stays on the owner session's list. When the owning connection later tears down, the second loop dereferences the stale channel: xa_for_each(&sess->ksmbd_chann_list, chann_id, chann) if (chann->conn != conn) ksmbd_conn_set_exiting(chann->conn); /* freed */ which is a use-after-free write into the freed ksmbd_conn (the same stale channel is also walked by show_proc_session() through /proc). The session is leaked as well, because its channel list never empties. Remove the conn->binding gate so a connection always removes its channels from every session on teardown. Fixes: faf8578c77f3 ("ksmbd: find bound sessions during reauthentication") Signed-off-by: Gil Portnoy <dddhkts1@gmail.com> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
8 daysksmbd: fix stack buffer overflow in multichannel session-key copyGil Portnoy
Commit 4b706360ffb7 ("ksmbd: fix multichannel binding and enforce channel limit") moved the binding-path session key out of the session-wide sess->sess_key (CIFS_KEY_SIZE = 40) into a new per-channel buffer, and sized both that buffer and the on-stack copy used during binding with SMB2_NTLMV2_SESSKEY_SIZE (16): struct channel { char sess_key[SMB2_NTLMV2_SESSKEY_SIZE]; /* 16 */ ... }; ntlm_authenticate() / krb5_authenticate(): char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {}; /* 16 */ char *auth_key = conn->binding ? channel_key : sess->sess_key; The two writers that fill this destination still bound the copy length against CIFS_KEY_SIZE (40), not against the 16-byte buffer: ksmbd_decode_ntlmssp_auth_blob() (NTLM key exchange): if (sess_key_len > CIFS_KEY_SIZE) /* 40 */ return -EINVAL; arc4_crypt(ctx_arc4, sess_key, (char *)authblob + sess_key_off, sess_key_len); ksmbd_krb5_authenticate(): if (resp->session_key_len > sizeof(sess->sess_key)) /* 40 */ ... memcpy(sess_key, resp->payload, resp->session_key_len); On a binding SESSION_SETUP, auth_key points at the 16-byte channel_key, so a client that supplies an NTLM EncryptedRandomSessionKey of up to 40 bytes (with NTLMSSP_NEGOTIATE_KEY_EXCH), or a Kerberos ticket whose session key is longer than 16 bytes (a normal AES256 key is 32), writes past the 16-byte stack buffer -- up to a 24-byte kernel stack overflow. KASAN reports it as a stack-out-of-bounds write in arc4_crypt() called from ksmbd_decode_ntlmssp_auth_blob(). The destinations must be able to hold the full session key the length checks already permit. Size the per-channel key buffer and the two on-stack channel_key buffers with CIFS_KEY_SIZE, matching sess->sess_key. Fixes: 4b706360ffb7 ("ksmbd: fix multichannel binding and enforce channel limit") Signed-off-by: Gil Portnoy <dddhkts1@gmail.com> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
8 daysksmbd: fix memory leak of xattr_stream_name in smb2_rename()Gil Portnoy
On an SMB2 SET_INFO(FileRenameInformation) whose target names an alternate data stream, smb2_rename() obtains a formatted stream-name string from ksmbd_vfs_xattr_stream_name(), which allocates it with kasprintf() and returns it through an out-param: rc = ksmbd_vfs_xattr_stream_name(stream_name, &xattr_stream_name, ...); if (rc) goto out; rc = ksmbd_vfs_setxattr(..., xattr_stream_name, ...); if (rc < 0) { ... goto out; } goto out; xattr_stream_name is declared inside the alternate-data-stream block, but the out: label is outside that block and frees only new_name, so it cannot release xattr_stream_name. ksmbd_vfs_setxattr() takes a const char * and only reads the name, so it does not take ownership either. Both the setxattr-failure and the success path therefore leak the kasprintf()'d string. An authenticated client with a writable share can leak kernel memory on every stream rename, exhausting kernel memory over time. Free xattr_stream_name after its use, before the block's goto out. The two earlier goto out paths never assign the variable, so there is no double-free. Signed-off-by: Gil Portnoy <dddhkts1@gmail.com> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
8 daysksmbd: zero the smb2_read alignment tail to avoid an infoleakGil Portnoy
Commit 6b9a2e09d4cc ("ksmbd: avoid zeroing the read buffer in smb2_read()") switched the SMB2 READ payload buffer from kvzalloc() to kvmalloc(), on the premise that only the nbytes actually read are ever transmitted, so the ALIGN(length, 8) tail need not be initialized. That premise does not hold for a compound response. ksmbd_vfs_read() fills only nbytes, leaving [nbytes, ALIGN(length, 8)) uninitialized. The aux payload is pinned as the last response iov with iov_len == nbytes, but when the READ is a member of a compound, init_chained_smb2_rsp() 8-byte-aligns the previous member by extending that same iov: new_len = ALIGN(len, 8); work->iov[work->iov_idx].iov_len += (new_len - len); inc_rfc1001_len(work->response_buf, new_len - len); so up to 7 uninitialized bytes of the kvmalloc()'d slab tail are sent to the client. When the read length is small the buffer is served from a general kmalloc slab, so those bytes can be stale kernel-heap contents, including pointer values -- an information leak usable to defeat KASLR. An authenticated client triggers it with a compound request containing a READ whose returned nbytes is not 8-aligned (for example [READ, CLOSE] with a 1-byte read). Zero only the alignment tail after the read, preserving the bulk no-zeroing optimization of 6b9a2e09d4cc. Fixes: 6b9a2e09d4cc ("ksmbd: avoid zeroing the read buffer in smb2_read()") Cc: stable@vger.kernel.org Signed-off-by: Gil Portnoy <dddhkts1@gmail.com> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
8 daysksmbd: pin conn during async oplock break notificationQihang
smb2_oplock_break_noti() and smb2_lease_break_noti() store a ksmbd_conn pointer in an async ksmbd_work and then queue that work on ksmbd-io. The work only increments conn->r_count, which prevents teardown from passing the pending-request wait after the increment, but it does not pin the struct ksmbd_conn object. If connection teardown races with an oplock break notification, the last conn reference can be dropped before the queued worker finishes. The worker then uses the freed conn in ksmbd_conn_write() and ksmbd_conn_r_count_dec(). Take a real conn reference when publishing the conn pointer to the async work item, and drop it after the notification work has decremented r_count. Apply the same lifetime rule to lease break notification, which uses the same work->conn pattern. Fixes: 3aa660c05924 ("ksmbd: prevent connection release during oplock break notification") Signed-off-by: Qihang <q.h.hack.winter@gmail.com> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
8 daysksmbd: fix integer overflow in set_file_allocation_info()Ibrahim Hashimov
set_file_allocation_info() converts the client-supplied FILE_ALLOCATION_INFORMATION::AllocationSize into a 512-byte block count with: alloc_blks = (le64_to_cpu(file_alloc_info->AllocationSize) + 511) >> 9; AllocationSize is a fully client-controlled __le64 field; the only validation performed by the caller (smb2_set_info_file(), case FILE_ALLOCATION_INFORMATION) is that the fixed buffer is at least sizeof(struct smb2_file_alloc_info) == 8 bytes. The value itself is never range-checked before this arithmetic. When AllocationSize is close to U64_MAX (e.g. 0xffffffffffffffff), "AllocationSize + 511" wraps around mod 2^64 to a small number (0xffffffffffffffff + 511 = 510), so alloc_blks becomes 0. Since any existing regular file has stat.blocks > 0, the function then takes the "shrink" branch and calls: ksmbd_vfs_truncate(work, fp, alloc_blks * 512); /* == 0 */ silently truncating the file to size 0, even though the client asked to grow the allocation to (what looks like) the maximum possible size. The trailing "if (size < alloc_blks * 512) i_size_write(inode, size);" restore is guarded by a comparison that is never true once alloc_blks == 0, so the truncation is not undone. This lets an authenticated SMB client that already holds an open handle with FILE_WRITE_DATA on a file silently truncate that same file to size 0 via a single crafted SET_INFO(FILE_ALLOCATION_INFORMATION) request advertising a near-U64_MAX AllocationSize, even though the request asks to grow the file's allocation rather than shrink it. This is a functional/data-loss bug, not a privilege-boundary violation: the same client could already truncate the file via FILE_END_OF_FILE_INFORMATION or a plain write. Fix it by validating AllocationSize against MAX_LFS_FILESIZE, the same upper bound the VFS itself uses to reject unrepresentable file sizes, before doing the "+511" rounding, and rejecting oversized values with -EINVAL. Bounding AllocationSize to MAX_LFS_FILESIZE - 511 guarantees the "+511" addition cannot wrap, and that the subsequent "alloc_blks * 512" values passed to vfs_fallocate() and ksmbd_vfs_truncate() stay within a representable loff_t as well. No legitimate SMB client asks for an allocation size anywhere near 2^64 bytes, so this only rejects a value that was previously silently misinterpreted as zero. Runtime-verified on a v6.19 KASAN test stand: sending SET_INFO (FILE_ALLOCATION_INFORMATION) with AllocationSize = 0xffffffffffffffff against ksmbd now returns -EINVAL and leaves the target file's size unchanged, where the unpatched kernel truncated it from 4096 to 0 bytes. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov <security@auditcode.ai> Assisted-by: AuditCode-AI:2026.07 Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
10 dayssmb/client: flush dirty data before punching a holeHuiwen He
Punching a hole after a large buffered write may leave the range reported as data. Reproduce it with: xfs_io -f \ -c "pwrite -b 3m -S 0x61 0 3m" \ -c "fpunch 1m 1m" \ -c "seek -h 0" \ -c "seek -d 1m" \ /mnt/test/repro Punching 1 MiB at offset 1 MiB should produce: 0 1 MiB 2 MiB 3 MiB | DATA | HOLE | DATA | EOF Instead, the entire file is reported as data. SEEK_HOLE(0) returns EOF, and SEEK_DATA(1M) returns 1M. This happens because a dirty folio spanning the punched range can be written back after the punch and refill the hole. Fix this by flushing and waiting for dirty data in the punched range before invalidating the page cache and issuing FSCTL_SET_ZERO_DATA. The xfstests generic/539 pass against Samba/ksmbd with this change. Signed-off-by: Huiwen He <hehuiwen@kylinos.cn> Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn> Signed-off-by: Steve French <stfrench@microsoft.com>
10 dayssmb/client: Use EXPORT_SYMBOL_IF_KUNIT() to export symbols in SMB2Andy Shevchenko
Replace EXPORT_SYMBOL_FOR_MODULES() with EXPORT_SYMBOL_IF_KUNIT() to mark the symbols as visible only if CONFIG_KUNIT is enabled. Kunit test should import the namespace EXPORTED_FOR_KUNIT_TESTING to use these marked symbols. This is the standard way for all KUnit tests. Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn> Signed-off-by: Steve French <stfrench@microsoft.com>
10 dayssmb/client: Use EXPORT_SYMBOL_IF_KUNIT() to export symbolsAndy Shevchenko
Replace EXPORT_SYMBOL_FOR_MODULES() with EXPORT_SYMBOL_IF_KUNIT() to mark the symbols as visible only if CONFIG_KUNIT is enabled. Kunit test should import the namespace EXPORTED_FOR_KUNIT_TESTING to use these marked symbols. This is the standard way for all KUnit tests. Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn> Signed-off-by: Steve French <stfrench@microsoft.com>
10 daysMerge tag 'for-7.2-rc3-tag' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux Pull btrfs fixes from David Sterba: - fix root structure leak after relocation error - fix optimization when checksums are read from commit root, fall back to checksum root during relocation - in tree-checker, validate length of inode reference in items - validate properties before setting them - validate free space cache entries on load - transaction abort fixes - fix printing of internal trees as signed numbers - add error messages after critical lzo compression errors * tag 'for-7.2-rc3-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux: btrfs: print-tree: print header owner as signed btrfs: decentralize transaction aborts in create_reloc_root() btrfs: tree-checker: validate INODE_REF's namelen btrfs: lzo: add error message for invalid headers btrfs: fallback to transaction csum tree on a commit root csum miss btrfs: fix root leak if its reloc root is unexpected in merge_reloc_roots() btrfs: reject free space cache with more entries than pages btrfs: fix transaction abort logic in btrfs_fileattr_set() btrfs: validate properties before setting them
11 daysxfs: don't zap bmbt forks if they are MAXLEVELS tallDarrick J. Wong
LOLLM noticed a discrepancy between the bmbt level checks in the libxfs bmbt code vs. the inode repair code. We do actually allow a bmbt root that proclaims to have a height of XFS_BM_MAXLEVELS. Cc: stable@vger.kernel.org # v6.8 Fixes: e744cef2060559 ("xfs: zap broken inode forks") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: clamp timestamp nanoseconds correctlyDarrick J. Wong
LOLLM noticed an off-by-one error in the nsec clamping; fix that so that we never have tv_nsec == 1e9. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: fully check the parent handle when it points to the rootdirDarrick J. Wong
LOLLM noticed that the directory tree path checking declares the path to be ok if the inumber in the parent pointer reaches the root directory. Unfortunately, it neglects to check that the generation is correct. Fix that by moving the generation check up. Cc: stable@vger.kernel.org # v6.10 Fixes: 928b721a11789a ("xfs: teach online scrub to find directory tree structure problems") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: handle non-inode owners for rtrmap record checkingDarrick J. Wong
LOLLM noticed that two helper functions in the rtrmapbt scrub code don't actually handle non-inode owners correctly -- CoW staging extents and rgsuperblock extents are not shareable, but they are mergeable. Fix these two helpers. Cc: stable@vger.kernel.org # v6.14 Fixes: 2d9a3e98053e8c ("xfs: allow overlapping rtrmapbt records for shared data extents") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: fix off-by-one error when calling xchk_xref_has_rt_ownerDarrick J. Wong
LOLLM noticed an off-by-one error when computing the length of the rtrmap to cross-check. Cc: stable@vger.kernel.org # v6.14 Fixes: 037a44d8277adf ("xfs: cross-reference the realtime rmapbt") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: set xfarray killable sort correctlyDarrick J. Wong
LOLLM noticed that we *disable* interruptible sorts when the KILLABLE flag is set. This is backwards. Fix the incorrect logic, and rename the variable to make the connection more obvious. Cc: stable@vger.kernel.org # v6.10 Fixes: 271557de7cbfde ("xfs: reduce the rate of cond_resched calls inside scrub") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: grab rtrmap btree when checking rgsuperDarrick J. Wong
LOLLM noticed that we aren't grabbing the rtrmap btree when we check the realtime group superblock. As a result, none of the cross-referencing checks have ever run. Fix this. Cc: stable@vger.kernel.org # v6.14 Fixes: 428e4884656db9 ("xfs: allow queued realtime intents to drain before scrubbing") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: write the rg superblock when fixing itDarrick J. Wong
The rtgroup superblock fixer should write the rtgroup superblock. LOLLM noticed this, oops. :/ Cc: stable@vger.kernel.org # v6.13 Fixes: 1433f8f9cead37 ("xfs: repair realtime group superblock") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: use the rt version of the cow staging checkerDarrick J. Wong
LOLLM also noticed that xchk_rtrmapbt_xref ought to be using the rtdev version of the "is this a cow extent?" helper function, not the datadev one. Cc: stable@vger.kernel.org # v6.14 Fixes: 91683bb3f264c0 ("xfs: cross-reference checks with the rt refcount btree") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_stagingDarrick J. Wong
LOLLM points out that we pass the wrong btree cursor here. We want the rtrefcount btree cursor, not the non-rt one. This is fairly benign since it only affects tracing data. Cc: stable@vger.kernel.org # v6.14 Fixes: 91683bb3f264c0 ("xfs: cross-reference checks with the rt refcount btree") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: don't wrap around quota ids in dqiterateDarrick J. Wong
LOLLM noticed that q_id is an unsigned 32-bit variable. If it happens to be set to XFS_DQ_ID_MAX due to a filesystem that actually has a dquot for ID_MAX, then this addition will truncate to zero and the iteration starts over. Fix this by casting to u64. Cc: stable@vger.kernel.org # v6.8 Fixes: 21d7500929c8a0 ("xfs: improve dquot iteration for scrub") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: move cow_replace_mapping to xfs_bmap_util.cDarrick J. Wong
Move the actual details of (partially) replacing a COW fork mapping to xfs_bmap_util.c so that all the code doing hairy operations on subsets of bmbt_irecs are kept together. Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: make cow repair somewhat flaky when debugging knob enabledDarrick J. Wong
Introduce a new behavior for the cow fork repair code: if the debugging knob is enabled, we'll pick a random subrange of each cow fork mapping to mark as bad. This will exercise the xrep_cow_replace_mapping more thoroughly. Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: don't replace the wrong part of the cow forkDarrick J. Wong
LOLLM points out that xfs_iext_lookup_extent can return a @got where got->br_startoff < startoff. In this case, xrep_cow_replace_range replaces the entire mapping instead of just the part that had been marked bad in the bitmap, but advances the bitmap cursor in xrep_cow_replace by the amount replaced. As a result, we fail to replace the end of the bad range, and replace part of the good range. Fix this by rewriting the replace method to handle replacing the middle of a cow fork mapping. This we do by returning both the current mapping as @got, and the subset of the mapping that we want to replace as @rep, using @rep to store the results of the new allocation, and comparing @rep to @got to figure out the exact transformations needed. Cc: stable@vger.kernel.org # v6.8 Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysxfs: resample the data fork mapping after cycling ILOCKDarrick J. Wong
xfs_reflink_fill_{cow_hole,delalloc} are both presented with an inode, a data fork mapping, and a cow fork mapping. Unfortunately, these two helpers cycle the ILOCK to grab a transaction, which means that the mappings are stale as soon as we reacquire the ILOCK. Currently we refresh the cow fork mapping by re-calling xfs_find_trim_cow_extent, but we don't refresh the data fork mapping beforehand, which means that the xfs_bmap_trim_cow in that function queries the refcount btree about the wrong physical blocks and returns an inaccurate value in *shared. If *shared is now false, the directio write proceeds with a stale data fork mapping. Fix this by querying the data fork mapping if the sequence counter changes across the ILOCK cycle. Cc: hch@lst.de Cc: stable@vger.kernel.org # v4.11 Fixes: 3c68d44a2b49a0 ("xfs: allocate direct I/O COW blocks in iomap_begin") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> Signed-off-by: Carlos Maiolino <cem@kernel.org>
11 daysbtrfs: fix GET_SUBVOL_INFO after compat refactorDaan De Meyer
btrfs_search_slot() returns a positive value when the search key does not exactly match an item. This is expected here, since offset 0 is used to find the first ROOT_BACKREF for the subvolume and the actual key has the parent root ID as its offset. Before the compat ioctl refactoring, the native handler still copied the filled structure to userspace when the search returned 1. After the lookup was moved to a shared helper, both native and compat callers treat the positive return value as a failure and skip copy_to_user(), leaving BTRFS_IOC_GET_SUBVOL_INFO unusable for non-top-level subvolumes. Reset ret after successfully validating and reading the ROOT_BACKREF so the helper reports success and both callers copy the result to userspace. Fixes: 538e5bdbc899 ("btrfs: add 32-bit compat ioctl for BTRFS_IOC_GET_SUBVOL_INFO") Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Daan De Meyer <daan@amutable.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: free mapping node on duplicate reloc root insertGuanghui Yang
__add_reloc_root() allocates a mapping_node before inserting it into rc->reloc_root_tree. If rb_simple_insert() finds an existing entry, it returns the existing rb_node and leaves the newly allocated node unlinked. The error path then returns -EEXIST without freeing the new node. Since the node was never inserted into reloc_root_tree, the later cleanup in put_reloc_control() cannot find it either. Free the newly allocated node before returning -EEXIST. The callers currently assert that -EEXIST should not happen, so this is a defensive cleanup for an unexpected duplicate insert path. If the path is ever reached, the local allocation should still be released. Fixes: 57a304cfd43b ("btrfs: do not panic in __add_reloc_root") Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Guanghui Yang <3497809730@qq.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: fix a regression where PAGECACHE_TAG_DIRTY is never clearedQu Wenruo
[BUG] The following script (already submitted as generic/798) will report incorrect dirty page numbers, with 64K page size systems and 4K fs block size: # mkfs.btrfs -s 4k -f $dev # mount $dev $mnt # xfs_io -f -c "pwrite 0 64K" -c fsync -c "cachestat 0 64K" $mnt/foobar Cached: 1, Dirty: 1, Writeback: 0, Evicted: 0, Recently Evicted: 0 Note that the dirtied page number is still 1. [CAUSE] The cachestat() goes through the XArray of the page cache, but instead of checking each folio's flag, it uses the PAGECACHE_TAG_DIRTY tag to report dirty pages. Since commit 095be159f3eb ("btrfs: unify folio dirty flag clearing"), btrfs replaced a folio_clear_dirty_for_io() call inside extent_write_cache_pages() with folio_test_dirty(). This will cause the following call sequence for the folio at file offset 0: extent_write_cache_pages() |- folio_test_dirty() | The folio is still dirty, continue to writeback. | |- extent_writepage() |- extent_writepage_io() |- submit_one_sector() for range [0, 4K) | |- btrfs_folio_clear_dirty() | |- btrfs_folio_set_writeback() | |- folio_start_writeback() | It's the first writeback block, we set the writeback | flag for the folio. | But the folio is still dirty, PAGECACHE_TAG_DIRTY is | kept | |- submit_one_sector() for range [4K, 8K) | |- btrfs_folio_clear_dirty() | |- btrfs_folio_set_writeback() | The folio already has writeback flag, no need to call | folio_start_writeback() | | ... |- submit_one_sector() for range [60K, 64K) |- btrfs_folio_clear_dirty() |- btrfs_folio_set_writeback() The folio already has writeback flag, no need to call folio_start_writeback() So the PAGECACHE_TAG_DIRTY is never cleared. Meanwhile for the old code, before that commit, the sequence looks like: extent_write_cache_pages() |- folio_clear_dirty_for_io() | The folio is still dirty, so continue to writeback. | But the folio dirty flag is cleared now. | |- extent_writepage() |- extent_writepage_io() |- submit_one_sector() for range [0, 4K) | |- btrfs_folio_clear_dirty() | |- btrfs_folio_set_writeback() | |- folio_start_writeback() | |- xas_clear(PAGECACHE_TAG) | | It's the first writeback block, we set the writeback | flag for the folio. | And the folio is not dirty, PAGECACHE_TAG_DIRTY is | cleared | |- submit_one_sector() for range [4K, 8K) | |- btrfs_folio_clear_dirty() | |- btrfs_folio_set_writeback() | The folio already has writeback flag, no need to call | folio_start_writeback() | | ... |- submit_one_sector() for range [60K, 64K) |- btrfs_folio_clear_dirty() |- btrfs_folio_set_writeback() The folio already has writeback flag, no need to call folio_start_writeback() Unlike the new code, old code will clear PAGECACHE_TAG_DIRTY for the first writeback block. There is a deeper problem, dirty and writeback folio flags are updated at very different timing. The dirty flag is only cleared when the last sub-folio block has dirty flag cleared. But the writeback flag is set when the first block starts writeback, and later blocks that go through writeback will not call folio_start_writeback() again. If we rely on folio_start_writeback() to update the PAGECACHE_TAG_DIRTY and PAGECACHE_TAG_TOWRITE, it will always be incorrect in one way or another. [FIX] Do not let folio_start_writeback() do any PAGECACHE_TAG_TOWRITE handling. Instead, manually clear both PAGECACHE_TAG_TOWRITE and PAGECACHE_TAG_DIRTY flags when the folio is no longer dirty during btrfs_subpage_set_writeback(). However this is only a hot-fix, for the long term solution we will follow iomap, by calling folio_start_writeback() immediately for the whole folio, and folio_end_writeback() after all writeback finished for the folio. Fixes: 095be159f3eb ("btrfs: unify folio dirty flag clearing") Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: don't propagate EXTENT_FLAG_LOGGING to split extent mapsLeo Martins
When btrfs_drop_extent_map_range() splits an extent map, the new split maps inherit the original map's flags through a local 'flags' variable. Commit f86f7a75e2fb ("btrfs: use the flags of an extent map to identify the compression type") changed the EXTENT_FLAG_LOGGING clearing to operate on em->flags instead of that local 'flags' copy, so a split of an extent map that is currently being logged wrongly inherits EXTENT_FLAG_LOGGING. The flag is then never cleared on the split, and when it is freed while still on the inode's modified_extents list (for example by the extent map shrinker) it trips the WARN_ON(!list_empty(&em->list)) in btrfs_free_extent_map() and leads to a use-after-free. Clear EXTENT_FLAG_LOGGING from the local 'flags' copy used for the splits and only clear EXTENT_FLAG_PINNED from em->flags, restoring the behaviour prior to f86f7a75e2fb. CC: Jeff Layton <jlayton@kernel.org> Link: https://lore.kernel.org/all/20260629-btrfs-skip-logging-v1-1-4e3a28c1acaf@kernel.org/ Fixes: f86f7a75e2fb ("btrfs: use the flags of an extent map to identify the compression type") Reviewed-by: Jeff Layton <jlayton@kernel.org> Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Leo Martins <loemra.dev@gmail.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: fix u32 to s64 type conversion in dirty_metadata_bytes accountingDave Chen
The percpu_counter dirty_metadata_bytes is updated by negating eb->len and passing it to percpu_counter_add_batch(), whose amount parameter is s64. Since commit 84cda1a6087d ("btrfs: cache folio size and shift in extent_buffer"), eb->len is u32. The u32 result of -eb->len, when widened to the s64 parameter, becomes a large positive value instead of the intended negative value. For eb->len == 16384 the counter adds +4294950912 instead of subtracting 16384. The counter therefore grows on every metadata writeback instead of shrinking by the extent buffer size, permanently exceeding BTRFS_DIRTY_METADATA_THRESH and causing __btrfs_btree_balance_dirty() to trigger balance_dirty_pages_ratelimited() unconditionally, adding unnecessary writeback pressure. Cast eb->len to s64 before negation at both call sites so the subtraction is performed in signed 64-bit arithmetic. Reviewed-by: Filipe Manana <fdmanana@suse.com> Fixes: 84cda1a6087d ("btrfs: cache folio size and shift in extent_buffer") Signed-off-by: Dave Chen <davechen@synology.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: fix NULL pointer deref during assertion in btrfs_backref_free_node()Filipe Manana
In btrfs_backref_free_node() we have the following assertion: ASSERT(node->eb == NULL, "node->eb->start=%llu", node->eb->start); and a user reported the following crash: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] CPU: 0 UID: 0 PID: 10422 Comm: syz.0.17 Not tainted 7.1.0-02765-g6b5a2b7d9bc1-dirty #44 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 RIP: 0010:btrfs_backref_free_node fs/btrfs/backref.c:3057 [inline] RIP: 0010:btrfs_backref_free_node+0xb9/0x200 fs/btrfs/backref.c:3051 Code: 00 fc ff (...) RSP: 0018:ffa0000006b0f3c0 EFLAGS: 00010246 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff840eb78b RDX: 0000000000000000 RSI: ffffffff840eafa5 RDI: ff110000742ab768 RBP: ff110000742ab700 R08: 0000000000000000 R09: 0000000000000000 R10: ff110000742ab700 R11: 00000000000a81f9 R12: ff11000107a92020 R13: ff1100005c182ea8 R14: 0000000000000000 R15: dffffc0000000000 FS: 0000555575536500(0000) GS:ff11000183985000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fa3d0e9d580 CR3: 000000002232a000 CR4: 0000000000753ef0 PKRU: 00000000 Call Trace: <TASK> btrfs_backref_cleanup_node+0x27/0x30 fs/btrfs/backref.c:3133 relocate_tree_block fs/btrfs/relocation.c:2604 [inline] relocate_tree_blocks+0x11b0/0x1a20 fs/btrfs/relocation.c:2707 relocate_block_group+0x499/0xf30 fs/btrfs/relocation.c:3635 do_nonremap_reloc fs/btrfs/relocation.c:5323 [inline] btrfs_relocate_block_group+0x1749/0x5fb0 fs/btrfs/relocation.c:5490 btrfs_relocate_chunk+0x12b/0x950 fs/btrfs/volumes.c:3647 __btrfs_balance fs/btrfs/volumes.c:4586 [inline] btrfs_balance+0x1c7f/0x55c0 fs/btrfs/volumes.c:4973 btrfs_ioctl_balance fs/btrfs/ioctl.c:3474 [inline] btrfs_ioctl+0x38a4/0x5d20 fs/btrfs/ioctl.c:5570 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18f/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x11f/0x860 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fb38e3b56dd Code: 02 b8 ff (...) RSP: 002b:00007fff04115788 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007fb38f6b0020 RCX: 00007fb38e3b56dd RDX: 00002000000003c0 RSI: 00000000c4009420 RDI: 0000000000000004 RBP: 00007fb38e451b48 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 0000000000000000 R14: 00007fb38f6b0020 R15: 00007fb38f6b002c </TASK> It seems that this happens on some systems for some reason, when the ASSERT() macro calls the inline function verify_assert_printk_format() to evaluate the format string and arguments, causing the NULL pointer dereference on node->eb. So change the assertion to check for a NULL node->eb before dereferencing it. Also, while at it, make the assertion more useful by printing the owner of the extent buffer as well as its level. Reported-by: Yue Sun <samsun1006219@gmail.com> Link: https://lore.kernel.org/linux-btrfs/20260626065542.38413-1-samsun1006219@gmail.com/ Fixes: c4e7778580d6 ("btrfs: use verbose assertions in backref.c") Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: only account delalloc bytes for regular file inodes in btrfs_getattr()Dave Chen
btrfs_getattr() unconditionally reads BTRFS_I(inode)->new_delalloc_bytes and adds it (sector-aligned) to stat->blocks for every inode type. However, new_delalloc_bytes lives in a union with last_dir_index_offset: union { u64 new_delalloc_bytes; /* files only */ u64 last_dir_index_offset; /* directories only */ }; For a directory inode this memory holds last_dir_index_offset, which is set during directory logging (e.g. flush_dir_items_batch()) to the offset of the last logged BTRFS_DIR_INDEX_KEY. That offset grows with the number of entries ever created in the directory (dir indexes are monotonic and never reused), so it can be arbitrarily large. As a result, after a directory has been logged (e.g. via an fsync that triggers directory logging), btrfs_getattr() reports inflated st_blocks for that directory. The inflation is purely in-core and disappears after the inode is evicted and reloaded (btrfs_alloc_inode() zeroes the union), e.g. after a remount. Reproducer (on a btrfs filesystem): D=/mnt/btrfs/d mkdir -p $D for i in $(seq 1 20000); do touch $D/f$i; done sync # commit, push dir index high touch $D/trigger # dirty the dir in a new transaction xfs_io -c fsync $D # log the directory -> sets last_dir_index_offset stat -c '%b' $D # st_blocks is now inflated (e.g. 40) # umount + mount -> st_blocks drops back to the correct value The evict path already knows this union is type-dependent and guards the corresponding WARN_ON with !S_ISDIR() in btrfs_destroy_inode(); only btrfs_getattr() was missing the equivalent check. Only read new_delalloc_bytes for regular files, which are the only inodes that ever set it. Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Dave Chen <davechen@synology.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: reject inline file extents item in get_new_location()Qu Wenruo
Commit a6908f88c9da ("btrfs: validate data reloc tree file extent item members") introduced extra checks on file extent items for data reloc inodes, but it checked the file extent offset without checking if the file extent is inlined. This can lead to either false alerts (as the offset member is inside the inlined data) or even reading beyond the item range. This has already triggered a warning in a syzbot report. Although the root fix is to avoid compression for data reloc inodes, for the sake of consistency, reject inlined file extents first. Fixes: a6908f88c9da ("btrfs: validate data reloc tree file extent item members") CC: stable@vger.kernel.org Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
11 daysbtrfs: do not try compression for data reloc inodesQu Wenruo
[BUG] There is a syzbot report that the check inside get_new_location() triggered: BTRFS info (device loop0): found 31 extents, stage: move data extents BTRFS info (device loop0): leaf 8908800 gen 16 total ptrs 28 free space 1676 owner 18446744073709551607 item 0 key (256 INODE_ITEM 0) itemoff 3835 itemsize 160 inode generation 5 transid 0 size 0 nbytes 0 block group 0 mode 40755 links 1 uid 0 gid 0 rdev 0 sequence 0 flags 0x0 atime 1669132761.0 ctime 1669132761.0 mtime 1669132761.0 otime 0.0 item 1 key (256 INODE_REF 256) itemoff 3823 itemsize 12 index 0 name_len 2 item 2 key (258 INODE_ITEM 0) itemoff 3663 itemsize 160 inode generation 1 transid 16 size 733184 nbytes 106496 block group 0 mode 100600 links 0 uid 0 gid 0 rdev 0 sequence 24 flags 0x18 item 3 key (258 EXTENT_DATA 0) itemoff 3595 itemsize 68 generation 16 type 0 inline extent data size 47 ram_bytes 4096 compression 1 [...] item 27 key (18446744073709551611 ORPHAN_ITEM 258) itemoff 2376 itemsize 0 BTRFS error (device loop0): unexpected non-zero offset in file extent item for data reloc inode 258 key offset 0 offset 9277520992061368337 ------------[ cut here ]------------ btrfs_abort_should_print_stack(__error) [CAUSE] The above dump tree shows the first file extent item is inlined, which should make no sense for data reloc inodes, as such inodes just represent where the data extents are in the relocation destination chunk. However the relocation path preallocates space for each block, then dirties them, cluster by cluster. It's possible to have a single block at the beginning of the block group, and no other block in the same cluster. So relocation will preallocate a file extent for that block and dirty the first block. Then memory pressure forces the data reloc inode to be written back, before any other blocks are dirtied/allocated. Finally commit 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper") changed the sequence of delalloc. Before that commit we always tried NOCOW first, so that dirtied block would be written back into the preallocated space, and appear as a regular extent. But with that commit, we always try inline first, and since compression is forced, we try compressing the first block, and then inline the compressed data, resulting in the above inlined file extent in the data reloc tree. Then the check in get_new_location() will check the file offset, without checking if the file extent is inlined or not, resulting in the above failure. [FIX] Do not allow compression for data reloc inodes. Since data reloc inode sizes are always block aligned, as long as we do not compress, @data_len will always be at least one block, and that will cause can_cow_file_range_inline() to return false, thus no inlined extent will be created. Reported-by: syzbot+d950c6ba09b79f6e1864@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-btrfs/6a373dc5.764cf64f.168fbe.0001.GAE@google.com/ Fixes: 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper") CC: stable@vger.kernel.org Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>