Skip to content

libct: fix two CPU mask nits from #5343 - #5392

Open
kolyshkin wants to merge 2 commits into
opencontainers:mainfrom
kolyshkin:cpu-aff-fixes
Open

libct: fix two CPU mask nits from #5343#5392
kolyshkin wants to merge 2 commits into
opencontainers:mainfrom
kolyshkin:cpu-aff-fixes

Conversation

@kolyshkin

@kolyshkin kolyshkin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Two follow-up fixes to #5343, found while re-reading the CPU mask changes.

libct: use len() to check for empty CPU masks

When CPUAffinity.Initial/Final and LinuxMemoryPolicy.Nodes were *unix.CPUSet, a nil check was a complete "unset" test. As unix.CPUSetDynamic (a slice) it no longer is — a non-nil zero-length slice is now representable, and {"Initial":[]} in state.json unmarshals to exactly that.

Such a value passes the != nil check and reaches sched_setaffinity(2) as a NULL pointer with a zero size, which the kernel reads as an empty mask and rejects with EINVAL, instead of resetting the affinity as intended.

Only reachable via a hand-edited state.json or a libcontainer user setting the field directly, but len() is the idiomatic slice check anyway and treats nil and empty alike. Also drops the now-redundant nil guards in the memory policy validator, since Count() on a nil slice is already 0.

setupMemoryPolicy is deliberately left as is: an empty Nodes is legitimate there, as MPOL_DEFAULT/MPOL_LOCAL want a NULL/zero-size nodemask, and the validator already rejects an empty Nodes for the modes that require one.

libct: make MaxCPU exclusive

MaxCPU was documented and enforced as an inclusive bound (the highest ID ToCPUSet accepts), while unix.NewCPUSet takes an exclusive one. So tryResetCPUAffinity's NewCPUSet(MaxCPU) mask was one ID short of what ToCPUSet can parse.

Harmless in practice — sched_setaffinity(2) truncates the mask to cpumask_size(), and no kernel is configured with anywhere near 64K CPUs — but one constant meaning two different things in two files is asking for a real off-by-one later.

Making MaxCPU exclusive means NewCPUSet(MaxCPU) is correct as written and ToCPUSet's NewCPUSet(maxID+1) can never exceed it. This restores the ret >= max check and max-1 wording that predated the constant, and shifts the test boundary cases down by one.

Fixes: #5388
Closes: #5389

Since CPUAffinity.Initial/Final and LinuxMemoryPolicy.Nodes became
unix.CPUSetDynamic (a slice) rather than *unix.CPUSet (a pointer),
a nil check is no longer a complete "unset" test: a non-nil but empty
slice is now representable, and can be produced by unmarshalling a
"[]" from state.json or by a libcontainer user setting the field
directly.

Such a value used to pass the "!= nil" check and get handed to
sched_setaffinity(2) as a NULL pointer with a zero size, which the
kernel interprets as an empty mask and rejects with EINVAL, rather
than resetting the affinity as intended.

Use len() instead, which is both idiomatic for slices and treats nil
and empty alike. While at it, drop the now-redundant nil guards in the
memory policy validator (Count on a nil slice is 0).

Fixes: daf934f ("libct: use CPUSetDynamic for affinity and mempolicy masks")
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
MaxCPU was documented and enforced as the highest CPU/NUMA node ID that
ToCPUSet accepts, i.e. an inclusive bound, while unix.NewCPUSet takes an
exclusive one. As a result, tryResetCPUAffinity's NewCPUSet(MaxCPU) mask
was one ID short of what ToCPUSet can parse.

This is harmless in practice, as sched_setaffinity(2) truncates the mask
to cpumask_size() anyway, and no kernel is configured with anywhere near
64K CPUs. Still, having a single constant mean two different things in
two different files is asking for a real off-by-one later on.

Make MaxCPU exclusive to match NewCPUSet, so that NewCPUSet(MaxCPU) is
correct as written, and ToCPUSet's NewCPUSet(maxID+1) can never exceed
it. This restores the "ret >= max" check and the "max-1" wording used
before MaxCPU was introduced, and shifts the test boundary cases by one.

Fixes: daf934f ("libct: use CPUSetDynamic for affinity and mempolicy masks")
Fixes: c55649b ("libct: reuse configs.MaxCPU")
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CPU reset mask excludes configs.MaxCPU

1 participant