From fc4d944dbaee92ab8d660e81e1469b4bb79f47f3 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Sun, 8 Mar 2026 20:01:27 +0100 Subject: libceph: reject preamble if control segment is empty commit c4c22b846eceff05b1129b8844a80310e55a7f87 upstream. While head_onwire_len() has a branch to handle ctrl_len == 0 case, prepare_read_control() always sets up a kvec for the CRC meaning that a non-empty control segment is effectively assumed. All frames that clients deal with meet that assumption, so let's make it official and treat the preamble with an empty control segment as malformed. Cc: stable@vger.kernel.org Signed-off-by: Ilya Dryomov Reviewed-by: Alex Markuze Signed-off-by: Greg Kroah-Hartman --- net/ceph/messenger_v2.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'net/ceph/messenger_v2.c') diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c index b67f2b582bc7..9eabc57ccde4 100644 --- a/net/ceph/messenger_v2.c +++ b/net/ceph/messenger_v2.c @@ -392,7 +392,7 @@ static int head_onwire_len(int ctrl_len, bool secure) int head_len; int rem_len; - BUG_ON(ctrl_len < 0 || ctrl_len > CEPH_MSG_MAX_CONTROL_LEN); + BUG_ON(ctrl_len < 1 || ctrl_len > CEPH_MSG_MAX_CONTROL_LEN); if (secure) { head_len = CEPH_PREAMBLE_SECURE_LEN; @@ -401,9 +401,7 @@ static int head_onwire_len(int ctrl_len, bool secure) head_len += padded_len(rem_len) + CEPH_GCM_TAG_LEN; } } else { - head_len = CEPH_PREAMBLE_PLAIN_LEN; - if (ctrl_len) - head_len += ctrl_len + CEPH_CRC_LEN; + head_len = CEPH_PREAMBLE_PLAIN_LEN + ctrl_len + CEPH_CRC_LEN; } return head_len; } @@ -528,11 +526,16 @@ static int decode_preamble(void *p, struct ceph_frame_desc *desc) desc->fd_aligns[i] = ceph_decode_16(&p); } - if (desc->fd_lens[0] < 0 || + /* + * This would fire for FRAME_TAG_WAIT (it has one empty + * segment), but we should never get it as client. + */ + if (desc->fd_lens[0] < 1 || desc->fd_lens[0] > CEPH_MSG_MAX_CONTROL_LEN) { pr_err("bad control segment length %d\n", desc->fd_lens[0]); return -EINVAL; } + if (desc->fd_lens[1] < 0 || desc->fd_lens[1] > CEPH_MSG_MAX_FRONT_LEN) { pr_err("bad front segment length %d\n", desc->fd_lens[1]); @@ -549,10 +552,6 @@ static int decode_preamble(void *p, struct ceph_frame_desc *desc) return -EINVAL; } - /* - * This would fire for FRAME_TAG_WAIT (it has one empty - * segment), but we should never get it as client. - */ if (!desc->fd_lens[desc->fd_seg_cnt - 1]) { pr_err("last segment empty, segment count %d\n", desc->fd_seg_cnt); -- cgit v1.2.3 From 69fe5af33fa3806f398d21c081d73c66e5523bc2 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Sun, 8 Mar 2026 17:38:00 +0100 Subject: libceph: prevent potential out-of-bounds reads in process_message_header() commit 69fb5d91bba44ecf7eb80530b85fa4fb028921d5 upstream. If the message frame is (maliciously) corrupted in a way that the length of the control segment ends up being less than the size of the message header or a different frame is made to look like a message frame, out-of-bounds reads may ensue in process_message_header(). Perform an explicit bounds check before decoding the message header. Cc: stable@vger.kernel.org Reported-by: Raphael Zimmer Signed-off-by: Ilya Dryomov Reviewed-by: Alex Markuze Reviewed-by: Viacheslav Dubeyko Signed-off-by: Greg Kroah-Hartman --- net/ceph/messenger_v2.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'net/ceph/messenger_v2.c') diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c index 9eabc57ccde4..1fc50a270158 100644 --- a/net/ceph/messenger_v2.c +++ b/net/ceph/messenger_v2.c @@ -2834,12 +2834,15 @@ static int process_message_header(struct ceph_connection *con, void *p, void *end) { struct ceph_frame_desc *desc = &con->v2.in_desc; - struct ceph_msg_header2 *hdr2 = p; + struct ceph_msg_header2 *hdr2; struct ceph_msg_header hdr; int skip; int ret; u64 seq; + ceph_decode_need(&p, end, sizeof(*hdr2), bad); + hdr2 = p; + /* verify seq# */ seq = le64_to_cpu(hdr2->seq); if ((s64)seq - (s64)con->in_seq < 1) { @@ -2870,6 +2873,10 @@ static int process_message_header(struct ceph_connection *con, WARN_ON(!con->in_msg); WARN_ON(con->in_msg->con != con); return 1; + +bad: + pr_err("failed to decode message header\n"); + return -EINVAL; } static int process_message(struct ceph_connection *con) -- cgit v1.2.3 From bdf614c81081b6c23bf271fa2bcc3e501ca27348 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Sun, 8 Mar 2026 17:57:23 +0100 Subject: libceph: admit message frames only in CEPH_CON_S_OPEN state commit a5a373705081d7cc6363e16990e2361b0b362314 upstream. Similar checks are performed for all control frames, but an early check for message frames was missing. process_message() is already set up to terminate the loop in case the state changes while con->ops->dispatch() handler is being executed. Cc: stable@vger.kernel.org Signed-off-by: Ilya Dryomov Reviewed-by: Alex Markuze Reviewed-by: Viacheslav Dubeyko Signed-off-by: Greg Kroah-Hartman --- net/ceph/messenger_v2.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'net/ceph/messenger_v2.c') diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c index 1fc50a270158..5c7435fc6483 100644 --- a/net/ceph/messenger_v2.c +++ b/net/ceph/messenger_v2.c @@ -2906,6 +2906,11 @@ static int __handle_control(struct ceph_connection *con, void *p) if (con->v2.in_desc.fd_tag != FRAME_TAG_MESSAGE) return process_control(con, p, end); + if (con->state != CEPH_CON_S_OPEN) { + con->error_msg = "protocol error, unexpected message"; + return -EINVAL; + } + ret = process_message_header(con, p, end); if (ret < 0) return ret; -- cgit v1.2.3