Skip to content

Arbitrary file write on host via path traversal in custom volume import

Critical
stgraber published GHSA-67qw-68v3-36h6 Jul 30, 2026

Package

gomod github.com/lxc/incus/v7/cmd/incusd (Go)

Affected versions

< v7.3.0

Patched versions

>= v7.3.0

Description

Summary

POST /1.0/storage-pools/<pool>/volumes with an application/octet-stream body (custom-volume ISO or backup import) uses an attacker-controlled name as an on-disk path segment without validation. A project user with the can_create_storage_volumes entitlement can supply X-Incus-name: ../../../../<target> (ISO) or an index.yaml name: ../../../../<target> (backup), causing the root Incus daemon to create directories, remove directory trees (os.RemoveAll), and (backup vector) unpack attacker content outside the storage pool. The validate.IsAPIName guard exists only on the JSON create branch and is never reached on the import branches.

Root Cause

In storagePoolVolumesPost (cmd/incusd/storage_volumes.go:828-833), octet-stream requests dispatch to createStoragePoolVolumeFromISO(..., r.Header.Get("X-Incus-name")) (:830) or createStoragePoolVolumeFromBackup(...) (:833) BEFORE the validate.IsAPIName(req.Name) at :845 (which is only on the JSON branch). createStoragePoolVolumeFromISO (:2697) checks only volName == "" (:2701). The name flows to pool.CreateCustomVolumeFromISO(projectName, volName, ...) -> project.StorageVolume(projectName, volName) -> GetVolume(...).MountPath() -> dir.CreateVolume EnsureMountPath os.Mkdir + reverter os.RemoveAll(volPath). VolumeDBCreate/validateVolume validate config keys only, not the name (verified - no ValidName/IsAPIName/filepath check in either).

Impact

Root-owned arbitrary directory creation and directory-tree removal (os.RemoveAll) at an attacker-chosen path (integrity/DoS). The backup import vector additionally unpacks tarball rootfs bytes, giving arbitrary-content file write as root -> host RCE.

Proof of Concept

ISO: curl -X POST --data-binary @x.iso -H 'Content-Type: application/octet-stream' -H 'X-Incus-type: iso' -H 'X-Incus-name: ../../../../etc/cron.d/pwn' https://incus/1.0/storage-pools/default/volumes (as a user with can_create_storage_volumes).

Attack Chain

  1. Entry: POST /1.0/storage-pools/<pool>/volumes, Content-Type: application/octet-stream, X-Incus-type: iso, X-Incus-name: ../../../../etc/cron.d/x -> createStoragePoolVolumeFromISO (storage_volumes.go:830). Guard: allowPermission(project, can_create_storage_volumes).
  2. Check: if volName == "" (:2701) - rejects only empty. validate.IsAPIName(req.Name) (:845) is on the JSON branch, not reached. Bypass proof: volName passes with any value including the traversal string.
  3. Sink: pool.CreateCustomVolumeFromISO(projectName, volName, ...) (backend.go) -> project.StorageVolume(proj, volName) -> GetVolume(...).MountPath() -> EnsureMountPath os.Mkdir + reverter os.RemoveAll(volPath), as root. VolumeDBCreate->validateVolume validates config keys only (verified).
  4. Impact: root arbitrary dir create + directory-tree removal at attacker path; backup vector adds arbitrary-content write (RCE).

Bypass Evidence

validate.IsAPIName present but on the wrong branch (:845, JSON only); import branches (:830/:833) bypass it; downstream validation is config-only. filepath.Join collapse verified (same as the instance backup-import path). Forced .iso suffix constrains the final filename but still permits arbitrary-directory creation/removal via ...

Affected Versions

<= 7.2.0 (vulnerable code verified present on latest release tag v7.2.0 and on HEAD 60d85b3; no fix commit on cmd/incusd/storage_volumes.go between v7.2.0 and HEAD).

Suggested Fix

Apply validate.IsAPIName(volName, false) to the X-Incus-name header and to bInfo.Name/each volume-snapshot name at handler entry, before any storage I/O.

Credit

Vulnerability independently discovered and responsibly disclosed by Zhixi "Jace" Sun (@manus-use).

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CVE ID

CVE-2026-81495

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

Relative Path Traversal

The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as .. that can resolve to a location that is outside of that directory. Learn more on MITRE.

Credits