Conversation
kolyshkin
left a comment
There was a problem hiding this comment.
This is not the way to go:
runc execwill re-validate the existingconfig.jsonand fail to run (on an existing container which worked before). This is not acceptable.- Same for
runc restore.
The validation belongs to libcontainer.
Also, the CHANGELOG entry should say "Changed" not "Fixed" because this is a change in behavior.
nit: add this PR number to changelog entry.
|
@pujitha24 please do not add more commits on top of the existing one, this does not make sense. We want to see how the code/commits look like when it's merged. Feel free to force-push to your branch. |
|
That's fixed in 20b5fd8. The duplicate check is no longer in |
|
Thanks for moving the check into libcontainer. A few things still need fixing:
Benchmark codepackage rlbench
import (
"fmt"
"slices"
"testing"
)
type Rlimit struct {
Type int
Hard, Soft uint64
}
func checkMap(limits []Rlimit) error {
seen := make(map[int]struct{}, len(limits))
for _, l := range limits {
if _, ok := seen[l.Type]; ok {
return fmt.Errorf("duplicate rlimit type: %d", l.Type)
}
seen[l.Type] = struct{}{}
}
return nil
}
func checkSearch(limits []Rlimit) error {
for i, l := range limits {
if slices.ContainsFunc(limits[:i], func(r Rlimit) bool { return r.Type == l.Type }) {
return fmt.Errorf("duplicate rlimit type: %d", l.Type)
}
}
return nil
}
func checkSort(limits []Rlimit) error {
types := make([]int, len(limits))
for i, l := range limits {
types[i] = l.Type
}
slices.Sort(types)
for i := 1; i < len(types); i++ {
if types[i] == types[i-1] {
return fmt.Errorf("duplicate rlimit type: %d", types[i])
}
}
return nil
}
var sink error
func Benchmark(b *testing.B) {
for _, n := range []int{0, 1, 2, 4, 8, 16} {
l := make([]Rlimit, n)
for i := range l {
l[i].Type = n - 1 - i // unique, reverse order
}
for _, f := range []struct {
name string
fn func([]Rlimit) error
}{{"map", checkMap}, {"search", checkSearch}, {"sort", checkSort}} {
b.Run(fmt.Sprintf("%s/n=%d", f.name, n), func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
sink = f.fn(l)
}
})
}
}
} |
|
You're right, that would have broken |
|
@kolyshkin I've pushed changes addressing your review — the branch is now at |
20b5fd8 to
ff323cd
Compare
|
@pujitha24 I've asked before, and I'll ask again: please squash your commits. |
|
Looks like my other review comments (#5494 (comment)) are still not addressed. PTAL @pujitha24 |
The OCI runtime-spec (config.md) says: "If rlimits contains duplicated entries with same type, the runtime MUST generate an error." runc did not check this: a config with two RLIMIT_NOFILE entries was accepted and the last entry silently took effect. Add configs.CheckRlimits and call it from validate.Validate (for config.Rlimits, at container creation) and from Container.start for init processes only, so exec into an existing container whose config.json has duplicates (created by an older runc) keeps working. The runc CLI never sets config.Rlimits, so the Validate check does not affect restore. For runc exec, check only the --process input, as that is new data. This is a behavior change: a spec with duplicate rlimit types now fails with "duplicate rlimit type: <numeric type>" instead of starting. Report: opencontainers#5493 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
ff323cd to
b412e5b
Compare
| // checkProcessRlimits returns an error if spec has duplicate rlimit types. | ||
| func checkProcessRlimits(spec *specs.Process) error { | ||
| rlimits := make([]configs.Rlimit, 0, len(spec.Rlimits)) | ||
| for _, r := range spec.Rlimits { | ||
| rl, err := createLibContainerRlimit(r) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| rlimits = append(rlimits, rl) | ||
| } | ||
| return configs.CheckRlimits(rlimits) | ||
| } | ||
|
|
There was a problem hiding this comment.
I don't like that we convert just to validate. Lot of garbage to collect.
Can be checked right here without extra allocations:
// checkProcessRlimits returns an error if p has duplicate rlimit types.
func checkProcessRlimits(p *specs.Process) error {
for i, r := range p.Rlimits {
if slices.ContainsFunc(p.Rlimits[:i], func(x specs.POSIXRlimit) bool { return x.Type == r.Type }) {
return fmt.Errorf("duplicate rlimit type: %s", r.Type)
}
}
return nil
}| if err := configs.CheckRlimits(process.Rlimits); err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
I think this check should be done after container already has init process.
Check process rlimits for duplicates without converting them first, and do the Container.start check after the init-process-exists check. Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
|
@pujitha24 please squash your commits |
|
Both inline points are done in d95e649: On One caveat: I'm on macOS, so I could only run |
The OCI runtime-spec (config.md) says: "If rlimits contains duplicated
entries with same type, the runtime MUST generate an error." runc did not
check this: a config with two RLIMIT_NOFILE entries was accepted and the
last entry silently took effect.
Approach:
configs.CheckRlimitsreturns an error for duplicate types.validate.Validate(forconfig.Rlimits) and fromContainer.startfor init processes only, so create/run reject a specwith duplicates.
runc execinto an existing container is unaffected (a config.jsoncreated by an older runc may have duplicates).
runc exec --processis new input, so it is checked in
getProcessviacheckProcessRlimits.runc restoredoes not go throughstart(). It does go throughvalidate.Validate, but the runc CLI never setsconfig.Rlimits(specconvdoesn't), so that check is a no-op there; only libcontainer API users that
set
Config.Rlimitsare affected.Behavior change: a spec with duplicate rlimit types now fails with
duplicate rlimit type: <numeric type>instead of starting.Testing:
TestValidateRlimitsandTestCheckProcessRlimitspass onLinux arm64 (Lima VM, Go 1.26).
config.json has duplicates still works;
exec --processandrunwithduplicates fail). I could not run bats here, so these have not been run
yet.
Fixes #5493