Commit 364d69fcfa for frr

commit 364d69fcfa20d99716f46fb738fa3e67c2480723
Author: Riccardo Ravaioli <rravaioli@nvidia.com>
Date:   Mon Oct 26 17:03:28 2026 +0100

    lib: validate the PTM message length before reading the body

    ptm_lib_process_msg() parses msglen from the CSV header with atoi()
    into a signed int and only checks "msglen > inlen". A negative value
    passes that check and is handed to _ptm_lib_read_ptm_socket(), where
    recv() receives it as a huge size_t and will happily fill whatever the
    peer sends past the end of zebra's fixed-size in_data buffer. The
    "bytes_read != len" loop only stops once the peer pauses long enough
    to exhaust the retries, so the amount written is bounded by the peer,
    not by the buffer. msglen == inlen is also accepted, which makes the
    later "inbuf[len] = '\0'" write one byte past the buffer; the legacy
    get-status path has the same off-by-one.

    Reject msglen values that are not strictly between 0 and inlen, leave
    one byte for the NUL terminator when reading a legacy command, and
    refuse negative lengths in _ptm_lib_read_ptm_socket() itself so no
    caller can reach recv() with one. Set errno to EBADMSG on rejection:
    the stream is out of sync after a bad header, and that is what makes
    zebra_ptm_sock_read() close and reconnect instead of retrying on the
    same connection.

    Signed-off-by: Riccardo Ravaioli <rravaioli@nvidia.com>

diff --git a/lib/ptm_lib.c b/lib/ptm_lib.c
index 8d1ed0a371..9d9011794b 100644
--- a/lib/ptm_lib.c
+++ b/lib/ptm_lib.c
@@ -324,6 +324,12 @@ static int _ptm_lib_read_ptm_socket(int fd, char *buf, int len)
 	int retries = 0, rc;
 	int bytes_read = 0;

+	/* a negative length would be handed to recv() as a huge size_t */
+	if (len < 0) {
+		errno = EBADMSG;
+		return -1;
+	}
+
 	while (bytes_read != len) {
 		rc = recv(fd, (void *)(buf + bytes_read), (len - bytes_read),
 			  MSG_DONTWAIT);
@@ -385,11 +391,11 @@ int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen,
 		 * it
 		 */
 		if (len == PTMLIB_MSG_HDR_LEN) {
-			len += _ptm_lib_read_ptm_socket(
-				fd, (inbuf + PTMLIB_MSG_HDR_LEN),
-				inlen - PTMLIB_MSG_HDR_LEN);
-			if (len <= 0)
-				return (len);
+			/* keep one byte free for the NUL terminator */
+			rc = _ptm_lib_read_ptm_socket(fd, inbuf + len, inlen - len - 1);
+			if (rc < 0)
+				return rc;
+			len += rc;
 		}

 		inbuf[len] = '\0';
@@ -408,9 +414,17 @@ int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen,
 		ptm_lib_append_msg(hdl, p_ctxt, "cmd", PTMLIB_CMD_GET_STATUS);

 	} else {
-
-		if (msglen > inlen) {
-			DLOG("msglen [%d] > inlen [%d]\n", msglen, inlen);
+		/*
+		 * msglen comes straight off the wire and is the length of the
+		 * body, which is read over the header at offset 0. A negative
+		 * value would be passed to recv() as a huge size_t, zero is
+		 * an empty message, and msglen == inlen leaves no room for the
+		 * NUL terminator written below. The stream is out of sync
+		 * after a bad header, so let the caller drop the connection.
+		 */
+		if (msglen <= 0 || msglen >= inlen) {
+			ERRLOG("invalid msglen [%d], inlen [%d]\n", msglen, inlen);
+			errno = EBADMSG;
 			return -1;
 		}