CEPH filesystem development
 help / color / mirror / Atom feed
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().

             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