Skip to content

LXC deprovision removes the firewall for a possibly-running container when lxc-info cannot be run #866

Description

What

deprovision gates the destroy on is_defined() but removes the firewall
unconditionally.

src/backends/lxc/common/src/state_aware.rs:666-676

if container.is_defined() {
    // destroy force-stops and removes the container ...
    container.destroy().map_err(..)?;
}
cleanup_network_authoritative(container_name, veth.as_deref(), &mut logger);

And is_defined cannot tell "absent" from "could not ask"
(lxc_bindings.rs:248-251):

let output = self.lxc_command("lxc-info").output();
matches!(output, Ok(o) if o.status.success())

A failure to spawn lxc-info returns Err and a non-zero exit returns
Ok(non-success). Both become false.

Why it matters

So when lxc-info cannot be run -- transient fork/exec failure, PATH problem,
resource exhaustion -- deprovision concludes the container does not exist, skips
destroy(), and then goes on to authoritatively tear down the firewall for a
container that may still be running. The result is a live container with
its egress filtering removed: a fail-open, and the exact outcome the ordering
comment immediately above is written to prevent.

The comment states the invariant plainly -- destroy first "so nothing runs
without egress filtering during teardown; if the destroy fails, leave the rules
in place." The is_defined() false negative walks around that invariant rather
than through it, because it never reaches destroy() at all.

Fix

Make the probe distinguish its two failure modes. Have is_defined return
something three-valued (present / absent / unknown), and treat unknown the way
a failed destroy is already treated: leave the firewall in place and report the
failure, rather than tearing down filtering on a guess.

This is the same fail-closed reasoning applied to the FORWARD-hook probe in
PR #849 (network_iptables.rs, commit a481f3d), but it is a signature change
on a widely called helper -- is_defined also gates provision adoption and the
start not-provisioned check -- so it wants its own review rather than a
drive-by.

Provenance

Reported by Copilot in a suppressed comment on PR #849. Mechanism verified
against source.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions