From df306511a63f68a36180404f6a4439213eaaa35b Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Tue, 25 Aug 2026 14:55:20 -0500 Subject: [PATCH] Add sticky overrun tracking to sbuf_t for MSP variable-layout reads MSP2_INAV_SET_AUX_RC and MSP_OSD_CHAR_WRITE have runtime-determined payload layouts, so they can't use the compile-time struct pattern applied to the fixed-layout SET handlers. Both are already correctly bounded by their own dataSize checks, but that correctness depends on keeping the byte-count math in sync with the read sequence by hand - any future edit that breaks that sync would have sbufReadU8/U16/U32 walk off the end of the buffer with no safety net, since those primitives previously had no bounds check at all. Add a sticky `overrun` flag to sbuf_t: sbufReadU8 now checks the buffer bounds before dereferencing, sets the flag and returns 0 instead of reading past `end` (sbufReadU16/U32 build on sbufReadU8 and inherit the check). Once set, ptr stops advancing, so repeated calls keep returning 0 rather than continuing to walk off the buffer. sbufInit clears the flag on construction and sbufSwitchToReader clears it when a write buffer flips to read mode. Both target handlers check the flag once after their read sequence and reject the command if it tripped. This keeps every existing call site's shape unchanged (`field = sbufReadU8(src);`), avoiding a wider mechanical rewrite, while giving the two variable-layout handlers the same guarantee the fixed-layout ones already have: a malformed payload can no longer walk the read pointer past the buffer. Part of the MSP payload bounds hardening effort covering #11672. --- src/main/common/streambuf.c | 8 +++++++- src/main/common/streambuf.h | 1 + src/main/fc/fc_msp.c | 9 +++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/common/streambuf.c b/src/main/common/streambuf.c index 5a766423421..f567135ab66 100644 --- a/src/main/common/streambuf.c +++ b/src/main/common/streambuf.c @@ -24,6 +24,7 @@ sbuf_t *sbufInit(sbuf_t *sbuf, uint8_t *ptr, uint8_t *end) { sbuf->ptr = ptr; sbuf->end = end; + sbuf->overrun = false; return sbuf; } @@ -95,12 +96,16 @@ void sbufWriteStringWithZeroTerminator(sbuf_t *dst, const char *string) uint8_t sbufReadU8(sbuf_t *src) { + if (src->ptr >= src->end) { + src->overrun = true; + return 0; + } return *src->ptr++; } int8_t sbufReadI8(sbuf_t *src) { - return *src->ptr++; + return (int8_t)sbufReadU8(src); } uint16_t sbufReadU16(sbuf_t *src) @@ -216,4 +221,5 @@ void sbufSwitchToReader(sbuf_t *buf, uint8_t *base) { buf->end = buf->ptr; buf->ptr = base; + buf->overrun = false; } diff --git a/src/main/common/streambuf.h b/src/main/common/streambuf.h index a2ac1f681a6..d89bfdc524a 100644 --- a/src/main/common/streambuf.h +++ b/src/main/common/streambuf.h @@ -26,6 +26,7 @@ typedef struct sbuf_s { uint8_t *ptr; // data pointer must be first (sbuff_t* is equivalent to uint8_t **) uint8_t *end; + bool overrun; // sticky: set by an unsafe sbufRead* call that ran past end } sbuf_t; sbuf_t *sbufInit(sbuf_t *sbuf, uint8_t *ptr, uint8_t *end); diff --git a/src/main/fc/fc_msp.c b/src/main/fc/fc_msp.c index 919665397a3..95b1df0d6be 100644 --- a/src/main/fc/fc_msp.c +++ b/src/main/fc/fc_msp.c @@ -2676,6 +2676,10 @@ static mspResult_e mspFcProcessInCommand(uint16_t cmdMSP, sbuf_t *src) } } } + + if (src->overrun) { + return MSP_RESULT_ERROR; + } } break; @@ -3124,6 +3128,11 @@ static mspResult_e mspFcProcessInCommand(uint16_t cmdMSP, sbuf_t *src) for (unsigned ii = 0; ii < MIN(osdCharacterBytes, sizeof(chr.data)); ii++) { chr.data[ii] = sbufReadU8(src); } + + if (src->overrun) { + return MSP_RESULT_ERROR; + } + displayPort_t *osdDisplayPort = osdGetDisplayPort(); if (osdDisplayPort) { displayWriteFontCharacter(osdDisplayPort, addr, &chr);