Skip to content

Remove prlimit (RLIMIT_AS) address space limiting from qemu-img execution #4186

Description

@akalenyu

Is your feature request related to a problem? Please describe:

CDI currently uses prlimit(RLIMIT_AS) to limit the virtual address space of qemu-img subprocesses. The limit originates from an OpenStack Nova recommendation (1 GiB) and was intended to prevent malicious disk images from causing qemu-img to allocate arbitrary amounts of memory.

However, RLIMIT_AS limits virtual address space, not physical (RSS) memory. This is a problematic distinction that has caused real issues:

  1. Incompatibility with Go runtime — The Go runtime reserves large virtual address spaces via mmap (for GC arenas, span management, etc.) that never translate to physical memory usage. Setting RLIMIT_AS on Go programs causes sporadic crashes. This is confirmed upstream as expected behavior, not a bug. The Go team effectively considers RLIMIT_AS incompatible with Go programs. This has caused s390x unit test failures with SIGSEGV (Prlimit Unit Test fails on s390x  #3341), sporadic amd64 unit test failures with runtime: cannot allocate memory, and architecture-dependent flakiness due to varying mmap layouts.

  2. Redundant in a containerized environment — CDI runs in Kubernetes pods with container-level resource limits. The container runtime enforces memory limits via cgroups (which limit RSS/physical memory), providing a more robust and appropriate mechanism than RLIMIT_AS. The address space limit adds no meaningful protection on top of cgroup constraints.

  3. Limited practical scope — The address space limit is currently only applied to qemu-img info calls (qemu.go:222). All other qemu-img operations (convert, resize, create, rebase, commit) already pass nil limits. The CPU time limit + context timeout already protect against hung processes.

  4. The original motivation no longer applies — The 1 GiB limit was borrowed from OpenStack Nova, which runs qemu-img in a very different environment (bare-metal hypervisors without container-level isolation). In Kubernetes, pod resource limits are the standard mechanism for constraining memory.

Describe the solution you'd like:

Remove the entire prlimit syscall mechanism:

  1. Remove SetAddressSpaceLimit, SetCPUTimeLimit, and the prlimit syscall wrapper from pkg/system/prlimit.go
  2. Remove AddressSpaceLimit from ProcessLimitValues
  3. Remove maxMemory and qemuInfoLimits address space usage from pkg/image/qemu.go
  4. Remove or simplify prlimit_test.go (the memory/CPU limit test cases)
  5. Keep ExecWithLimits — it still provides value via context.WithTimeout for killing hung subprocesses (e.g. the 30s timeout on qemu-img info), plus stdout/stderr scanning and progress callbacks. SetCPUTimeLimit (RLIMIT_CPU) is unused in production code (only in tests) — the context timeout is the actual mechanism protecting against hangs.

Describe alternatives you've considered:

  • Bumping the limit to 2 GiB (architecture-dependent) — this was discussed in Prlimit Unit Test fails on s390x  #3341 but is a band-aid; the fundamental incompatibility between RLIMIT_AS and Go's virtual memory model remains, and the limit value would need ongoing tuning across architectures and Go versions.
  • Switching to RLIMIT_RSS — not reliably enforced by the Linux kernel; cgroups are the proper mechanism for RSS limiting.
  • Replacing RLIMIT_AS with a transient cgroup for physical memory limiting — instead of prlimit, we could create a child cgroup per qemu-img invocation with a memory.max limit. This can be done either via systemd-run --scope -p MemoryMax=<limit> wrapping the command, or programmatically using the containerd/cgroups Go library (create a cgroup v2 manager with memory.max set, add the child PID, clean up on exit). This would correctly limit RSS/physical memory rather than virtual address space. However, it adds complexity (cgroup filesystem permissions, cleanup, cgroup v1/v2 compat) and may be overkill given that Kubernetes pod-level resource limits already constrain the importer container's total memory.

Additional context:

Metadata

Metadata

Assignees

No one assigned

    Labels

    good-first-issueIdentifies an issue that has been specifically created or selected for first-time contributors.kind/enhancement

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions