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-FMC8-P6Q7-75CC

GHSA-fmc8-p6q7-75cc: Argument Injection in Canonical LXD Backup Compression Leads to Host Root Code Execution

Amit Schendel
Amit Schendel
Senior Security Researcher

Nov 7, 2026·10 min read·0 visits

Executive Summary (TL;DR)

An argument injection flaw in Canonical LXD's backup compression handling allows authenticated users to pass arbitrary command-line flags to host compression utilities like zstd. By abusing flags such as -o, an attacker can write files from a container directly to the host's /etc/cron.d/ directory, escaping the container and executing arbitrary code as root on the host.

GHSA-fmc8-p6q7-75cc (associated with CVE-2026-48755) is a critical argument injection vulnerability in Canonical LXD (github.com/canonical/lxd). The vulnerability occurs during instance and storage volume backup operations when processing the user-supplied compression_algorithm configuration parameter. While LXD validates the primary executable token against an allowlist of supported compression binaries, it fails to sanitize or restrict subsequent tokens parsed via shellquote.Split. These additional arguments are passed directly to exec.Command. An authenticated attacker with privileges to trigger backups can inject command-line flags for utilities like zstd to read staged files from a container filesystem and write them to arbitrary paths on the underlying host filesystem, such as /etc/cron.d/, achieving root-level arbitrary code execution on the host.

Vulnerability Overview

Canonical LXD is a system container and virtual machine manager that provides a REST API for managing instances, storage volumes, and network configurations. Among its management capabilities, LXD exposes endpoints—such as /1.0/instances/{name}/backups and corresponding storage volume backup endpoints—that allow authenticated users to export compressed archives of container filesystems and virtual machine disks.

During the backup creation process, the API accepts a JSON payload containing a compression_algorithm field. This setting allows users to override the default server compression method and specify utilities such as gzip, bzip2, xz, lz4, or zstd. Because the LXD daemon (lxd) executes with root privileges on the host operating system, any external processes spawned to handle backup compression inherit this privileged execution context unless explicitly confined.

GHSA-fmc8-p6q7-75cc (tracked upstream in commit messages as CVE-2026-48755 and related to the Linux Containers Incus advisory GHSA-v6mj-8pf4-hhw4) is an argument injection vulnerability (CWE-88) in LXD's compression command parser. When validating the compression_algorithm string, LXD splits the input into shell tokens and verifies only the binary name against a static allowlist. All trailing tokens are appended directly to the os/exec.Command invocation.

By supplying compressor-specific flags, an attacker with backup permissions can alter the behavior of the invoked compression utility. Specifically, when invoking zstd, an attacker can inject flags that switch the utility into pass-through decompression mode, specify an arbitrary input file from the container's mounted rootfs on the host, and write the output to a privileged host path like /etc/cron.d/. This creates an arbitrary file write (CWE-73) primitive as root, resulting in a complete container-to-host escape and arbitrary code execution.

Root Cause Analysis

The root cause of GHSA-fmc8-p6q7-75cc resides in the disconnect between how LXD validates the compression_algorithm parameter and how it constructs the command execution slice in compressFile(). When a user submits a backup request or configures an instance compression property, the input string is tokenized using shellquote.Split(value), which parses the string according to standard POSIX shell quoting rules.

In the validation logic, LXD evaluates only the first element of the resulting slice (fields[0]). It checks whether fields[0] matches an allowlisted binary name (bzip2, gzip, lz4, lzma, pigz, pzstd, pxz, tar2sqfs, xz, zstd) and confirms the binary exists in the system PATH via exec.LookPath(fields[0]). However, the validation function does not check the length of the fields slice, nor does it inspect or reject fields[1:]. This design was originally intended to let users pass harmless compression level flags (such as gzip -9), but it lacked any restrictions on which flags could be passed.

When the backup routine calls compressFile(), the function re-parses the compress string with shellquote.Split. It initializes the argument slice with args := []string{"-c"} to instruct the compressor to write to standard output, and then appends all remaining user-provided tokens via args = append(args, fields[1:]...). The command is then executed directly on the host via exec.Command(fields[0], args...) with cmd.Stdin attached to the uncompressed backup stream and cmd.Stdout attached to the backup target file.

Because most standard UNIX compression utilities process command-line flags sequentially from left to right, later flags override earlier ones. Although LXD prepends -c (write to standard output), passing -o <path> to zstd overrides the -c flag and directs output to <path>. Furthermore, passing -- <source_file> instructs zstd to ignore standard input (cmd.Stdin) and instead read directly from <source_file> on the filesystem. Combining -d (decompress), -f (force overwrite), and --pass-through (copy uncompressed data verbatim when input is not a valid zstd frame) transforms zstd into an arbitrary file copy utility running as host root.

Code Analysis

Prior to the patch, LXD validated the compression algorithm by splitting the string and checking fields[0] against a slice of permitted binaries, while ignoring additional tokens:

// Vulnerable validation logic
fields, err := shellquote.Split(value)
if err != nil {
    return err
}
 
if len(fields) == 0 {
    return fmt.Errorf("No compression algorithm specified")
}
 
// Only fields[0] is checked; fields[1:] are ignored during validation
if !slices.Contains([]string{"bzip2", "gzip", "lz4", "lzma", "pigz", "pzstd", "pxz", "tar2sqfs", "xz", "zstd"}, fields[0]) {
    return fmt.Errorf("Compression algorithm %q isn't currently supported", fields[0])
}
 
_, err = exec.LookPath(fields[0])

In lxd/images.go, the compressFile function took the validated compress string, split it again, and passed all trailing elements directly to exec.Command without sandboxing or argument filtering. Canonical addressed this in commits such as e224d6f8230a2da7ed7e60ab2aee44c788f9dba8 and 0915bb88cbb0baed5ac1aef8e62079c57ac877da by modifying compressFile to accept the daemon's *sys.OS structure and wrapping the command execution in a dynamically generated AppArmor profile via apparmor.CompressWrapper:

-func compressFile(compress string, infile io.Reader, outfile io.Writer) error {
+func compressFile(sysOS *sys.OS, compress string, infile io.Reader, outfile io.Writer) error {
 	reproducible := []string{"gzip"}
-	var cmd *exec.Cmd
 
 	// Parse the command.
 	fields, err := shellquote.Split(compress)
...
-	cmd := exec.Command(fields[0], args...)
-	cmd.Stdin = infile
-	cmd.Stdout = outfile
-	err := cmd.Run()
-	if err != nil {
-		return err
-	}
+	cmd := exec.Command(fields[0], args...)
+	cmd.Stdin = infile
+	cmd.Stdout = outfile
+	// Wrap the compressor command in a restrictive AppArmor profile
+	cleanup, err := apparmor.CompressWrapper(sysOS, cmd, "", nil)
+	if err != nil {
+		return err
+	}
+	defer cleanup()
+	err = cmd.Run()
+	if err != nil {
+		return err
+	}

The apparmor.CompressWrapper function (defined in lxd/apparmor/archive.go) generates a temporary AppArmor profile from compressProfileTpl and rewrites cmd to execute under aa-exec -p <profileName> <command> <args...>. The template enforces strict Mandatory Access Control (MAC) rules at the kernel level:

var compressProfileTpl = template.Must(template.New("compressProfile").Parse(`#include <tunables/global>
profile "{{.name}}" {
  #include <abstractions/base>
{{range $index, $element := .allowedCommandPaths}}
  {{$element}} mixr,
{{- end }}
 
{{- if .hasDstPath }}
	{{ .dstPath }} rw,
{{- end }}
 
  signal (receive) set=("term"),
 
{{- if .snap }}
  # Snap-specific libraries
  /snap/lxd/*/lib/**.so*                  mr,
{{- end }}
}
`))

> [!WARNING] > Fix Completeness Assessment: Rather than forbidding extra arguments in fields[1:] or validating flags against a strict allowlist, Canonical's remediation relies on AppArmor confinement to block unauthorized file access. While effective on standard Ubuntu systems where AppArmor is enabled and enforcing, CompressWrapper checks if !sysOS.AppArmorAvailable and returns a no-op cleanup function if AppArmor is absent or disabled. Consequently, LXD deployments running on Linux distributions or custom kernels without active AppArmor support remain vulnerable to this argument injection attack.

Exploitation Methodology

Exploiting GHSA-fmc8-p6q7-75cc requires network access to the LXD REST API (typically port 8443/tcp) and valid authentication credentials—such as a client TLS certificate or bearer token—that grant permission to access an instance and initiate a backup. The user does not need administrative privileges over the host or access to privileged container configurations; a standard unprivileged container within an isolated project is sufficient.

The attack proceeds in five distinct stages:

  1. Payload Staging: The attacker creates a plain-text cron job file inside a target container using the LXD file upload API (POST /1.0/instances/{name}/files?path=/lxd-zstd-cron). Because zstd --pass-through only copies input verbatim when decompressed with -d, the staged file simply contains a standard /etc/cron.d/ line:

    * * * * * root /bin/sh -c 'date >/incus-zstd-rce; id >>/incus-zstd-rce'
  2. Host Path Resolution: LXD mounts container filesystems at predictable locations on the host under /var/lib/lxd/storage-pools/<pool>/containers/<project>_<instance>/rootfs/ (or /var/snap/lxd/common/lxd/storage-pools/... for Snap installations). If the instance belongs to the default project, the directory name is simply <instance>. The staged file is therefore accessible to the host daemon at /var/lib/lxd/storage-pools/default/containers/<instance>/rootfs/lxd-zstd-cron.

  3. Argument Injection via Backup API: The attacker sends a direct backup request to POST /1.0/instances/{name}/backups with Accept: application/octet-stream and the following JSON body:

    {
      "compression_algorithm": "zstd -d -f --pass-through -o /etc/cron.d/incus-zstd-rce -- /var/lib/lxd/storage-pools/default/containers/my-container/rootfs/lxd-zstd-cron",
      "instance_only": true
    }
  4. Arbitrary File Write: The LXD daemon constructs and executes zstd -c -d -f --pass-through -o /etc/cron.d/incus-zstd-rce -- <source_path>. The zstd process reads the uncompressed cron specification from the container's rootfs path on the host and writes it directly to /etc/cron.d/incus-zstd-rce with root:root ownership and mode 0644. Note that the overall backup operation may return an HTTP 400/500 error after zstd exits because nothing was written to standard output, but the host file write has already succeeded.

  5. Host Privilege Escalation: Within 60 seconds, the host's cron daemon parses /etc/cron.d/incus-zstd-rce and executes the injected shell command with full host root privileges.

Impact Assessment

The vulnerability carries a CVSS v3.1 base score of 9.9 (Critical) with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H. The critical rating reflects the Changed Scope (S:C) combined with Low Privileges Required (PR:L) and Low Attack Complexity (AC:L). An attacker who only possesses restricted API access to manage a single unprivileged container within a multi-tenant LXD deployment can completely compromise the underlying physical or virtual host.

Once arbitrary code execution as root on the host is achieved via /etc/cron.d/ injection (or alternative file write targets such as /root/.ssh/authorized_keys, /etc/ld.so.preload, or systemd unit directories), confidentiality, integrity, and availability of the entire host and all co-located containers and virtual machines are lost.

From a multi-tenancy perspective, LXD projects and RBAC policies are designed to isolate untrusted or semi-trusted users into distinct namespaces while sharing the same underlying LXD daemon and kernel. Because backup creation is a standard operational permission granted to project users, GHSA-fmc8-p6q7-75cc invalidates tenant isolation boundaries across all unpatched LXD and Incus deployments.

Remediation & Mitigation

Administrators operating Canonical LXD or Linux Containers Incus must upgrade to a patched release immediately. In the github.com/canonical/lxd repository, the fix is incorporated starting from commit pseudo-version 0.0.0-20260630083100-c5184be310b1 and has been backported across supported stable branches (lxd-5.0, lxd-5.21). For Ubuntu systems using Snap packages, refresh the LXD snap channel using snap refresh lxd.

> [!IMPORTANT] > Because Canonical's patch relies on apparmor.CompressWrapper to confine the compression binary rather than stripping command-line arguments in Go, AppArmor must be enabled and active on the host kernel. Verify that AppArmor is running and enforcing profiles by executing sudo aa-status. If AppArmor is disabled or unsupported on the host OS, the patch fails open and the system remains vulnerable.

In environments where immediate patching is not feasible, or on hosts without AppArmor support, apply the following mitigations:

  1. Restrict API and Socket Access: Ensure only fully trusted host administrators belong to the lxd UNIX group or hold client TLS certificates/tokens registered with the LXD trust store. Revoke API access for untrusted multi-tenant users.
  2. Block Dangerous Compression Arguments at a Reverse Proxy: If the LXD REST API (:8443) is exposed through an API gateway or layer-7 reverse proxy that inspects JSON bodies, reject any requests to /1.0/instances/*/backups or /1.0/storage-pools/*/volumes/*/*/backups where the compression_algorithm field contains whitespace or hyphenated flags (-o, --pass-through, -d).
  3. Host File Integrity Monitoring: Configure auditd rules to alert on any unauthorized writes to cron directories or binary preloading configurations by compressor binaries.

Official Patches

CanonicalCanonical LXD Security Advisory GHSA-fmc8-p6q7-75cc
Linux Containers (Incus)Upstream Incus Security Advisory GHSA-v6mj-8pf4-hhw4

Fix Analysis (12)

Technical Appendix

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

Affected Systems

Canonical LXD (github.com/canonical/lxd)Linux Containers Incus (github.com/lxc/incus)

Affected Versions Detail

Product
Affected Versions
Fixed Version
github.com/canonical/lxd
Canonical
< 0.0.0-20260630083100-c5184be310b10.0.0-20260630083100-c5184be310b1
AttributeDetail
Vulnerability IDGHSA-fmc8-p6q7-75cc (CVE-2026-48755)
Primary WeaknessCWE-88: Argument Injection
CVSS v3.1 Score9.9 Critical (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
Affected ComponentCanonical LXD (compressFile / backup compression parser)
Attack VectorAuthenticated REST API request (/1.0/instances/{name}/backups)
Security ImpactContainer escape, arbitrary host file write, root RCE
Exploit MaturityPublic Proof-of-Concept (PoC)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1059.004Command and Scripting Interpreter: Unix Shell
Execution
T1053.003Scheduled Task/Job: Cron
Persistence / Privilege Escalation
T1055Process Injection / Escape to Host
Privilege Escalation / Defense Evasion
T1222.002File and Directory Permissions Modification: Linux and Mac File Permissions
Defense Evasion
CWE-88
Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')

The software constructs a command line with external input intended for a single argument or binary name, but does not neutralize delimiters or auxiliary command switches, allowing an attacker to inject arguments that modify the application's intended command line execution.

Known Exploits & Detection

GitHub Advisory (Incus Upstream PoC)Publicly documented Python proof-of-concept script demonstrating remote RCE via zstd argument injection into /etc/cron.d/.
GitHub Advisory (Canonical LXD)Canonical LXD security advisory containing full reproduction steps and PoC script.

Vulnerability Timeline

Fix commits authored by Mark Laing introducing AppArmor CompressWrapper confinement across LXD branches.
2026-06-10
Patched LXD pseudo-version 0.0.0-20260630083100-c5184be310b1 committed.
2026-06-30
Security advisories GHSA-fmc8-p6q7-75cc (LXD) and GHSA-v6mj-8pf4-hhw4 (Incus) publicly disclosed.
2026-11-06

References & Sources

  • [1]Canonical LXD Security Advisory: GHSA-fmc8-p6q7-75cc
  • [2]Linux Containers Incus Security Advisory: GHSA-v6mj-8pf4-hhw4
  • [3]NVD Detail: CVE-2026-48755
  • [4]Canonical LXD Fix Commit: e224d6f8230a2da7ed7e60ab2aee44c788f9dba8
  • [5]Canonical LXD Fix Commit: 0915bb88cbb0baed5ac1aef8e62079c57ac877da
Related Vulnerabilities
CVE-2026-48755

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-PJFF-C2WC-F6JM
9.9

CVE-2026-48769: Arbitrary File Write and Remote Code Execution via Trusted Image Hash in Canonical LXD and Linux Containers Incus

A critical path traversal vulnerability caused by improper input validation (CWE-20) exists in the direct image download handler of Canonical LXD and Linux Containers Incus. When an authenticated user requests an image import using the URL source type, the daemon reads the image fingerprint from an untrusted HTTP response header (`Incus-Image-Hash` or `LXD-Image-Hash`) without validating that it conforms to a SHA-256 format. This unvalidated string is concatenated directly into a filesystem path via `filepath.Join` prior to file creation and cryptographic verification, allowing directory traversal sequences to write arbitrary files on the host filesystem as root.

Alon Barad
Alon Barad
5 views•10 min read
•about 5 hours ago•CVE-2026-9639
6.5

CVE-2026-9639: Authenticated Denial of Service via Nil-Pointer Dereference in Canonical LXD Custom Volume Backup Import

CVE-2026-9639 (GHSA-j93m-3j9p-m5m8) is a null pointer dereference vulnerability in Canonical LXD affecting versions 5.0.0 through 5.21.4 and 6.0 through 6.8. The flaw resides in the (*lxdBackend).CreateCustomVolumeFromBackup method within lxd/storage/backend_lxd.go. An authenticated attacker with the can_create_storage_volumes permission can upload a crafted custom volume backup tarball that omits the expires_at field on a snapshot entry, triggering an unhandled SIGSEGV panic and terminating the LXD daemon.

Amit Schendel
Amit Schendel
5 views•9 min read
•about 6 hours ago•CVE-2026-33050
8.8

CVE-2026-33050: OS Command Injection in GeoNode Vector Upload Handler

An operating system command injection vulnerability (CWE-78) exists in GeoNode versions prior to 5.1.0. The vulnerability occurs in the vector upload processing pipeline when handling user-supplied geospatial files. By supplying a crafted layer name containing shell metacharacters, an authenticated attacker with basic data upload privileges can execute arbitrary commands on the underlying server or container hosting the GeoNode application and Celery worker processes.

Alon Barad
Alon Barad
5 views•9 min read
•about 7 hours ago•CVE-2026-46429
5.5

CVE-2026-46429: Unrestricted File Upload and Validation Bypass in GeoNode

GeoNode versions prior to 5.0.3 contain an unrestricted file upload vulnerability (CWE-434) within the document and dataset ingestion architecture. Authenticated low-privileged users can bypass upload validation via direct REST API interactions, file extension masquerading, metadata mutation via PATCH requests, or malicious archive submissions. This allows attackers to store and serve dangerous file types, alter on-disk file references, or trigger path traversal and resource exhaustion during archive extraction.

Amit Schendel
Amit Schendel
6 views•10 min read
•about 8 hours ago•CVE-2026-55649
6.3

CVE-2026-55649: Stored Cross-Site Scripting in GeoNode Document Metadata Abstract Field

CVE-2026-55649 is a stored Cross-Site Scripting (XSS) vulnerability in GeoNode versions prior to 5.0.3. The flaw exists because document abstract metadata fields are ingested via the metadata management pipeline and stored in the backend database without HTML sanitization or input neutralization. An authenticated attacker with privileges to create or edit document metadata can inject arbitrary HTML and JavaScript payloads into these fields. When another user, such as an administrator, views the affected document page or catalog listing, the unencoded payload is rendered into the Document Object Model (DOM) and executes within the victim's browser session.

Amit Schendel
Amit Schendel
7 views•9 min read
•about 9 hours 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
6 views•11 min read