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;
}