Print::printf returns an incorrect byte count (and a huge garbage value on vsnprintf error)
Summary
Print::printf stores vsnprintf's int result in a size_t and returns it. Two correctness problems follow:
- Wrong count on truncation.
vsnprintf returns the number of bytes it would have written, not what it actually wrote. When the formatted string exceeds the 256-byte stack buffer it is silently truncated, but the return value still reports the full would-be length, so callers that sum return values (to track bytes emitted) overcount.
- Garbage value on error.
vsnprintf returns a negative int on an encoding error. Assigned to size_t n, that becomes a huge value (~SIZE_MAX), which is then returned to the caller.
Not a memory-safety bug (clarification)
The emitted output is not over-read: write(buf) resolves to Print::write(const char*), which writes strlen(buf) bytes, and vsnprintf always NUL-terminates within the buffer. So this is a return-value / truncation correctness issue, not an out-of-bounds read. (Flagging this explicitly because a sibling implementation that passed the length explicitly, write(buf, n), did over-read; portduino's write(buf) form avoids that.)
Affected version
meshtastic/framework-portduino @ 32b7b2c (HEAD, 2026-05-01).
Location
cores/portduino/PortduinoPrint.cpp:8-17:
size_t Print::printf(const char *format, ...) {
char buf[MAX_STR_LEN]; // 256
va_list args;
va_start(args, format);
size_t n = vsnprintf(buf, sizeof(buf), format, args); // int -> size_t; negative becomes huge
write(buf); // writes strlen(buf), ignores n
va_end(args);
return n; // may overstate, or be ~SIZE_MAX
}
Expected
Return the number of bytes actually written; never return a value larger than the buffer, and never a wrapped-negative.
Suggested fix
Keep the vsnprintf result as a signed int, handle the error and truncation cases, and write/return the clamped count:
- size_t n = vsnprintf(buf, sizeof(buf), format, args);
- write(buf);
- va_end(args);
- return n;
+ int n = vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+ if (n <= 0) return 0;
+ // vsnprintf returns the would-be length; clamp to what actually fit.
+ size_t len = (n < (int)sizeof(buf)) ? (size_t)n : sizeof(buf) - 1;
+ return write((const uint8_t *)buf, len);
(If silent truncation of long output is itself a concern, that's worth a separate decision, heap-allocate on overflow, or document the 255-byte cap.)
Severity
Low. Output is correct up to 255 bytes; the impact is a misleading/garbage return value, which matters only for callers that rely on it.
Notes
Found while working on ArduLinux, a downstream continuation; the clamp above (verified under AddressSanitizer) is what landed there.
Print::printfreturns an incorrect byte count (and a huge garbage value onvsnprintferror)Summary
Print::printfstoresvsnprintf'sintresult in asize_tand returns it. Two correctness problems follow:vsnprintfreturns the number of bytes it would have written, not what it actually wrote. When the formatted string exceeds the 256-byte stack buffer it is silently truncated, but the return value still reports the full would-be length, so callers that sum return values (to track bytes emitted) overcount.vsnprintfreturns a negativeinton an encoding error. Assigned tosize_t n, that becomes a huge value (~SIZE_MAX), which is then returned to the caller.Not a memory-safety bug (clarification)
The emitted output is not over-read:
write(buf)resolves toPrint::write(const char*), which writesstrlen(buf)bytes, andvsnprintfalways NUL-terminates within the buffer. So this is a return-value / truncation correctness issue, not an out-of-bounds read. (Flagging this explicitly because a sibling implementation that passed the length explicitly,write(buf, n), did over-read; portduino'swrite(buf)form avoids that.)Affected version
meshtastic/framework-portduino@32b7b2c(HEAD, 2026-05-01).Location
cores/portduino/PortduinoPrint.cpp:8-17:Expected
Return the number of bytes actually written; never return a value larger than the buffer, and never a wrapped-negative.
Suggested fix
Keep the
vsnprintfresult as a signedint, handle the error and truncation cases, and write/return the clamped count:(If silent truncation of long output is itself a concern, that's worth a separate decision, heap-allocate on overflow, or document the 255-byte cap.)
Severity
Low. Output is correct up to 255 bytes; the impact is a misleading/garbage return value, which matters only for callers that rely on it.
Notes
Found while working on ArduLinux, a downstream continuation; the clamp above (verified under AddressSanitizer) is what landed there.