fix buffer overflow errors#15
Conversation
There was a problem hiding this comment.
Pull request overview
Updates _convert_execution_trace_to_json_string to compute a per-write remaining buffer length (remaining_len) and use it as the size limit for several snprintf calls, aiming to prevent buffer overflows while building a JSON string representation of an execution trace.
Changes:
- Introduce
remaining_lenderived frombuffersize and the currentstr_ptrposition. - Replace several
snprintf(..., total_len, ...)calls withsnprintf(..., remaining_len, ...)to better reflect actual remaining space. - Apply the new remaining-length computation in multiple formatting paths (event header, attribute header, numeric formatting, several DPI type formats).
Comments suppressed due to low confidence (1)
src/lib/mmt_security.c:472
remaining_lenis used as thesnprintflimit, but the code later treatssizeas “bytes written” (it’s used to updatestr_ptr/total_len).snprintfreturns the number of chars that would have been written (or a negative value on error), so when truncation happenssize >= remaining_lenand subsequent pointer arithmetic can jump pastbufferand reintroduce out-of-bounds writes. Handlesnprintf’s return value explicitly (error vs truncated vs fully written) and only advancestr_ptrby the actual appended length (typicallymin(ret, remaining_len-1)).
remaining_len = sizeof(buffer) - (str_ptr - buffer) - 1;
size = snprintf( str_ptr, remaining_len, "\"event_%zu\":{\"timestamp\":%ld.%06ld,\"counter\":%"PRIu64",\"attributes\":[",
index,
time.tv_sec, //timestamp: second
time.tv_usec, //timestamp: microsecond
msg->counter );
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| size_t size, i, j, index, n, e_len; | ||
| size_t remaining_len; | ||
| int total_len; | ||
| const message_t *msg; |
There was a problem hiding this comment.
After introducing remaining_len, the function now has two independent “remaining length” concepts (remaining_len and total_len). total_len is still used later for bounds checks and for sizing other writes, but it is not kept in sync with manual pointer increments (e.g., writing separators/brackets via *(str_ptr++) = ...). This desynchronization can defeat the intended overflow fix. Consider removing total_len entirely and consistently computing remaining space from str_ptr, or ensure every manual write decrements total_len so both stay consistent.
| remaining_len = sizeof(buffer) - (str_ptr - buffer) - 1; | ||
| switch( pro_ptr->dpi_type ){ | ||
| case MMT_DATA_TIMEVAL: | ||
| u8_ptr = (uint8_t *) me->data; | ||
| ptime = (struct timeval *) me->data; |
There was a problem hiding this comment.
remaining_len is computed once for the dpi_type switch, but not all branches honor it: the IPv6 branch below still uses sprintf (no bounds), and it also leaves size unchanged if inet_ntop fails, which can make subsequent str_ptr += size use a stale value. Use a bounded write (snprintf with the current remaining length) and set size deterministically (e.g., 0 on failure) in every branch.
| remaining_len = sizeof(buffer) - (str_ptr - buffer) - 1; | ||
| size = snprintf( str_ptr, remaining_len, "%s[\"%s.%s\",", //[key, value] | ||
| (is_first? "":","), | ||
| pro_ptr->proto, | ||
| pro_ptr->att); |
There was a problem hiding this comment.
This snprintf has the same truncation/return-value issue: if remaining_len is too small, snprintf can return a value >= remaining_len, but the code immediately does str_ptr += size and total_len -= size. That can move str_ptr beyond buffer even though only remaining_len-1 bytes were actually written. Please clamp size to the actual appended length (and handle negative returns) before using it for pointer/length updates.
| remaining_len = sizeof(buffer) - (str_ptr - buffer) - 1; | ||
| size = snprintf( str_ptr, remaining_len, "%.2f", double_val ); | ||
|
|
There was a problem hiding this comment.
Same snprintf return-value handling problem here: size is derived from snprintf and later used to advance str_ptr and write the closing bracket. If formatting is truncated (or snprintf errors), size won’t equal the number of bytes actually placed in buffer, and this can still produce out-of-bounds writes. Please validate/clamp the return value before using size for pointer arithmetic.
…tion - Replace unbounded sprintf with snprintf for IPv6 address formatting - Use remaining_len (pointer-based) instead of total_len in array element loop - Add truncation clamp after attribute header snprintf to prevent str_ptr advancing past buffer - Use remaining_len in error path snprintf (avoids passing negative total_len as size_t) - Use remaining_len in _copy_plein_text call for plain-text attribute values
c44e49e to
2f7a310
Compare
- Remove total_len entirely; all bounds now use remaining_len computed from actual pointer position, eliminating stale/negative size drift - Replace first-event check (total_len != MAX_STR_SIZE) with pointer comparison (str_ptr > buffer + 1) - Add truncation clamp for double snprintf; guard zero-stripping loop against size == 0 to prevent size_t underflow wrap - Wrap MMT_DATA_IP6_ADDR case body in braces to scope ip_string[] - Add bounds guard for final closing brace write
…l write - inet_ntop failure now sets truncated=YES and reverts to event_start, preventing the malformed `["proto.att",]` JSON that size=0 produced. - Remove the temporary `*(str_ptr+1)='\0'` that was overwritten immediately by the next write, serving no purpose.
6d8fec5 to
03f6aee
Compare
luongnv89
left a comment
There was a problem hiding this comment.
Nice cleanup overall — the event-boundary rollback and latest inet_ntop failure handling make this PR look safe to merge.
One non-blocking follow-up note: numeric serialization still uses snprintf("%.2f", double_val). If double_val is ever NaN or ±Inf, the function may emit nan / inf / -inf, which are not valid JSON numbers. I don't think this should block merge for the current overflow fix, but it would be worth handling in a follow-up if those values can reach this path.
This fixes use the new variable "remaining_len" to control the real remaining length of the buffer in the function _convert_execution_trace_to_json_string
Closes #16
Decision Record
Root cause:
_convert_execution_trace_to_json_stringused a singletotal_lencounter (decremented as writes happened) rather than computing remaining capacity from the current pointer position. On event boundaries this could advancestr_ptrone byte pastbufferand produce asize_tunderflow in the nextremaining_lencomputation, passing a huge length tosnprintf.Options considered:
total_lenbut fix the off-by-one arithmetic at each boundary.total_lenwith a per-writeremaining_len = sizeof(buffer) - (str_ptr - buffer) - 1computation.Options rejected:
Selected option: Option 2 — recompute
remaining_lenfrom pointer arithmetic before everysnprintfor direct byte write. Track per-event start withevent_start; on any truncation revertstr_ptrtoevent_startand stop, guaranteeing the returned JSON is always well-formed.Residual risk:
inet_ntopfailure (unusual in practice) now safely aborts the current event rather than emitting a valueless["proto.att",]entry. The_copy_plein_textlimit is intentionally capped atremaining_len - 20to leave headroom for the surrounding JSON delimiters.Acceptance Criteria Verification
str_ptrnever advances beyond the bounds ofbuffer, including event boundariesevent_startcaptures position before each event; every advance is gated on aremaining_lencheck; truncation reverts toevent_start(mmt_security.c:459,675)size_tunderflow when space is insufficientremaining_lenrecomputed fromsizeof(buffer) - (str_ptr - buffer) - 1before each write;remaining_len == 0andsize >= remaining_lenchecks guard all paths (mmt_security.c:463–482)event_start);inet_ntopfailure now setstruncated=YESrather than writing an empty value, preventing["proto.att",]output (mmt_security.c:570–574)