Summary
The OCI Runtime Specification defines container memory limits in bytes. When translating this value for most hypervisor backends, BytesToStringMB performs a decimal conversion (bytes / 1,000,000) instead of a binary conversion (bytes / (1024 * 1024)).
Since QEMU, Solo5, and Cloud Hypervisor interpret memory values in MiB, the resulting VM memory can exceed the original OCI byte limit.
Root cause
In pkg/unikontainers/hypervisors/utils.go:
func bytesToMB(bytes uint64) uint64 {
const bytesInMB = 1000 * 1000
return bytes / bytesInMB
}
func BytesToStringMB(argMem uint64) string {
...
userMem := bytesToMB(argMem)
...
}
BytesToStringMB is used by the QEMU, Solo5 (hvt/spt), and Cloud Hypervisor backends when constructing their memory arguments.
However, these VMMs interpret memory values as binary MiB, not decimal MB.
Backend behavior
- QEMU (verified): Passing
-m 256M results in exactly 268,435,456 bytes (256 MiB) of guest RAM.
- Solo5: Documentation indicates
--mem is interpreted in MiB.
- Cloud Hypervisor: Documentation indicates
size=<N>M is interpreted in MiB.
- Firecracker: Not affected, since
firecracker.go already uses bytesToMiB (1024 * 1024).
Reproduction
- Configure an OCI memory limit of 256,000,000 bytes.
- Launch the container using the QEMU backend.
BytesToStringMB converts the value to 256.
urunc invokes QEMU with:
- QEMU allocates 256 MiB (268,435,456 bytes) of guest RAM.
This can be verified from the guest boot memory map, where RAM ends at 0x10000000 (268,435,456 bytes).
Impact
For a configured limit of 256,000,000 bytes, the guest receives 268,435,456 bytes, approximately 12.4 MiB (~4.9%) more memory than requested.
As a result, the guest memory configured by the VMM no longer matches the OCI memory limit. Depending on how memory limits are enforced and the workload, this discrepancy may contribute to memory pressure or unexpected OOM behavior.
Suggested fix
Use binary conversion consistently for all hypervisor backends:
const bytesInMiB = 1024 * 1024
func bytesToMiB(bytes uint64) uint64 {
return bytes / bytesInMiB
}
Then update BytesToStringMB (or rename it accordingly) to use this conversion.
Related issues
Note: DefaultMemory = 256 remains correct after this change, since the supported hypervisors already interpret 256 as 256 MiB.
Environment
- urunc commit:
2785749875a1b13f543b6ad4137c68903a9ec87e
- QEMU:
8.2.2 (1:8.2.2+ds-0ubuntu1.17)
- Test image:
harbor.nbfc.io/nubificus/urunc/redis-qemu-unikraft-initrd:latest
Summary
The OCI Runtime Specification defines container memory limits in bytes. When translating this value for most hypervisor backends,
BytesToStringMBperforms a decimal conversion (bytes / 1,000,000) instead of a binary conversion (bytes / (1024 * 1024)).Since QEMU, Solo5, and Cloud Hypervisor interpret memory values in MiB, the resulting VM memory can exceed the original OCI byte limit.
Root cause
In
pkg/unikontainers/hypervisors/utils.go:BytesToStringMBis used by the QEMU, Solo5 (hvt/spt), and Cloud Hypervisor backends when constructing their memory arguments.However, these VMMs interpret memory values as binary MiB, not decimal MB.
Backend behavior
-m 256Mresults in exactly 268,435,456 bytes (256 MiB) of guest RAM.--memis interpreted in MiB.size=<N>Mis interpreted in MiB.firecracker.goalready usesbytesToMiB(1024 * 1024).Reproduction
BytesToStringMBconverts the value to256.uruncinvokes QEMU with:This can be verified from the guest boot memory map, where RAM ends at
0x10000000(268,435,456 bytes).Impact
For a configured limit of 256,000,000 bytes, the guest receives 268,435,456 bytes, approximately 12.4 MiB (~4.9%) more memory than requested.
As a result, the guest memory configured by the VMM no longer matches the OCI memory limit. Depending on how memory limits are enforced and the workload, this discrepancy may contribute to memory pressure or unexpected OOM behavior.
Suggested fix
Use binary conversion consistently for all hypervisor backends:
Then update
BytesToStringMB(or rename it accordingly) to use this conversion.Related issues
BytesToStringMBonly guards against values that truncate to zero instead of enforcing a minimum usable memory.Environment
2785749875a1b13f543b6ad4137c68903a9ec87e8.2.2(1:8.2.2+ds-0ubuntu1.17)harbor.nbfc.io/nubificus/urunc/redis-qemu-unikraft-initrd:latest