Описание
LXD: Importing a crafted backup leads to project restriction bypass
Summary
LXD instance backup import validates project restrictions against backup/index.yaml embedded in the tar archive, but creates the actual instance from backup/container/backup.yaml extracted to the storage volume. Because these are separate, independently attacker-controlled files within the same tar archive, an attacker with instance-creation rights in a restricted project can craft a backup where index.yaml contains clean configuration (passing all restriction checks) while backup.yaml contains security.privileged=true, raw.lxc host filesystem mounts, and restricted device types. The instance is created from the unchecked backup.yaml, bypassing all project restriction enforcement.
Details
LXD projects support a restricted=true mode that enforces security boundaries on what instances within the project can do. These restrictions include blocking security.privileged=true containers, raw.lxc / raw.apparmor overrides, and device passthrough (GPU, USB, PCI, unix-char). These restrictions are intended to prevent container escape vectors regardless of user privilege level within the project.
The backup import path has two distinct configuration sources within a single tar archive:
backup/index.yaml- A quick-access metadata file read bybackup.GetInfo()atbackup/backup_info.go:68. This is the config checked against project restrictions.backup/container/backup.yaml- The full instance configuration extracted to the storage volume and used for actual instance creation atapi_internal.go:784.
The vulnerability exists because:
-
AllowInstanceCreation()atinstances_post.go:885validates project restrictions using onlybInfo.Configfromindex.yaml. -
The tar contents (including
backup/container/backup.yaml) are extracted to the storage volume atgeneric_vfs.go:952viaunpackVolume(). -
UpdateInstanceConfig()atbackup_config_utils.go:236readsbackup.yamlfrom storage but only syncsName,Project, pool info, and volume UUIDs - it does not overwriteInstance.ConfigorInstance.Devices. -
internalImportFromBackup()atapi_internal.go:784readsbackup.yamlfrom the storage mount path (notindex.yaml) to build the instance database record. -
instance.CreateInternal()atapi_internal.go:946creates the instance using the config frombackup.yaml.CreateInternalcallsValidConfigwhich validates config key format only, not project restriction compliance.
Proof of Concept
Environment setup (server admin)
These steps are performed by the LXD server administrator to set up the restricted project and grant access to the user. This represents the normal multi-tenant configuration that the exploit targets.
After this setup, the attacker can create normal unprivileged instances in
restricted-project but should not be able to create privileged containers,
use raw.lxc, or attach GPU/USB/unix-char devices. The exploit bypasses
all of these restrictions.
Steps
1. Create an instance backup archive locally
The attacker constructs the entire backup archive locally. No access to any LXD server is needed for this step.
2. Connect to the target LXD server and import the backup
Connect to the target LXD server and confirm restricted access:
3. Escalate to full LXD admin
Start the container and use the LXD Unix socket, which was bind-mounted
from the host via raw.lxc. Local connections over the Unix socket are
trusted as full admin with unrestricted access across all projects.
Impact
The exploit allows full host compromise from within a restricted project.
The requirement is that the user has can_view_instances, can_create_instances and can_operate_instances on the project -- standard permissions for any tenant expected to manage instances.
Possible remediation
Add a second AllowInstanceCreation (or checkInstanceRestrictions) call after backup.yaml is read from storage and before CreateInternal is called. In api_internal.go, between the ParseConfigYamlFile call (line 784) and the CreateInternal call (line 946):
Patches
Пакеты
github.com/canonical/lxd
>= 0.0.0-20210305023314-538ac3df036e, <= 0.0.0-20260226085519-736f34afb267
Отсутствует
Связанные уязвимости
In Canonical LXD before 6.8, the backup import path validates project restrictions against backup/index.yaml in the supplied tar archive but creates the instance from backup/container/backup.yaml, a separate file in the same archive that is never checked against project restrictions. An authenticated remote attacker with instance-creation permission in a restricted project can craft a backup archive where backup.yaml carries restricted settings such as security.privileged=true or raw.lxc directives, bypassing all project restriction enforcement and allowing full host compromise.
In Canonical LXD before 6.8, the backup import path validates project restrictions against backup/index.yaml in the supplied tar archive but creates the instance from backup/container/backup.yaml, a separate file in the same archive that is never checked against project restrictions. An authenticated remote attacker with instance-creation permission in a restricted project can craft a backup archive where backup.yaml carries restricted settings such as security.privileged=true or raw.lxc directives, bypassing all project restriction enforcement and allowing full host compromise.
In Canonical LXD before 6.8, the backup import path validates project ...
Уязвимость системы управления контейнерами LXD (Linux Container Daemon), связанная с недостаточной проверкой вводимых данных, позволяющая нарушителю оказать воздействие на конфиденциальность, целостность и доступность защищаемой информации