From: Dairui Zhang <zhangdairui@gmail.com>
To: ceph-devel@vger.kernel.org
Cc: Dairui Zhang <zhangdairui@gmail.com>,
security@kernel.org, Ilya Dryomov <idryomov@gmail.com>,
Xiubo Li <xiubli@redhat.com>, Venky Shankar <vshankar@redhat.com>
Subject: [PATCH] libceph: bound sparse-read consumption to received data in secure mode
Date: Mon, 21 Sep 2026 01:45:25 +0800 [thread overview]
Message-ID: <20260920174525.1331992-1-zhangdairui@gmail.com> (raw)
In msgr2 secure mode the whole reply tail is read into a page vector
sized by the preamble-declared tail_len:
enc_page_cnt = calc_pages_for(0, tail_len);
tail_len comes from the peer: preamble parsing only enforces
fd_lens[3] <= CEPH_MSG_MAX_DATA_LEN, and for sparse replies the
data_len sanity check is skipped entirely (osd_client.c:5458, gated
on !srlen).
process_v2_sparse_read() then walks that vector by server-supplied
extent lengths with no bounds check:
int idx = spos >> PAGE_SHIFT;
struct page *spage = con->v2.in_enc_pages[idx];
sparse_extent_map_valid() only requires the extents to be ordered,
non-overlapping, inside the *requested* range, sum(len) ==
sr_datalen, and each len <= INT_MAX. So an OSD can send a tiny tail
with a perfectly valid extent map claiming data that was never sent,
and nothing notices. (The destination cursor doesn't clamp this
either: sparse_read_total is the sum of the *request's* op lengths,
and the map validation forces sum(len) <= sr_req_len, so the
destination can't run out before the source overruns.)
With a malicious or compromised OSD - the same model as
CVE-2026-89657 - the copy loop runs off the end of the page vector:
- past in_enc_page_cnt, adjacent slab gets dereferenced as struct
page * -> near-certain oops;
- even before that, ceph_alloc_page_vector() doesn't zero its
pages, so the tail-page slack is stale kernel memory that gets
memcpy_page()d into the user's read() buffer every time;
- if a wild page * happens to be dereferenceable, adjacent heap
keeps flowing into read() too.
Plain (crc) mode doesn't have the problem - it pulls from the socket
on demand and bails with -EIO past cursor->total_resid.
Trigger: cephfs with ms_mode=secure, sparse path taken via the
sparseread mount option or fscrypt-encrypted inodes
(fs/ceph/file.c:1079,1541). I didn't check whether rbd issues
SPARSE_READ, so no claim there.
This isn't CVE-2026-89657: that one validated the map against the
request range to avoid the BUG_ON, it doesn't cover the secure-mode
supply/consumption mismatch. Unchanged in mainline as of today.
Reject consumption beyond the received data. Guarding idx covers
both the metadata and the data copies.
Fixes: f36217e35ce1 ("libceph: support sparse reads on msgr2 secure codepath")
Signed-off-by: Dairui Zhang <zhangdairui@gmail.com>
---
First time posting to ceph-devel, and this is from code reading
rather than a live cluster - if I've misread the messenger flow or
the guard belongs somewhere else, please say so and I'll respin.
net/ceph/messenger_v2.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c
index b323b61..c677ff8 100644
--- a/net/ceph/messenger_v2.c
+++ b/net/ceph/messenger_v2.c
@@ -1077,6 +1077,10 @@ static int process_v2_sparse_read(struct ceph_connection *con,
do {
int idx = spos >> PAGE_SHIFT;
int soff = offset_in_page(spos);
+
+ if (idx >= con->v2.in_enc_page_cnt)
+ return -EREMOTEIO;
+
struct page *spage = con->v2.in_enc_pages[idx];
int len = min_t(int, ret, PAGE_SIZE - soff);
If there's interest I'll follow up with two small patches: zeroing
pages in ceph_alloc_page_vector() (closes the stale-page leak), and
a KUnit case that feeds a short tail plus a large extent map into
process_v2_sparse_read().
next reply other threads:[~2026-09-20 17:45 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-20 17:45 Dairui Zhang [this message]
2026-10-02 9:15 ` libceph: bound sparse-read consumption to received data in secure mode Alex Markuze
2026-10-02 10:47 ` [PATCH v2] " Dairui Zhang
2026-10-06 7:41 ` [v2] " Alex Markuze
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260920174525.1331992-1-zhangdairui@gmail.com \
--to=zhangdairui@gmail.com \
--cc=ceph-devel@vger.kernel.org \
--cc=idryomov@gmail.com \
--cc=security@kernel.org \
--cc=vshankar@redhat.com \
--cc=xiubli@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox