summaryrefslogtreecommitdiff
path: root/fs/ceph
AgeCommit message (Collapse)Author
3 daysMerge tag 'ceph-for-7.4-rc1' of https://github.com/ceph/ceph-clientLinus Torvalds
Pull ceph updates from Ilya Dryomov: "The two things of note here are the disablement of inline data support and a new lazyio mount option to enable LAZYIO behavior globally (instead of via an ioctl on a per-file basis), both from Xiubo. Despite being over a decade old, the inline data feature never lost its experimental status -- it has always been off by default and required passing --yes-i-really-mean-it flag to be turned on. It was officially deprecated in 2020 and is now in the process of being removed on the MDS side. For now it's still possible to re-enable it in Kconfig, but the code will eventually be dropped from the kernel client entirely. Outside of that we have a number of bug fixes, a series from Tal to help phase out older page APIs, some cleanups from Max and a large pile of LLM-based hardening patches. Xiubo is also back in a maintainer role, replacing Slava" * tag 'ceph-for-7.4-rc1' of https://github.com/ceph/ceph-client: (66 commits) ceph: fix uninitialized err in ceph_netfs_issue_read() MAINTAINERS: update ceph and libceph maintainers ceph: validate encoded xattr name lengths ceph: validate MDS reply fragtree split array length ceph: bound name skips in ceph_mdsmap_decode() by the message end ceph: validate fscrypt auth blob length ceph: check the caps of a closed file after caps_wanted_delay_min ceph: don't fail write_begin with -EAGAIN after flushing old snapc ceph: fail readdir when the MDS reply carries no dirfrag ceph: clamp MDS reply result code to the errno range libceph: initialize CRUSH choose argument weights libceph: fix out-of-bounds reads from repeated CRUSH bucket entries ceph: fix 32-bit overflow in readdir reply count check libceph: reject bad connect_seq in msgr1 READY reply ceph: Fix mds session leak in __open_export_target_session() ceph: Fix xattr buffer leak in __send_cap() ceph: fix integer overflow in the snap trace bounds check libceph: bound mon_command ack decode to front len ceph: use alloc_page() in fill_fscrypt_truncate() libceph: use alloc_page() in ceph_alloc_page_vector() ...
7 daysceph: fix uninitialized err in ceph_netfs_issue_read()Alex Markuze
ceph_netfs_issue_read() declares err without an initializer. On the unencrypted, non-sparse success path nothing assigns it, then the out: label does if (err) netfs_read_subreq_terminated(subreq). With CONFIG_INIT_STACK_ALL_PATTERN, err is 0xaaaaaaaa (nonzero), so the subrequest is terminated while the OSD READ is still in flight. netfs then frees the rolling buffer that the OSD iterator still points at, causing a KASAN UAF / oops, and finish_netfs_read() terminates the same subrequest a second time. CONFIG_INIT_STACK_ALL_ZERO hides it because err happens to be 0. Restore the err = 0 initializer so a successful submit leaves completion to finish_netfs_read() only. Cc: stable@vger.kernel.org Fixes: ee4cdf7ba857 ("netfs: Speed up buffered reading") Reported-by: Karl Mehltretter <kmehltretter@gmail.com> Signed-off-by: Alex Markuze <amarkuze@redhat.com> Reviewed-by: Xiubo Li <xiubo.li@clyso.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: validate encoded xattr name lengthssung byeongchan
__build_xattrs() advances over an MDS-provided name length before checking that the name remains in the encoded xattr blob. On 32-bit clients, UINT_MAX wraps the cursor and is stored as a negative internal name length. A later ordinary listxattr() converts it back to a UINT_MAX memcpy size and corrupts kernel memory. Check the name range before advancing and reject name, value, or aggregate sizes that overflow the signed internal accounting. The malformed blob corrupted kernel state in two baseline boots. It was rejected cleanly twice with this change, while normal and length-only controls remained safe. Cc: stable@vger.kernel.org Fixes: 355da1eb7a1f ("ceph: inode operations") Assisted-by: LLM Signed-off-by: sung byeongchan <tjdqudcks0424@naver.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: validate MDS reply fragtree split array lengthsung byeongchan
parse_reply_info_in() advances over the fragtree split array using an unchecked count multiplication. On a 32-bit client, nsplits=0x20000000 wraps the byte length to zero, so the reply is accepted and ceph_fill_fragtree() sorts far beyond the message front. Compute the split length with size_mul() and require the complete array to remain in the inode record before advancing the cursor. The malformed reply faulted in frag_tree_split_cmp() twice on the tested i386 baseline. It was rejected without a fault twice after this change, while a normal reply and a one-split control remained functional. Cc: stable@vger.kernel.org Fixes: 2f2dc053404f ("ceph: MDS client") Assisted-by: LLM Signed-off-by: sung byeongchan <tjdqudcks0424@naver.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: bound name skips in ceph_mdsmap_decode() by the message endsung byeongchan
ceph_mdsmap_decode() reads two remote u32 name lengths and advances its parser cursor without checking that either string remains in the message. On a 32-bit client, a sufficiently large length can wrap the cursor below `end`. The following numerical bounds check then succeeds and dereferences the wrapped address. Use ceph_decode_skip_n() for both names so the remaining bytes are checked before the cursor advances. The issue produced a page fault in ceph_mdsmap_decode twice on the tested i386 baseline. The same frame was rejected without a fault twice after this change, while a normal MDSMap continued to be accepted. Cc: stable@vger.kernel.org Fixes: 2f2dc053404f ("ceph: MDS client") Assisted-by: LLM Signed-off-by: sung byeongchan <tjdqudcks0424@naver.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: validate fscrypt auth blob lengthsung byeongchan
ceph_crypt_get_context() trusts cfa_blob_len after checking only that the inode-owned fscrypt_auth allocation contains the fixed header and one byte. An MDS can therefore place a larger inner length in a shorter outer object, causing memcpy() to read beyond the allocation when a caller requests the encryption nonce. Require the complete blob to fit in the inode-owned allocation before using the inner length. On the tested baseline, the malformed reply produced the same KASAN slab-out-of-bounds read twice. An ordinary UID obtained sixteen wire-absent SLUB redzone bytes in two non-KASAN production-path runs. The input was rejected twice after this change, while a complete context still worked. Cc: stable@vger.kernel.org Fixes: 2d332d5bc424 ("ceph: fscrypt_auth handling for ceph") Assisted-by: LLM Signed-off-by: sung byeongchan <tjdqudcks0424@naver.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: check the caps of a closed file after caps_wanted_delay_minXiubo Li
__ceph_caps_file_wanted() stops wanting the caps of a closed file once its last read or write is caps_wanted_delay_min (5s) old, but nothing checks its caps again before caps_wanted_delay_max (60s): closing does not call ceph_check_caps(), and the inode waits on cap_delay_list, whose check has always come at caps_wanted_delay_max. Meanwhile the MDS keeps us the loner, so each stat from another client of a file we created first waits for the MDS to revoke our exclusive caps. This is what makes mdtest-easy-stat in IO500 on kernel clients swing between 3.6k and 100k stats/s from run to run. Queue a regular file that still holds exclusive or write caps for a cap check caps_wanted_delay_min after its last close, on a list of its own, as cap_delay_list is in caps_wanted_delay_max order. The check gives back what it already gives back at caps_wanted_delay_max (shared caps and the page cache caps stay), only earlier. Directories and open files are not affected. Two noshare kernel mounts on one VM, 1 MDS: 20000 empty files created on A, statted by path on B after a wait. Caps messages are the ones A sends, revokes are the ones the MDS sends A: +--------+------+---------------+---------------+---------------+ | | | in the wait | in the stat | | kernel | wait | caps msgs | stats/s | revokes | +--------+------+---------------+---------------+---------------+ | before | 15s | 0 | 374 - 435 | 11164 - 11730 | | after | 15s | 20000 | 18680 - 24027 | 0 | | before | 3s | 0 | 328 - 342 | 13727 - 14538 | | after | 3s | 0 - 3010 | 2238 - 3216 | 963 - 996 | +--------+------+---------------+---------------+---------------+ The caps go back in the background before the stat instead of one revoke per file during it; with a 3s wait most of them still go back while the stat runs. Signed-off-by: Xiubo Li <xiubo.li@clyso.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: don't fail write_begin with -EAGAIN after flushing old snapcVenky Shankar
When ceph_netfs_check_write_begin() finds the folio dirty in an older snap context, it drops the folio, waits for that snap context to be flushed and then returns -EAGAIN so that the write is retried. However, since commit fac47b43c760 ("netfs: do not unlock and put the folio twice"), netfs_write_begin() retries only when ->check_write_begin() returns 0 with *foliop cleared -- every error, -EAGAIN included, is returned as is. The -EAGAIN thus propagates up to write(2), which fails with "Resource temporarily unavailable" once the dirty data has been flushed, rather than being retried. This is easily hit by buffered writers rewriting dirty pages while snapshots are being taken, e.g. a kernel build linking vmlinux into a directory under a snapshot schedule: ld: final link failed: Resource temporarily unavailable Return the wait_event_killable() result as is so that netfs re-gets the folio and retries on success. Cc: stable@vger.kernel.org Fixes: fac47b43c760 ("netfs: do not unlock and put the folio twice") Link: https://tracker.ceph.com/issues/57656 Signed-off-by: Venky Shankar <vshankar@redhat.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fail readdir when the MDS reply carries no dirfragYuanfu Xie
A ceph mount oopses in ceph_readdir() when the MDS answers a READDIR request with a reply whose extra blob is empty. parse_reply_info() skips reply blobs whose length is zero, so parse_reply_info_readdir() never runs, r_reply_info.dir_dir stays NULL, and the reply still parses as successful. ceph_mdsc_do_request() returns 0 and ceph_readdir() then reads rinfo->dir_dir->frag with no NULL check: /* extra */ ceph_decode_32_safe(&p, end, len, bad); if (len > 0) { ... err = parse_reply_info_extra(&p, p + len, req, features, s); ... } The oops on v7.3.0-rc4-00537-ga3ff15db6820, one getdents64(2) on the mount root after an ms_mode=legacy mount, trimmed: BUG: KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI CPU: 1 UID: 0 PID: 266 Comm: ls Tainted: G N 7.3.0-rc4-00537-ga3ff15db6820 #1 PREEMPT(lazy) RIP: 0010:ceph_readdir+0xbff/0x3ea0 Call Trace: <TASK> ? __pfx_ceph_readdir+0x10/0x10 wrap_directory_iterator+0xa2/0xe0 iterate_dir+0x1da/0x5d0 __x64_sys_getdents64+0x13b/0x2a0 do_syscall_64+0xdd/0x4a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f </TASK> The unconditional frag read came in with 81c6aea5275ea ("ceph: handle frag mismatch between readdir request and reply"), so the bug is old. Treat a reply with no dirfrag as a protocol violation: warn once, drop the request and return -EIO, the same way ceph_readdir() already handles the two malformed-reply cases later in the function. Tested on v7.3.0-rc4-00537-ga3ff15db6820 (KASAN). Unpatched, the getdents64(2) above takes the machine down (oops=panic) on the first READDIR reply. With the patch it warns once and getdents64(2) returns -EIO, no oops, no hang. A conformant reply for an empty directory — dirfrag present, zero entries, FRAG_END|FRAG_COMPLETE — still lists "." and ".." and returns EOF on both kernels, so normal readdir behavior is unchanged. The oops is still present in v7.3-rc5 (72d3fcf802c45). Cc: stable@vger.kernel.org Fixes: 81c6aea5275ea ("ceph: handle frag mismatch between readdir request and reply") Signed-off-by: Yuanfu Xie <yuanfuxie@stu.pku.edu.cn> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: clamp MDS reply result code to the errno rangeYuanfu Xie
ceph_mdsc_wait_request() returns the reply head result code of the MDS request to its callers as a plain int errno. The value is a server-controlled u32 and nothing checks that it actually is an errno; several callers pass it straight to ERR_PTR(), e.g. ceph_finish_lookup() on the atomic open path: err = ceph_mdsc_wait_request(...) /* e.g. +0x50fc00 */ ... if (err) dentry = ERR_PTR(err); IS_ERR() only recognizes the [-MAX_ERRNO, -1] window, so an out-of-range value is not caught anywhere and survives as a "struct dentry *". On the open path the positive error travels ceph_finish_lookup() -> ceph_atomic_open() -> ERR_PTR() in fs/namei.c's atomic_open() wrapper -> step_into(), which dereferences it as a dentry and oopses (mainline 7.3-rc3-135-g4982d3552a3b, the value in R12 is the server-supplied result): BUG: KASAN: probably user-memory-access in range [0x000000000050fc00-0x000000000050fc07] RIP: 0010:step_into_slowpath+0x3e3/0xe60 R12: 000000000050fc00 Call Trace: <TASK> ? __pfx_ceph_atomic_open+0x10/0x10 path_openat+0xf41/0x2440 do_file_open+0x228/0x480 do_sys_openat2+0x103/0x1d0 __x64_sys_openat+0x141/0x200 do_syscall_64+0xe8/0x4f0 entry_SYSCALL_64_after_hwframe+0x77/0x79 </TASK> (0xdffffc00000a1f80 in the full report is the KASAN shadow of 0x50fc00; without KASAN the raw value is read.) Values with the top bit set fare no better: 0xd8000000 sign-extends through the int conversion into the kernel address range and takes a page fault instead. Clamp the result at the single read and return -EIO for anything outside the errno range, which is what a malformed reply means there. net/9p does the same to server errno values with safe_errno(). This is the path the reported crash takes; the other readers of head->result, including the ceph_async_create_cb() and ceph_async_unlink_cb() callbacks, are not covered by this patch. Each of the six crashing opens was run on a build of 7.3-rc3-135-g4982d3552a3bf with only this change applied: it crashes the unpatched kernel on the first openat() (verified on the same tree without the patch), and with the patch the open fails cleanly without an oops. A few hundred normal file operations on the patched kernel also completed without a crash; valid errno values pass through unchanged. Cc: stable@vger.kernel.org Fixes: 2f2dc053404febedc9c273452d9d518fb31fde72 ("ceph: MDS client") Signed-off-by: Yuanfu Xie <yuanfuxie@stu.pku.edu.cn> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix 32-bit overflow in readdir reply count checkJérémy Jean
parse_reply_info_readdir() checks how many entries are in a readdir reply by computing the end pointer of the decoded array. That pointer addition can wrap on 32-bit kernels and let an oversized count pass the check. For example, a count of 0x40000000 with 156-byte entries wraps to a zero offset, so the decoder can write past the preallocated buffer. Check the count against the number of entries that fit in the allocation before publishing dir_nr. Reject oversized replies through the existing error path. Cc: stable@vger.kernel.org Fixes: 54008399dc0c ("ceph: preallocate buffer for readdir reply") Assisted-by: LLM Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: Fix mds session leak in __open_export_target_session()Wentao Liang
__open_export_target_session() holds a reference on the target session, either from __ceph_lookup_mds_session() or from register_session(), which the caller is expected to drop. When __open_session() fails, the helper returns ERR_PTR(ret) without dropping that reference, leaking one session reference on every open failure. Drop the session reference before returning the error. Cc: stable@vger.kernel.org Fixes: b682c6d41bc2 ("ceph: switch to WARN_ON_ONCE in encode_supported_features()") Signed-off-by: Wentao Liang <vulab@iscas.ac.cn> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: Fix xattr buffer leak in __send_cap()Wentao Liang
__prep_cap() takes a reference on the xattr blob via ceph_buffer_get() and also grabs a reference to the old xattr blob returned by __ceph_build_xattrs_blob(). These references are normally dropped in __send_cap() after the cap message has been sent, but when ceph_msg_new() fails, __send_cap() requeues the cap and returns early without dropping them, leaking both buffers on every allocation failure. Release arg->old_xattr_buf and arg->xattr_buf on the ceph_msg_new() failure path before returning. Cc: stable@vger.kernel.org Fixes: 16d68903f56a ("ceph: break up send_cap_msg") Signed-off-by: Wentao Liang <vulab@iscas.ac.cn> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix integer overflow in the snap trace bounds checkXiang Mei
ceph_update_snap_trace() bounds the two encoded snapid arrays that follow a struct ceph_mds_snap_realm with sizeof(u64) * (num_snaps + num_prior_parent_snaps). Both counts come straight off the wire as __le32, so the addition is evaluated in unsigned int and wraps before sizeof(u64) promotes it to size_t: an MDS sending num_snaps = 1 and num_prior_parent_snaps = 0xffffffff makes the sum zero and the check degenerates to ceph_decode_need(&p, e, 0, bad), which always passes. dup_array() then reads num_snaps * 8 bytes past the end of the message front, which ceph_msg_new2() allocates at exactly front_len bytes. An unsolicited CEPH_MSG_CLIENT_SNAP reaches this from a malicious or compromised MDS with no client-side action, as do the snaptraces carried in an MDS reply and in a cap import. Compute the length with size_add()/size_mul() so it saturates at SIZE_MAX instead of wrapping, in the style fs/ceph/mdsmap.c already uses for num_export_targets. BUG: KASAN: slab-out-of-bounds in dup_array (include/linux/unaligned.h:28 fs/ceph/snap.c:500) Read of size 8 at addr ffff8880102a16c8 by task kworker/1:2/129 Workqueue: ceph-msgr ceph_con_workfn Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) dup_array (include/linux/unaligned.h:28 fs/ceph/snap.c:500) ceph_update_snap_trace (fs/ceph/snap.c:836) ceph_handle_snap (fs/ceph/snap.c:1155) mds_dispatch (fs/ceph/mds_client.c:7234) ceph_con_process_message (net/ceph/messenger.c:1424) ceph_con_v1_try_read (net/ceph/messenger_v1.c:1430) ceph_con_workfn (net/ceph/messenger.c:1576) process_one_work (kernel/workqueue.c:3396) worker_thread (kernel/workqueue.c:3479 kernel/workqueue.c:3560) 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) ... The buggy address is located 0 bytes to the right of allocated 72-byte region [ffff8880102a1680, ffff8880102a16c8) Cc: stable@vger.kernel.org Fixes: 963b61eb041e ("ceph: snapshot management") Reported-by: co+ecaada42f806f629@bugs.sh Assisted-by: LLM Signed-off-by: Xiang Mei <xmei5@asu.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: use alloc_page() in fill_fscrypt_truncate()Tal Zussman
fill_fscrypt_truncate() allocates a scratch page with __page_cache_alloc(). The page never enters the page cache and is freed with __free_pages(). It does not need the cpuset memory spread policy that __page_cache_alloc() applies, so use alloc_page() instead. Assisted-by: Cursor Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: don't keep a mon_addr string that failed to parseYogesh Gaur
ceph_parse_mon_addr() stores the string in fsopt->mon_addr before handing it to ceph_parse_mon_ips(), and then returns that function's error. So after a mon_addr= that does not parse, fsconfig() reports the failure but the mount context keeps the rejected string. That matters because ceph_get_tree() uses fsopt->mon_addr as the "monitor addresses were supplied" test: if (fsopt->new_dev_syntax && !fsopt->mon_addr) return invalfc(fc, "No monitor address"); Userspace is free to ignore the error from fsconfig(FSCONFIG_SET_STRING) and go straight to FSCONFIG_CMD_CREATE. The check then passes on a value that was rejected, and the mount continues with ceph_options::num_mon still 0 - which is how syzbot reached the BUG_ON() in pick_new_mon() fixed by the previous patch. Parse first and commit only on success. A failed mon_addr= now leaves any previously accepted value in place instead of replacing it with one that does not parse. Fixes: 7b19b4db5add ("ceph: new device mount syntax") Reported-by: syzbot+1dbed5969931c19d3eb5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1dbed5969931c19d3eb5 Assisted-by: Cursor Signed-off-by: Yogesh Gaur <yogeshgaur.83@gmail.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: add 'lazyio' mount option to kclientXiubo Li
Add a 'lazyio' mount option to the kernel Ceph client that enables LazyIO globally for all regular file opens on a mount. This is the kclient equivalent of the 'client_force_lazyio=true' config option in the ceph-fuse userspace client. When 'lazyio' is specified, CEPH_FILE_MODE_LAZY is automatically added to every regular file's fmode at open time in ceph_open() and ceph_atomic_open(), causing the I/O paths to request CEPH_CAP_FILE_LAZYIO from the MDS. This permits buffered I/O via the page cache even when multiple clients have the file open for write — beneficial for HPC workloads that can tolerate relaxed cache coherency. The mount option is exposed as 'lazyio' / 'nolazyio' via the VFS fsparam_flag_no mechanism and supports remount. Link: https://tracker.ceph.com/issues/77594 Signed-off-by: Xiubo Li <xiubo.li@clyso.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: wake up i_cap_wq waiters on inode shutdownXiubo Li
ceph_inode_shutdown() purges every cap on the inode but never wakes i_cap_wq, unlike remove_session_caps_cb() which does so after the same kind of purge. Anyone blocked in ceph_get_caps() is waiting on that queue for caps that are now gone for good, so the wait only ends when the task is killed. Wake the queue after purging so the waiters get a chance to notice the shutdown and bail out. Signed-off-by: Xiubo Li <xiubo.li@clyso.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: require the dentry lease record to cover struct ceph_mds_reply_leaseXiang Mei
parse_reply_info_lease() reads a 32-bit struct_len off the wire and only checks that many bytes are present, without requiring struct_len to cover sizeof(struct ceph_mds_reply_lease) (10 bytes). A malicious MDS can declare a shorter record and still get the pointer published: the following *p += sizeof(**lease) overshoot is undone by *p = lend, so nothing downstream rejects the reply. ceph_fill_trace() -> update_dentry_lease() then reads 10 bytes through that pointer on any lookup on a mounted CephFS, running past the end of the message front. Require the record to cover the structure, matching the sibling parse_reply_info_dir(). A conforming MDS always encodes the full 10 bytes. BUG: KASAN: slab-out-of-bounds in __update_dentry_lease.constprop.0 (fs/ceph/inode.c:1459) Read of size 4 at addr ffff888026c63d65 by task kworker/0:3/2366 Workqueue: ceph-msgr ceph_con_workfn Call Trace: __update_dentry_lease.constprop.0 (fs/ceph/inode.c:1459) ceph_fill_trace (fs/ceph/inode.c:1474 fs/ceph/inode.c:1823) mds_dispatch (fs/ceph/mds_client.c:4229 fs/ceph/mds_client.c:7225) ceph_con_process_message (net/ceph/messenger.c:1424) ceph_con_v1_try_read (net/ceph/messenger_v1.c:1430) ceph_con_workfn (net/ceph/messenger.c:1576) process_one_work (kernel/workqueue.c:3396) worker_thread (kernel/workqueue.c:3479 kernel/workqueue.c:3560) 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) The buggy address is located 357 bytes inside of allocated 360-byte region [ffff888026c63c00, ffff888026c63d68) Cc: stable@vger.kernel.org Fixes: 4ac4c23eaa38 ("ceph: decode alternate_name in lease info") Reported-by: co+f92fbe44e3c6df69@bugs.sh Assisted-by: LLM Signed-off-by: Xiang Mei <xmei5@asu.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: reject mdsmaps that claim more than CEPH_MAX_MDS ranksXiang Mei
check_new_map() sizes its export-target bitmap from CEPH_MAX_MDS but walks it with newmap->possible_max_rank, which comes straight off the wire: ceph_mdsmap_decode() sets it to max(m_num_active_mds, m_max_mds), then overwrites it with the size of the "in" set. Neither is bounded by CEPH_MAX_MDS, only by m_info allocating, so a monitor announcing more ranks than the bitmap holds makes test_bit() read past the end of targets[]. The clear_bit() loop above indexes the same bitmap, though it is additionally bounded by mdsc->max_sessions. The map arrives on the monitor connection, which is unauthenticated in legacy msgr mode. CEPH_MAX_MDS is a protocol constant from src/mds/mdstypes.h, and the reserved inode ranges in super.h are laid out one rank apart over that span, so a map claiming more ranks is corrupt. Reject it where the rank space is established rather than letting the count reach check_new_map(). BUG: KASAN: stack-out-of-bounds in check_new_map (fs/ceph/mds_client.c:5968) Read of size 8 at addr ffffc90000ac7798 by task kworker/1:2/127 Workqueue: ceph-msgr ceph_con_workfn Call Trace: <TASK> dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) kasan_check_range (mm/kasan/generic.c:186 mm/kasan/generic.c:200) check_new_map (fs/ceph/mds_client.c:5968) ceph_mdsc_handle_mdsmap (fs/ceph/mds_client.c:7060) extra_mon_dispatch (fs/ceph/super.c:802) mon_dispatch (net/ceph/mon_client.c:1483) ceph_con_process_message (net/ceph/messenger.c:1424) ceph_con_v1_try_read (net/ceph/messenger_v1.c:1430) ceph_con_workfn (net/ceph/messenger.c:1576) process_one_work (kernel/workqueue.c:3396) worker_thread (kernel/workqueue.c:3479 kernel/workqueue.c:3560) 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) </TASK> The buggy address belongs to stack of task kworker/1:2/127 and is located at offset 288 in frame: check_new_map (fs/ceph/mds_client.c:5846) This frame has 1 object: [32, 288) 'targets' Cc: stable@vger.kernel.org Fixes: d517b3983dd3 ("ceph: reconnect to the export targets on new mdsmaps") Reported-by: co+f8e3e01d8dda8689@bugs.sh Assisted-by: LLM Signed-off-by: Xiang Mei <xmei5@asu.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix off-by-one NUL termination in decode_encrypted_symlink()Xiang Mei
decode_encrypted_symlink() allocates enclen + 1 bytes but writes the NUL terminator at sym[declen + 1] instead of sym[declen]. A malicious MDS can report symlink_len == 0 for an encrypted inode, and base64_decode() returns 0 for a zero-length input, so the store lands one byte past a kmalloc(1) allocation. Terminate at sym[declen], which is always within the allocation. BUG: KASAN: slab-out-of-bounds in ceph_fill_inode (fs/ceph/inode.c:996) Write of size 1 at addr ffff888020aad361 by task kworker/0:0/9 Workqueue: ceph-msgr ceph_con_workfn Call Trace: kasan_report (mm/kasan/report.c:595) ceph_fill_inode (fs/ceph/inode.c:996 fs/ceph/inode.c:1277) ceph_fill_trace (fs/ceph/inode.c:1716) ceph_con_process_message (net/ceph/messenger.c:1424) ceph_con_v1_try_read (net/ceph/messenger_v1.c:1430) ceph_con_workfn (net/ceph/messenger.c:1576) ... The buggy address belongs to the object at ffff888020aad360 which belongs to the cache kmalloc-8 of size 8 The buggy address is located 0 bytes to the right of allocated 1-byte region [ffff888020aad360, ffff888020aad361) Fixes: 79f2f6ad878c ("ceph: create symlinks with encrypted and base64-encoded targets") Reported-by: co+b0f93e29183ce737@bugs.sh Assisted-by: LLM Signed-off-by: Xiang Mei <xmei5@asu.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix out-of-bounds read in ceph_netfs_issue_op_inline()Guanglei Zhu
The read offset is validated against i_size but never against inline_len, and the two fields come from the MDS independently. When a read starts past the end of the inline data, the subtraction len = min_t(size_t, iinfo->inline_len - subreq->start, subreq->len); underflows and min_t() ends up with subreq->len, so copy_to_iter() reads past the end of the inline buffer straight into the user buffer. A malicious or buggy MDS reporting a short inline payload together with an inflated i_size can thus leak kernel heap memory to userspace. Bail out with -ENODATA when the offset is not within the inline data. Cc: stable@vger.kernel.org Fixes: 5b19f1eba459 ("ceph: make ceph_netfs_issue_op() handle inlined data") Signed-off-by: Guanglei Zhu <zhugl3@xiaopeng.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: clamp the inline data length in ceph_fill_inline_data()Guanglei Zhu
The MDS decides how much inline data to attach to a reply, and the client parses inline_len without any upper bound: ceph_decode_need() only verifies that the message actually carries that many bytes. ceph_fill_inline_data() then copies the data into a single folio with no length check, so a malicious or buggy MDS returning more inline data than the folio can hold writes past its end. Clamp the length to folio_size() so both callers, handle_cap_grant() and fill_inode(), are covered. Cc: stable@vger.kernel.org Fixes: 31c542a199d7 ("ceph: add inline data to pagecache") Signed-off-by: Guanglei Zhu <zhugl3@xiaopeng.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix longname buffer overflow in ceph_fname_to_usr()Jiangshan Yi
Snapshot longname reconstruction formats "_<name>_<inode>" via snprintf() and memcpy()s the result into oname->name. Checking the length against NAME_MAX only protects the LOOKUPNAME/export callers, whose buffer is allocated with fscrypt_fname_alloc_buffer(NAME_MAX). The readdir path points oname->name straight at the altname in the MDS reply, whose capacity is altname_len; the reconstructed name can exceed that while still fitting under NAME_MAX, overflowing the in-place destination. Save the actual oname->len before the decryption overwrites it, and fail with -ENAMETOOLONG when the reconstructed name would not fit. Cc: stable@vger.kernel.org Fixes: dd66df0053ef ("ceph: add support for encrypted snapshot names") Suggested-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Jiangshan Yi <yijiangshan@kylinos.cn> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: use iov_iter_extract_bvecs() for direct I/OTal Zussman
ceph_direct_read_write() builds its bio_vec array with iov_iter_get_pages2(), which takes a reference on each page rather than pinning it. As the page is not pinned, fork() does not copy it early for the child and page migration is free to move it. If a process forks while a direct read is in flight and the parent then writes to the page, the parent gets a fresh copy while the read completes into the original, which now belongs to the child. The read reports success and the parent never sees the data. Switch to iov_iter_extract_bvecs(), which pins user pages with FOLL_PIN and borrows kernel pages without taking a reference. This is the same conversion the block layer did in commit a7e689dd1c06 ("block: Convert bio_iov_iter_get_pages to use iov_iter_extract_pages") and fuse did in commit 738adade96b2 ("fuse: Fix missing FOLL_PIN for direct-io"). Since iov_iter_extract_bvecs() coalesces contiguous ranges within a folio into a single bio_vec, release per folio in put_bvecs() with unpin_user_folio(). Unpinning is done based on iov_iter_extract_will_pin(). The bio_vec array no longer needs to be zeroed. put_bvecs() used to walk all npages entries and skip those with a NULL bv_page. iov_iter_extract_bvecs() counts the entries it fills, so return that count instead and don't go past it. The array is still sized by npages, as that is the worst case of one bio_vec per page. Cap npages at USHRT_MAX to match the type taken by iov_iter_extract_bvecs(). This also replaces the deprecated set_page_dirty_lock() with folio_mark_dirty_lock() and drops the on-stack page array. Tested against a Ceph 19.2.3 cluster with KASAN, lockdep and DEBUG_VM enabled. A reproducer issues an 8 MiB O_DIRECT read (libaio and blocking pread, with and without THP), forks while the read is held in flight by pausing the OSDs, has the parent write to every page of the buffer, and verifies the result. Without this change the parent loses the read data in every variant, and with it the data is intact. The AIO and direct I/O xfstests in the generic group pass as well. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: don't clear write_congested when collecting foliosTal Zussman
ceph_process_folio_batch() assigns the result of is_write_congestion_happened() to fsc->write_congested for every folio it collects. The code it replaced only ever set write_congested to true here, leaving the decrement paths to clear it once writeback_count drops below CONGESTION_OFF_THRESH. The unconditional assignment means that a sync writeback that runs while the count sits between the off and on thresholds clears the flag on its first folio, and background writeback resumes early. Only set the flag here, as before. Fixes: ce80b76dd327 ("ceph: introduce ceph_process_folio_batch() method") Assisted-by: Claude:fable-5.1 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix writeback_count leak when a bounce page allocation failsTal Zussman
ceph_process_folio_batch() calls is_write_congestion_happened(), which increments fsc->writeback_count, before calling move_dirty_folio_in_page_array(). If the fscrypt bounce page allocation there fails, the folio is redirtied and never makes it into the page array, but the count is never dropped. write_congested is only cleared when a decrement falls below CONGESTION_OFF_THRESH, so the leaked counts set congestion early and eventually leave write_congested set for good. Every WB_SYNC_NONE writeback then returns 0 and background writeback stops entirely. Bump the count only once the folio is in the page array. Fixes: d55207717ded ("ceph: add encryption support to writepage and writepages") Reported-by: Sashiko <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260902-remove-wait-on-page-writeback-v5-0-0b512e77e75b%40columbia.edu?part=1 Assisted-by: Claude:fable-5.1 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: avoid pending truncate during nonblocking cap acquireMax Kellermann
Readahead acquires caps while the folios are locked. This can deadlock when processing a pending truncate; truncate_pagecache() can then attempt to a folio that is already locked. Return -EAGAIN instead when `NON_BLOCKING` is set, causing the readahead to be abandoned. Cc: stable@vger.kernel.org Fixes: 49870056005c ("ceph: convert ceph_readpages to ceph_readahead") Signed-off-by: Max Kellermann <max.kellermann@ionos.com> Reviewed-by: Xiubo Li <xiubo.li@clyso.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: rename ceph_check_page_before_write() to ceph_check_folio_before_write()Tal Zussman
This function only operates on folios, so rename it accordingly. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: remove ceph_fscrypt_pagecache_page() and ceph_fscrypt_page_offset()Tal Zussman
All callers have been converted to the folio equivalents, so remove these unused helpers. This also removes a use of page_offset(). Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: convert page cleanup loop in writepages_finish() to foliosTal Zussman
Convert the page cleanup loop in writepages_finish() to work on folios. Resolve the folio up front and unwrap any fscrypt bounce folio to its page cache folio, storing the page cache page back into the array. This removes a use of detach_page_private() and five calls to compound_head() per page, while adding one back via page_folio(). While at it, remove the BUG_ON() in the num_pages loop, as it cannot be triggered. No functional change. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: remove page remnants from write_folio_nounlock()Tal Zussman
Use ceph_folio_start_fscache() and only use a struct page pointer at the osd_req_op_extent_osd_data_pages() boundary, which requires a page array. This removes a call to compound_head() in ceph_set_page_fscache(). This was the last user of ceph_set_page_fscache(), so remove it. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: convert ceph_submit_write() to foliosTal Zussman
Convert the request assembly loop and error path in ceph_submit_write() to folios. This drops ceph's uses of the set_page_writeback(), redirty_page_for_writepage(), and unlock_page() compatibility wrappers in the writeback submission path. Add ceph_folio_start_fscache(), a folio counterpart of ceph_set_page_fscache(). The remaining caller of the latter in write_folio_nounlock() will be converted separately. In total, this removes eight calls to compound_head() hidden in the page-based APIs and one explicit page_folio() call in ceph_undo_wrbuffer_claim()'s caller, while adding four explicit page_folio() calls. Note that get_writepages_data_length() must still be passed the possibly-bounce folio, not the unwrapped pagecache folio, as it checks fscrypt_is_bounce_folio() to round encrypted lengths up to the fscrypt block size. No functional change. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: convert get_writepages_data_length() to foliosTal Zussman
Introduce ceph_fscrypt_pagecache_folio() and ceph_fscrypt_folio_offset(), folio equivalents of ceph_fscrypt_pagecache_page() and ceph_fscrypt_page_offset(), and use them to convert get_writepages_data_length() to folios. This removes the last caller of page_snap_context(), so remove it as well. This also removes a use of page->private and a call to fscrypt_is_bounce_page(). Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: convert ceph_wait_until_current_writes_complete() to foliosTal Zussman
Iterate the writeback batch as folios rather than using &folios[i]->page. Use ceph_folio_snap_context() for the snap context check and folio_wait_writeback() instead of wait_on_page_writeback(). This removes one call to compound_head() and the last caller of wait_on_page_writeback(). No functional change. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Matthew Wilcox (Oracle) <willy@infradead.org> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: add ceph_folio_snap_context()Tal Zussman
Add a folio-based counterpart to page_snap_context() that reads the snap context from folio->private directly. Convert the three writeback paths that passed &folio->page to page_snap_context() to ceph_folio_snap_context(), removing three open-coded folio-to-page conversions. No functional change. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Acked-by: Zi Yan <ziy@nvidia.com> Reviewed-by: Matthew Wilcox (Oracle) <willy@infradead.org> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: wait for pending fscache write in write_folio_nounlock()Tal Zussman
write_folio_nounlock() marks the folio with PG_private_2 via ceph_set_page_fscache() and starts an async write to the cache via ceph_fscache_write_to_cache(). PG_private_2 is only cleared once that write completes. If the OSD write fails, the error paths return without waiting for the cache write. On retry, ceph_find_incompatible() would wait for writeback, but not for PG_private_2, before calling write_folio_nounlock() again, which would trip the VM_BUG_ON_FOLIO() in folio_start_private_2(). Wait for any pending cache write before starting writeback on the folio. Do so before bumping the writeback congestion count and allocating the possibly mempool-backed OSD request, so that a thread sleeping on the cache I/O does not hold either while it waits. Add ceph_folio_wait_fscache(), a wrapper for folio_wait_private_2(), and use it. Fixes: 1702e7973410 ("ceph: add fscache writeback support") Reported-by: Sashiko <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260804-remove-wait-on-page-writeback-v2-0-81f0ab065284%40columbia.edu?part=7 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix error handling when the OSD client is stopping in ceph_submit_write()Tal Zussman
When ceph_inc_osd_stopping_blocker() fails, ceph_submit_write() redirties and unlocks the folios remaining in ceph_wbc->fbatch. However, none of those folios are locked at this point: locked folios were moved to ceph_wbc->pages[] and removed from the fbatch by ceph_process_folio_batch(). Unlocking them triggers VM_BUG_ON_FOLIO(!folio_test_locked(folio)) in folio_unlock(), or unlocks a folio locked by another task. This error path also returns -EIO without fully cleaning up the folios in ceph_wbc->pages[]: their references are never dropped, fscrypt bounce pages are never freed, the writeback congestion count is never decremented, and the pages array is leaked. ceph_wbc->locked_pages is also left non-zero, so ceph_writepages_start() loops back around and hits BUG_ON(ceph_wbc.locked_pages). Leave the fbatch folios alone, as ceph_writepages_start() drops their references when releasing the batch, and unwind ceph_wbc->pages[] the same way writepages_finish() would have. Abort writeback in ceph_writepages_start() instead of continuing, matching the handling of ceph_inc_osd_stopping_blocker() failure on entry, as the OSD client is being torn down and further writeback cannot make progress. An LLM was used to verify that the cleanup was comprehensive, and suggested aborting the writeback instead of continuing. Fixes: 1551ec61dc55 ("ceph: introduce ceph_submit_write() method") Reported-by: Sashiko <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260804-remove-wait-on-page-writeback-v2-0-81f0ab065284%40columbia.edu?part=5 Assisted-by: Claude:fable-5 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: convert r_locked_page to r_locked_folioTal Zussman
Pass a folio through the inline data plumbing: __ceph_do_getattr(), struct ceph_mds_request, and ceph_fill_inode(). No callers pass a page anymore, so this removes the &folio->page conversions in the fault and READ_INLINE paths and the page_folio() call in ceph_fill_inode(). Remove externs from function declarations while at it. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: convert ceph_fill_inline_data() to take a folioTal Zussman
Convert the internals to folio APIs and pass a folio in from ceph_fill_inode(). The kmap_atomic()/memcpy() pair becomes memcpy_to_folio(), which also flushes the dcache, so drop the explicit flush from the folio creation path. When a locked folio is passed in, the callers flush after filling it, so the flush in memcpy_to_folio() is redundant there, but harmless. This removes the last caller of find_or_create_page() in ceph and saves six calls to compound_head(). The page_folio() call added in ceph_fill_inode() goes away once the locked page plumbing is converted. Remove extern from the function declaration while at it. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: use a folio in the READ_INLINE path of ceph_read_iter()Tal Zussman
The inline data is read into a scratch buffer that never enters the page cache, so allocate it with folio_alloc() instead of __page_cache_alloc(). This drops the cpuset page spreading that __page_cache_alloc() applies on NUMA systems, which only makes sense for page cache pages. The temporary buffer here is better off node local. The mismatched __free_page()/__free_pages() pair both become folio_put(). The __ceph_do_getattr() locked page plumbing will be converted in a later patch, so pass &folio->page for now. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: use a folio in the inline read branch of ceph_filemap_fault()Tal Zussman
Convert find_or_create_page() to __filemap_get_folio(), keeping the existing GFP constraint. Saves three calls to compound_head(). The __ceph_do_getattr() locked page plumbing is converted separately, so pass &folio->page for now. Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: use a folio in ceph_get_caps()Tal Zussman
Convert the inline data page cache check to filemap_get_folio(), which returns an ERR_PTR instead of NULL when no folio is present. Saves two calls to compound_head(). Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: fix page vector leak on encrypted netfs read EIO pathAlex Markuze
ceph_netfs_issue_read() pins pages with iov_iter_get_pages_alloc2() for encrypted inodes and attaches them with own_pages=false. If ceph_inc_osd_stopping_blocker() then fails, the request is dropped without calling finish_netfs_read(), so the page array and the references taken on those pages leak: ceph_osd_data_release() only frees pages when own_pages is true, and __free_pages() would be the wrong destructor for GUP-pinned pages. Release the vector with put_page() and kvfree() on that error path, matching finish_netfs_read(). Unencrypted ITER reads are unaffected. Fixes: 1464de9f813e ("ceph: wait for OSD requests' callbacks to finish when unmounting") Signed-off-by: Alex Markuze <amarkuze@redhat.com> Reviewed-by: Tal Zussman <tz2294@columbia.edu> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: keep the inode, not the name, when a dentry lease expiresXiubo Li
ceph_d_delete() drops a dentry once its lease expires. Since ceph uses inode_just_drop() for ->drop_inode, that last dput also evicts the inode, and ceph_evict_inode() throws away a page cache that the caps still vouch for. A buffered write that outlasts the dentry lease (30s by default on the MDS side) is therefore flushed and closed, and the reopen re-reads everything from the OSDs even though both the caps and the pages were still valid. Commit 52dd0f1b3f94 ("ceph: use generic_delete_inode() for ->drop_inode") removed the ->drop_inode hook because "positive dentry and corresponding inode are always accompanied in MDS reply. So no need to keep inode in the cache after dropping all its aliases". That holds for the metadata, but it ties the lifetime of the page cache to the lifetime of a name, and the two are validated by entirely different things: a dentry is only as good as its lease, while cached pages are good for as long as the caps that back them. So bring the hook back and keep a regular file's inode while it still has cached pages and still holds real caps. The name is unaffected: an expired dentry is unhashed exactly as before, and the next lookup goes to the MDS and finds the retained inode via ceph_get_inode() -> iget5_locked(), which is keyed on the vino from the reply rather than on any dentry. ceph_fill_inode() then refills it without touching the mapping. Retention is bounded by the page cache itself, and reclaim runs in that order rather than the other way round. A retained inode holds no reference of its own, but it does not sit on the inode LRU either: mapping_shrinkable() is false while real folios are present, so the inode shrinker never sees it. Page reclaim frees its pages like any other file's; emptying the mapping is what makes it shrinkable and puts it on the LRU, and only then can the inode shrinker evict it. Unmount evicts it regardless, as evict_inodes() walks sb->s_inodes rather than the LRU. MDS cap trim needs one more thing. trim_caps_cb() releases an unused cap indirectly, by pruning the inode's aliases and letting the eviction that follows call __ceph_remove_caps(). Retaining the inode breaks that chain in both of its cases: an inode whose aliases are already gone was never reachable that way, and after d_prune_aliases() the iput() that ceph_iterate_session_caps() owes the inode no longer evicts it. Give that path an explicit override in CEPH_I_EVICT_ON_FINAL_IPUT_BIT, which ceph_drop_inode() consumes with test_and_clear_bit(). Being one-shot is the point: it lets the MDS force this inode out now without permanently changing how the inode is cached afterwards, which is why I_DONTCACHE is not used here. Signed-off-by: Xiubo Li <xiubo.li@clyso.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: mark the superblock active after mountingXiubo Li
ceph_get_tree() calls sget_fc() directly and so never goes through vfs_get_super(), which is what sets SB_ACTIVE after fill_super for filesystems that use it. Before commit 82995cc6c5ae ("libceph, rbd, ceph: convert to use the new mount API") the flag used to be set by get_sb_nodev(), which did this for ceph, but the conversion dropped it. SB_ACTIVE is required for iput_final() to retain an inode on the inode LRU instead of evicting it once its last reference goes away. This has gone unnoticed because ceph sets inode_just_drop() for ->drop_inode, which always asks for eviction, so the retain branch in iput_final() was never reachable. The next patch makes ->drop_inode() able to retain inodes, and that needs the flag to actually work. Set it once the mount is up, like nfs_get_tree() does. Fixes: 82995cc6c5ae ("libceph, rbd, ceph: convert to use the new mount API") Signed-off-by: Xiubo Li <xiubo.li@clyso.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: __{dir,dentry}_lease_check() returns an enumMax Kellermann
The declaration of these return valued (KEEP, DELETE, TOUCH, STOP) and their use in __dentry_leases_walk() suggests that these are bit flags, but there is only ever one bit set. Converting these to a non-anonymous enum expresses the intent more clearly. [ idryomov: comment on enum lease_check_result isn't a kernel-doc comment ] Signed-off-by: Max Kellermann <max.kellermann@ionos.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: disable inline data supportXiubo Li
Inline data support has been deprecated in the CephFS MDS and is being formally removed. Existing inline data can be migrated to data pool objects via MDS scrub (see PR #44359). Add CONFIG_CEPH_FS_INLINE_DATA (default n) to gate all inline data code in the kclient. When disabled, CEPH_FEATURE_MDS_INLINE_DATA is not advertised to the MDS, which will then refuse to open files that still have inline data, returning -EPERM and preventing silent data corruption that would occur if the kclient tried to read inline data from OSD objects that do not contain it. The conditional compilation is centralized in fs/ceph/super.h, where static inline helpers make all the inline data code compile out cleanly when the option is disabled, so that removing the option later only touches that one place. ceph_inline_version() returns CEPH_INLINE_NONE when the option is disabled, which makes every version comparison in the disabled build constant-fold to false; the stubbed out ceph_uninline_data() and ceph_fill_inline_data() are then unreachable. The MDS still carries inline_version + inline_data on the wire, so the reply and cap grant parsers keep consuming those fields even with the option disabled, only skipping them, to keep the decode pointer in sync for the fields that follow. This config option will be kept for a transitional period to allow existing CephFS deployments to migrate inline data via MDS scrub. Once the migration period ends, the option and all inline data code will be permanently removed. Users upgrading from an older Ceph release should ensure all inline data has been migrated before deploying a kclient with this option disabled: 1. Enable and run MDS scrub to uninline existing data 2. Disable inline_data on the filesystem: ceph fs set <fs> inline_data false --yes-i-really-really-mean-it Link: https://tracker.ceph.com/issues/67769 Signed-off-by: Xiubo Li <xiubo.li@clyso.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: remove redundant inode number from ceph_inode_infoMax Kellermann
On 64 bit systems, ceph_vino_to_ino_t() is a no-op, and inode.i_ino is the same as ceph_vino.ino. This reduces the size of `struct ceph_inode_info` by 8 bytes. Signed-off-by: Max Kellermann <max.kellermann@ionos.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
7 daysceph: use ceph_vino() etc. instead of accessing i_vino directlyMax Kellermann
Prepare to remove (optimize) `i_vino`. Signed-off-by: Max Kellermann <max.kellermann@ionos.com> Reviewed-by: Alex Markuze <amarkuze@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>