Skip to content

Project restriction `restricted.containers.privilege=isolated` bypassable by omitting `security.idmap.isolated`

Moderate
tomponline published GHSA-7vp9-3vmp-c5jm Jul 31, 2026

Package

lxd

Affected versions

>= 4.0

Patched versions

4.0.12, 5.0.8, 5.21.6, 6.10

Description

Reported from GHSA-53cg-qvg7-m8vg

Summary

Project-level enforcement of restricted.containers.privilege=isolated can be trivially bypassed, allowing a user to create a non-isolated (shared host idmap) container in a project that is configured to forbid them. The restriction only rejects an explicitly set security.idmap.isolated=false (or empty) and fails to enforce anything when the key is omitted entirely. Because an unset security.idmap.isolated defaults to false (non-isolation), a user simply leaves the key out and obtains exactly the container state the restriction is meant to forbid. This defeats the tenant-isolation guarantee the restriction exists to provide. Containers in the project share the host uid/gid map instead of receiving unique, non-overlapping ranges, weakening the isolation boundary between co-tenant containers and the host.

Details

In the restriction check for restricted.containers.privilege, a checker is registered for the security.idmap.isolated in internal/server/project/permissions.go L631-634.

The checker is applied by entityConfigChecker, which only iterates over keys that are present in the instance/profile config permissions.go L815

While the config passed in is the profile-expanded config (permissions.go L1452), it injects no defaults. Therefore, if neither the instance config nor any attached profile contains security.idmap.isolated, the key is absent from the iterated map, containerConfigChecks["security.idmap.isolated"] is never invoked, and the restriction passes.

Meanwhile, the container runtime treats the absent key as non-isolated (internal/server/instance/drivers/driver_lxc.go). The net effect: the check forbids the explicit values of false/"", but the default behavior reached by omission is identical to false.

The restriction's own error string: "Non-isolated containers are forbidden" (permissions.go L633), without a doubt demonstrates that this is not expected behavior, and is a vulnerability.

This affects every entry point that routes through checkRestrictions (instance create, instance update/patch, profile update, project update). None of them can catch the omitted-key case. It is the underlying logic that is incomplete, not any single call path.

Additionally, baking security.idmap.isolated=true into the project's default profile does not save the operator. A user who controls their profile list can create an instance with a different profile and omit the key, resulting in a non-isolated container.

PoC

1. Create a project that mandates isolated containers:
incus project create {PROJECT} -c features.profiles=true -c restricted=true -c restricted.containers.privilege=isolated
incus profile device add default root disk path=/ pool={POOL} --project {PROJECT}

2. Control: Explicit false is correctly rejected:
incus create {IMAGE_SOURCE}:{IMAGE} control --project {PROJECT} -c security.idmap.isolated=false

Output:

Error: Failed instance creation: Failed checking if instance creation allowed: Invalid value "false" for config "security.idmap.isolated" on container "control" of project "issue": Non-isolated containers are forbidden

3. Bypass: Key omission allow creation:
incus launch {IMAGE_SOURCE}:{IMAGE} bypass --project {PROJECT}
(creation will succeed)
4. Prove it is genuinely non-isolated:
incus config get bypass security.idmap.isolated --project {PROJECT}

Output:

""

incus config get bypass volatile.idmap.base --project {PROJECT}

Output:

0

volatile.idmap.base as 0 demonstrates that the container is live in the shared host map, which is exactly the non-isolated state restricted.containers.privilege=isolated is meant to forbid.

5. Compare to a properly isolated container:
incus launch {IMAGE_SOURCE}:{IMAGE} isolated --project {PROJECT} -c security.idmap.isolated=true
(creation will succeed)
incus config get isolated volatile.idmap.base --project {PROJECT}

Output in my test:

1065536

1065536 is a unique, non-overlapping range, proving security.idmap.isolated=true exhibits correct behavior.

All these steps ultimately prove the restriction was bypassed by simply forgoing the key. Creation is not the only vector attack. Any case in which the key is unset will result in a non-isolated container, whether deployed or already existing.

Impact

This is an incomplete enforcement of a project security restriction. restricted.containers.privilege=isolated fails to guarantee container idmap isolation. The insecure state (a non-isolated, shared host id map) is reachable both by users creating/editing instances and, critically, is left in place when an administrator enables the restriction on an existing project. Consequences span cross-container denial of service, integrity, and confidentiality. Even if an administrator created a project with restricted.containers.privilege=isolated with the thinking it would isolate containers in the project, they very well may have deployed unisolated containers for untrusted users to use without knowledge, simply because Incus didn't prevent them from doing so, and it's as simple as never setting the key. All operations from the administrator standpoint, succeed with no warning (whether its setting an existing project to isolated, or launching unconfigured instances with the belief they will be isolated). This is a severe false assurance of lockdown, which also means no host access or elevated privileges are required.

By far the most dangerous element of this vulnerability is what an attacker could accomplish if used:

Denial of Service:
A non-isolated container shares host uids with every other non-isolated container in the project. The kernel accounts several resources per real host uid, system-wide, so these budgets are pooled across all of them. A single tenant can exhaust a per-uid resource and deny it to all co-tenants sharing the map:

  • RLIMIT_NPROC / process exhaustion: enforced against the real host uid globally, a process flood as uid 0 (= host 1000000) prevents every other non-isolated container (whose uid 0 is also 1000000) from forking.
  • Per-user kernel object limits: fs.inotify.max_user_instances / max_user_watches, POSIX mqueue, pending-signal and related ucount budgets are keyed on the shared host uid, therefore, one container can starve the rest.

This requires no shared filesystem or other meeting point, it is an intrinsic consequence of the shared id map.

Loss of data integrity & confidentiality:
Wherever two containers' files can meet (e.g. a shared storage pool, a custom volume attached to multiple instances, a host bind-mount, backup/restore between instances), a process in one container carries the exact host uid that owns the other's files, so it passes ownership checks as that tenant. This allows both reading another tenant's data (confidentiality) and creating or modifying files owned by another tenant's uids (integrity). It also widens the blast radius, as a compromise landing as the shared mapped uid affects every container on that base, not just the one breached.

Severity

Moderate

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
Unchanged
Confidentiality
None
Integrity
None
Availability
Low

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:U/C:N/I:N/A:L

CVE ID

CVE-2026-63295

Weaknesses

No CWEs

Credits