Skip to content

daemon: fix out-of-bounds read in dlt_daemon_control_set_log_level_v2 - #862

Closed
SoundMatt wants to merge 1 commit into
COVESA:masterfrom
SoundMatt:fix/set-log-level-v2-oob-read
Closed

daemon: fix out-of-bounds read in dlt_daemon_control_set_log_level_v2#862
SoundMatt wants to merge 1 commit into
COVESA:masterfrom
SoundMatt:fix/set-log-level-v2-oob-read

Conversation

@SoundMatt

Copy link
Copy Markdown

Problem

Same shape as #861, applied to the parallel SET_LOG_LEVEL V2 handler.

dlt_daemon_control_set_log_level_v2 validates only the 11-byte fixed
portion of the SET_LOG_LEVEL V2 request before reading two attacker-
controlled uint8_t length fields (apidlen, ctidlen) and using
them to advance a buffer offset. A short message with non-zero length
fields causes dlt_set_id_v2(), the trailing 1-byte log_level read,
and the final 4-byte memcpy() of the com field to read past the
end of msg->databuffer. Reachable from any client that can talk to
the daemon's control socket. Bounded OOB read; no OOB write.

Fix

Two bounds checks:

  • After apidlen is parsed, verify the message contains
    apidlen + 1 more bytes (apid + the upcoming ctidlen byte).
  • After ctidlen is parsed, verify the message contains
    ctidlen + 1 + DLT_ID_SIZE more bytes (ctid + log_level + com).

Unlike dlt_daemon_control_get_log_info_v2 in #861, the request struct
here lives on the stack, so no allocation cleanup is required on the
early returns.

Notes

  • Natural follow-up to daemon: fix OOB read and parse bug in dlt_daemon_control_get_log_info_v2 (closes #870) #861. Functionally independent — can be merged
    in either order.
  • I noticed but did not address a separate, independent bug in this
    function: at lines 3719/3721 the local apid/ctid pointers
    (declared NULL at 3687/3688) are passed to dlt_set_id_v2() while
    still NULL, so the call is a no-op, yet line 3723 (and later
    branches) dereferences them: apid[apid_length - 1]. This NULL-
    derefs whenever apid_length != 0. Looks like the intent was
    apid = req.apid; (pointer assignment). Filing separately since
    it's an orthogonal bug class.

Mirror of COVESA#861 for the parallel SET_LOG_LEVEL V2 control handler. The
fixed-size precheck validates only the 11-byte minimum SET_LOG_LEVEL V2
request (apidlen = ctidlen = 0). The function then reads two attacker-
controlled uint8_t length fields (apidlen, ctidlen) and uses them to
advance an offset into msg->databuffer, before passing pointers into
the buffer to dlt_set_id_v2() (which reads up to apidlen / ctidlen
bytes), a 1-byte log_level read, and a 4-byte memcpy of the trailing
com field. A short message with non-zero length fields causes the
variable-length reads to walk past the end of msg->databuffer.

Add bounds checks after each length is parsed; on failure return
without further processing (req is on the stack, no allocation cleanup
required).
SoundMatt added a commit to SoundMatt/dlt-daemon that referenced this pull request Jun 18, 2026
Address review feedback on COVESA#864: instead of hand-parsing the wire
buffer with a raw pointer assignment, copy the variable-length apid and
ctid into local fixed-size buffers through the shared dlt_set_id_v2()
helper, then point req.apid / req.ctid at those buffers.

The original handler called dlt_set_id_v2(req.apid, ...) where req.apid
is a NULL char * in the zero-initialised request struct, so the copy was
a silent no-op that left req.apid / req.ctid NULL and crashed the daemon
on the subsequent apid[apid_length - 1] dereference (COVESA#863, part of COVESA#866).

Giving dlt_set_id_v2() a real destination keeps id initialisation inside
the shared API (as requested in review) and avoids aliasing the receive
buffer. Bounds validation of apidlen/ctidlen against datasize is handled
separately in the set-log-level-v2 OOB-read fix (COVESA#862).

Add unit tests for dlt_set_id_v2 (normal copy, cap/truncation at
DLT_V2_ID_SIZE with overflow guard, and NULL/zero-length handling),
which previously had no coverage.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SoundMatt

Copy link
Copy Markdown
Author

⚠️ Merge-ordering note for maintainers: this PR and #864 both edit the same lines of dlt_daemon_control_set_log_level_v2#862 adds the apidlen/ctidlen bounds validation, #864 fixes the apid/ctid parse (now via the dlt_set_id_v2() helper). They are designed to stack, but will conflict textually if merged independently. Please merge one and rebase the other (either order is fine); the combined result is the fully-validated handler.

@minminlittleshrimp

Copy link
Copy Markdown
Collaborator

This change is improperly applied.
Fix should be handled inside the API dt_set_id logic.
Close as this issue shall be handled with #895

santhoshsivanhere pushed a commit to santhoshsivanhere/dlt-daemon that referenced this pull request Jul 13, 2026
Correct APID and CTID parsing in
dlt_daemon_control_set_log_level_v2().

The request structure stores APID and CTID as char * fields, but these
pointers were never initialized before being passed to dlt_set_id_v2().
Since the helper returns immediately when given a NULL destination, the
IDs were never parsed from the request, leaving req.apid and req.ctid
NULL.

Requests with empty APID/CTID fields silently became no-ops, while
requests containing non-empty IDs could dereference a NULL pointer in
the wildcard and field-only handling paths, crashing the daemon.

Copy the APID and CTID into local fixed-size buffers using
dlt_set_id_v2() and point req.apid and req.ctid at those buffers. This
keeps ID initialization within the shared helper, avoids aliasing the
receive buffer, and ensures the parsed IDs remain valid for the lifetime
of the function.

Also add unit tests covering dlt_set_id_v2() normal operation,
truncation at DLT_V2_ID_SIZE, overflow handling, and NULL/zero-length
inputs.

Closes: COVESA#863
Related: COVESA#862

Signed-off-by: Matt Jones
               <47545907+SoundMatt@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Santhosh Sivan Murugan
               <122221363+santhoshsivanhere@users.noreply.github.com>
santhoshsivanhere pushed a commit to santhoshsivanhere/dlt-daemon that referenced this pull request Jul 13, 2026
Correct APID and CTID parsing in
dlt_daemon_control_set_log_level_v2().

The request structure stores APID and CTID as char * fields, but these
pointers were never initialized before being passed to dlt_set_id_v2().
Since the helper returns immediately when given a NULL destination, the
IDs were never parsed from the request, leaving req.apid and req.ctid
NULL.

Requests with empty APID/CTID fields silently became no-ops, while
requests containing non-empty IDs could dereference a NULL pointer in
the wildcard and field-only handling paths, crashing the daemon.

Copy the APID and CTID into local fixed-size buffers using
dlt_set_id_v2() and point req.apid and req.ctid at those buffers. This
keeps ID initialization within the shared helper, avoids aliasing the
receive buffer, and ensures the parsed IDs remain valid for the lifetime
of the function.

Also add unit tests covering dlt_set_id_v2() normal operation,
truncation at DLT_V2_ID_SIZE, overflow handling, and NULL/zero-length
inputs.

Closes: COVESA#863
Related: COVESA#862

Signed-off-by: Matt Jones
               <47545907+SoundMatt@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Santhosh Sivan Murugan
               <122221363+santhoshsivanhere@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants