[PATCH v2 1/2] libceph: validate OSD extent maps before cursor advance

From: Michael Bommarito

Date: Tue Jul 14 2026 - 07:54:44 EST


net/ceph/osd_client.c:osd_sparse_read() validates that the sparse-read
data length matches the summed extent lengths, but it does not validate
that each OSD-supplied extent is monotonic and lies inside the original
request range. A malformed authenticated OSD reply can advertise a
far-forward nonzero extent offset with a matching data length and make
the client advance the message-data cursor beyond the request buffer.
This reaches the BUG_ON(!*length) assertion in ceph_msg_data_next() from
the client receive path.

Impact: A malicious or compromised authenticated Ceph OSD peer can crash
a kernel Ceph client via a malformed sparse-read reply.

Reject sparse extent maps that overflow, move backwards, overlap, or
extend outside the original sparse-read request before advancing the
cursor.

Fixes: f628d7999727 ("libceph: add sparse read support to OSD client")
Cc: stable@xxxxxxxxxxxxxxx
Reviewed-by: Viacheslav Dubeyko <slava@xxxxxxxxxxx>
Assisted-by: Codex:gpt-5-5-xhigh
Signed-off-by: Michael Bommarito <michael.bommarito@xxxxxxxxx>
---
net/ceph/osd_client.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c
index 2ff00070c1810..76ba3abdad9b1 100644
--- a/net/ceph/osd_client.c
+++ b/net/ceph/osd_client.c
@@ -6,6 +6,7 @@
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/mm.h>
+#include <linux/overflow.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
@@ -5799,6 +5800,31 @@ static inline void convert_extent_map(struct ceph_sparse_read *sr)
}
#endif

+static bool sparse_extent_map_valid(struct ceph_sparse_read *sr)
+{
+ u64 req_end, pos;
+ int i;
+
+ if (check_add_overflow(sr->sr_req_off, sr->sr_req_len, &req_end))
+ return false;
+
+ pos = sr->sr_req_off;
+ for (i = 0; i < sr->sr_count; i++) {
+ struct ceph_sparse_extent *ext = &sr->sr_extent[i];
+ u64 end;
+
+ if (ext->off < pos)
+ return false;
+ if (check_add_overflow(ext->off, ext->len, &end))
+ return false;
+ if (end > req_end)
+ return false;
+ pos = end;
+ }
+
+ return true;
+}
+
static int osd_sparse_read(struct ceph_connection *con,
struct ceph_msg_data_cursor *cursor,
char **pbuf)
@@ -5856,6 +5882,10 @@ static int osd_sparse_read(struct ceph_connection *con,
case CEPH_SPARSE_READ_DATA_PRE:
/* Convert sr_datalen to host-endian */
sr->sr_datalen = le32_to_cpu((__force __le32)sr->sr_datalen);
+ if (!sparse_extent_map_valid(sr)) {
+ pr_warn_ratelimited("invalid sparse extent map\n");
+ return -EREMOTEIO;
+ }
for (i = 0; i < count; i++)
len += sr->sr_extent[i].len;
if (sr->sr_datalen != len) {
--
2.53.0