Dev.to Security 🔐 Cybersecurity 👁 0 📖 4 min read

CVE-2026-12411: CVE-2026-12411: Cross-Guest Volume Hijack via DevLXD Device PATCH in Canonical LXD

CVE-2026-12411: Cross-Guest Volume Hijack via DevLXD Device PATCH in Canonical LXD Vulnerability ID: CVE-2026-12411 CVSS Score: 8.4 Published: 2026-11-05 CVE-2026-12411 is a broken access control vulnerability in th

CVE-2026-12411: Cross-Guest Volume Hijack via DevLXD Device PATCH in Canonical LXD

Vulnerability ID: CVE-2026-12411
CVSS Score: 8.4
Published: 2026-11-05

CVE-2026-12411 is a broken access control vulnerability in the DevLXD interface of Canonical LXD versions 6.6 through 6.8. The flaw resides in the devLXDInstancePatchHandler component, which fails to verify storage volume ownership when an instance attaches a new disk device via the /dev/lxd guest socket. When the security.devlxd.management.volumes configuration option is enabled, an untrusted guest or DevLXD identity can issue a crafted HTTP PATCH request referencing another tenant's custom storage volume. The LXD host daemon subsequently mounts the target volume into the requesting guest, permitting unauthorized cross-tenant read and write operations.

TL;DR

An authorization bypass in Canonical LXD (versions 6.6 to 6.8) allows a guest instance with DevLXD volume management enabled to mount, read, and modify custom storage volumes belonging to other guests or identities by submitting a crafted device PATCH request over the /dev/lxd socket.

⚠️ Exploit Status: POC

Technical Details

  • CVE ID: CVE-2026-12411 (GHSA-hhf9-qw4v-72xp)
  • Weakness Class: CWE-639 (Authorization Bypass Through User-Controlled Key) / CWE-862
  • Affected Versions: Canonical LXD >= 6.6, < 6.9
  • Attack Vector: Local via /dev/lxd socket (AV:L/AC:L/PR:L/UI:N)
  • CVSS v3.1 Score: 8.4 High (Scope Changed, High Confidentiality & Integrity Impact)
  • EPSS Score: 0.29% (0.00288 - 21.48th percentile)
  • Exploit Status: Functional PoC in upstream test suite (Not in CISA KEV)
  • Patched Version: Canonical LXD 6.9

Affected Systems

  • Canonical LXD
  • LXD: >= 6.6, < 6.9 (Fixed in: 6.9)

Code Analysis

Commit: 7cb96ac

lxd/devlxd: Validate device backing resource ownership on instance patch

--- a/lxd/devlxd_instances.go
+++ b/lxd/devlxd_instances.go
@@ -14,7 +14,7 @@ import (
    "github.com/canonical/lxd/shared/entity"
 )

-type devLXDDeviceAccessValidator func(device map[string]string) bool
+type devLXDDeviceAccessValidator func(device map[string]string) error

@@ -136,7 +137,8 @@ func devLXDInstancePatchHandler(d *Daemon, r *http.Request) response.Response {

    // Evaluate instance device changes and derive instance configuration with appropriate device ownership.
    deviceChecker := newDevLXDDeviceAccessValidator(inst)
-   devices, config, err := generateDevLXDInstanceDevices(targetInst, reqInst, requestor.Username, deviceChecker)
+   deviceResourceOwnershipChecker := newDevLXDDeviceResourceOwnershipValidator(r.Context(), d, projectName)
+   devices, config, err := generateDevLXDInstanceDevices(targetInst, reqInst, requestor.Username, deviceChecker, deviceResourceOwnershipChecker)
    if err != nil {
        return response.DevLXDErrorResponse(err)
    }
@@ -175,12 +177,17 @@ func devLXDInstancePatchHandler(d *Daemon, r *http.Request) response.Response {
-func generateDevLXDInstanceDevices(inst api.Instance, req api.DevLXDInstancePut, identityID string, isDeviceAccessible devLXDDeviceAccessValidator) (devices map[string]map[string]string, config map[string]string, err error) {
+func generateDevLXDInstanceDevices(inst api.Instance, req api.DevLXDInstancePut, identityID string, isDeviceAccessible devLXDDeviceAccessValidator, isDeviceResourceOwned devLXDDeviceAccessValidator) (devices map[string]map[string]string, config map[string]string, err error) {
+   if isDeviceAccessible == nil {
+       return nil, nil, api.StatusErrorf(http.StatusInternalServerError, "Missing device access validator")
+   }
+   if isDeviceResourceOwned == nil {
+       return nil, nil, api.StatusErrorf(http.StatusInternalServerError, "Missing device resource ownership validator")
+   }
...
+           // Ensure the resource the device references is owned by devLXD identity.
+           err = isDeviceResourceOwned(device)
+           if err != nil {
+               return nil, nil, err
+           }
...
+// newDevLXDDeviceResourceOwnershipValidator returns a device validator function that checks
+// whether the resource backing a device is owned by the devLXD identity making the request.
+func newDevLXDDeviceResourceOwnershipValidator(ctx context.Context, d *Daemon, projectName string) devLXDDeviceAccessValidator {
+   return func(device map[string]string) error {
+       poolName := device["pool"]
+       volName := device["source"]
+
+       if poolName == "" || volName == "" {
+           return api.StatusErrorf(http.StatusBadRequest, "Invalid device configuration: Missing pool or volume name")
+       }
+
+       url := api.NewURL().Path("1.0", "storage-pools", poolName, "volumes", "custom", volName).Project(projectName)
+       req, err := lxd.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil, "")
+       if err != nil {
+           return api.StatusErrorf(http.StatusInternalServerError, "Failed creating request: %w", err)
+       }
+
+       req.SetPathValue("type", "custom")
+       req.SetPathValue("poolName", poolName)
+       req.SetPathValue("volumeName", volName)
+
+       err = addStoragePoolVolumeDetailsToRequestContext(d.State(), req)
+       if err != nil {
+           return err
+       }
+
+       // Retrieve volume to ensure the volume exists and is owned by the devLXD identity.
+       _, _, err = devLXDStoragePoolVolumeGet(req.Context(), d, "", projectName, poolName, volName, "custom")
+       if err != nil {
+           return err
+       }
+
+       return nil
+   }
+}

Exploit Details

Mitigation Strategies

  • Upgrade Canonical LXD to version 6.9 or later to enforce resource ownership checks on DevLXD device attachments.
  • Disable DevLXD guest volume management (security.devlxd.management.volumes) on all instances where guest self-service volume attachment is not strictly required.
  • Restrict DevLXD identity permissions by revoking can_edit on instances and storage_volume_manager on projects for untrusted guests.
  • Audit instance expanded configurations and custom volume metadata to verify that volatile..devlxd.owner matches the underlying storage volume's volatile.devlxd.owner.

Remediation Steps:

  1. Identify vulnerable LXD installations running versions between 6.6 and 6.8 inclusive using 'lxc version'.
  2. Audit running instances for the vulnerable configuration using: lxc list -c n,c -f json | jq '.[] | select(.config["security.devlxd.management.volumes"] == "true") | .name'
  3. Upgrade the LXD snap package to 6.9 or newer via: sudo snap refresh lxd --channel=latest/stable
  4. If upgrading cannot be performed immediately, unset the volume management flag on affected instances: lxc config unset security.devlxd.management.volumes
  5. Inspect existing device ownership keys on instances using: lxc config show --expanded | grep devlxd.owner

References

Read the full report for CVE-2026-12411 on our website for more details including interactive diagrams and full exploit analysis.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.