Skip to content

Bug: Kill() calls CleanupAllUruncTaps() which destroys TAP devices of all running containers #962

Description

@ujjwalv01

Summary

While reviewing the container teardown lifecycle in pkg/unikontainers/unikontainers.go, I noticed that the Kill() method calls network.CleanupAllUruncTaps() - a function that unconditionally deletes every TAP device on the host whose name matches the pattern ^tap\d+_urunc$, regardless of which container owns it.

This means that when a single unikernel container is killed, all other concurrently running unikernel containers on the same host immediately lose their network connectivity, because their TAP devices are silently destroyed as a side effect.

Affected Code

File: pkg/unikontainers/unikontainers.go (inside Kill())

func (u *Unikontainer) Kill() error {
	err := u.joinSandboxNetNs()
	if err != nil {
		// ...
	}

	vmmType := u.State.Annotations[annotHypervisor]
	vmm, err := hypervisors.NewVMM(hypervisors.VmmType(vmmType), u.UruncCfg.Monitors)
	if err != nil {
		return err
	}
	err = vmm.Stop(u.State.Pid)
	if err != nil {
		return err
	}

	err = network.CleanupAllUruncTaps() // ← deletes ALL urunc TAP devices, not just this container's
	if err != nil {
		uniklog.Errorf("failed to cleanup tap devices: %v", err)
	}

	return nil
}

File: pkg/network/network.go (inside CleanupAllUruncTaps())

func CleanupAllUruncTaps() error {
	handle, err := netlink.NewHandle()
	// ...
	links, err := handle.LinkList()
	// ...
	tapRe := regexp.MustCompile(`^tap\d+_urunc$`) // matches every urunc TAP on the host

	for _, link := range links {
		name := attrs.Name
		if !tapRe.MatchString(name) {
			continue
		}
		// No ownership check - deletes unconditionally for every match
		deleteAllTCFilters(link)
		deleteAllQDiscs(link)
		deleteTapDevice(link)
	}
	return retErr
}

There is no association stored between a TAP device name and the container that created it. CleanupAllUruncTaps() simply destroys every interface that looks like a urunc TAP, making it fundamentally unsafe to call on individual container teardown when other containers are still running.

Impact

Silent network outage for surviving containers: If containers A, B, and C are running simultaneously with TAP devices tap0_urunc, tap1_urunc, and tap2_urunc respectively, killing container A results in all three TAP devices being deleted. Containers B and C continue to have their VMM processes running, but their network backend has been torn down silently - from the guest's perspective this looks like a physical cable unplug with no error or log on either the host or the guest side.

No warning or recovery path: Because Kill() only logs a warning if CleanupAllUruncTaps() itself errors (which it won't - deleting other containers' devices succeeds), the operator has no way to detect that surviving containers have been affected.

Reproducible at any scale: This affects any deployment running more than one networked unikernel container at a time, including Kubernetes nodes using urunc as a runtime.

Proposed Solution

Kill() should only remove the TAP device(s) belonging to the container being killed. Two viable approaches:

Option A - Store the TAP device name in monitor resources during setup.
When a TAP device is created for a container in networkSetup(), persist its name into the monitor resources file (alongside the existing block and mount data). In Kill(), load the stored name and call a scoped CleanupUruncTap(tapName string) that targets only that one device, while keeping CleanupAllUruncTaps() available for full-host recovery scenarios (e.g., daemon restart).

Option B - Derive the TAP name deterministically from the container ID.
Replace the incremental counter-based naming (tap0_urunc, tap1_urunc, ...) with a name derived from the container ID (e.g., a short hash prefix: tap_<8chars>_urunc). Kill() can then reconstruct the exact TAP name for the container being killed without needing to enumerate or match all TAP interfaces on the host.

Metadata

Metadata

Assignees

No one assigned

    Labels

    invalidThis doesn't seem right

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions