From a4f9ed000a996bfefc053b5832d64da36bba931d Mon Sep 17 00:00:00 2001 From: Michael Galaxy Date: Fri, 22 May 2026 16:27:10 -0500 Subject: [PATCH] runsc/cgroup: fix systemd cpuset byte order RangeToBits builds a cgroup v2 CPU or memory-node mask with big.Int, then passes big.Int.Bytes to systemd as AllowedCPUs/AllowedMemoryNodes. big.Int returns bytes in big-endian order, while systemd expects byte 0 to describe IDs 0-7, byte 1 to describe IDs 8-15, and so on. This reverses multi-byte masks when Docker/containerd asks runsc to create a systemd transient scope. For example, an OCI cpuset of "8,184" can be applied as CPUs "0,176". Reverse the generated byte slice before returning it and add a regression test that spans enough bytes to catch the lane reversal. Assisted-by: OpenAI Codex Signed-off-by: Michael Galaxy --- runsc/cgroup/cgroup_v2.go | 9 +++++++++ runsc/cgroup/systemd_test.go | 24 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/runsc/cgroup/cgroup_v2.go b/runsc/cgroup/cgroup_v2.go index 117dea0024..fd0063ac70 100644 --- a/runsc/cgroup/cgroup_v2.go +++ b/runsc/cgroup/cgroup_v2.go @@ -971,5 +971,14 @@ func RangeToBits(str string) ([]byte, error) { // do not allow empty values return nil, errors.New("empty value") } + + // systemd's AllowedCPUs/AllowedMemoryNodes properties use little-endian byte + // ordering: byte 0 describes IDs 0-7, byte 1 describes IDs 8-15, and so on. + // big.Int.Bytes returns a minimal big-endian representation, which shifts + // multi-byte masks to the wrong CPU/node IDs. Reverse the bytes so the D-Bus + // property matches systemd's expected mask layout. + for l, r := 0, len(ret)-1; l < r; l, r = l+1, r-1 { + ret[l], ret[r] = ret[r], ret[l] + } return ret, nil } diff --git a/runsc/cgroup/systemd_test.go b/runsc/cgroup/systemd_test.go index 2d0f3573a4..b931d3ec08 100644 --- a/runsc/cgroup/systemd_test.go +++ b/runsc/cgroup/systemd_test.go @@ -197,7 +197,29 @@ func TestInstall(t *testing.T) { }, wantProps: []systemdDbus.Property{ {"AllowedCPUs", dbus.MakeVariant([]byte{0b_101110})}, - {"AllowedMemoryNodes", dbus.MakeVariant([]byte{1, 0b_11100000})}, + {"AllowedMemoryNodes", dbus.MakeVariant([]byte{0b_11100000, 1})}, + }, + }, + { + name: "cpuset high cpu", + res: &specs.LinuxResources{ + CPU: &specs.LinuxCPU{ + // This reproduces the Docker/containerd/runsc failure mode + // where the OCI bundle asks for sibling CPUs 8 and 184 but + // the systemd scope was previously assigned CPUs 0 and 176. + // The pair intentionally spans many bytes so the test fails + // if RangeToBits returns big-endian bytes instead of the byte + // order expected by systemd's AllowedCPUs property. + Cpus: "8,184", + }, + }, + wantProps: []systemdDbus.Property{ + {"AllowedCPUs", dbus.MakeVariant(func() []byte { + bits := make([]byte, 24) + bits[1] = 1 + bits[23] = 1 + return bits + }())}, }, }, {