Skip to content

Commit 0f0a97e

Browse files
authored
[CHAOSPLT-1254] Jpn/fix cpuset (#998)
* add cpuset_test.go * fix very large inputs causing crash * fix copyright on cpuset_test to match the DD copyright entries * add back in the default header so circle does not fail the header check * is this correct? * think this worked * update cpuset.go * switch to get gocritic to be happy * add cpu limit to doc
1 parent f60eae7 commit 0f0a97e

File tree

3 files changed

+97
-4
lines changed

3 files changed

+97
-4
lines changed

cpuset/cpuset.go

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2025 Datadog, Inc.
5+
16
/*
27
Copyright 2017 The Kubernetes Authors.
3-
48
Licensed under the Apache License, Version 2.0 (the "License");
59
you may not use this file except in compliance with the License.
610
You may obtain a copy of the License at
@@ -278,7 +282,13 @@ func MustParse(s string) CPUSet {
278282
// Parse CPUSet constructs a new CPU set from a Linux CPU list formatted string.
279283
//
280284
// See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
285+
281286
func Parse(s string) (CPUSet, error) {
287+
// Maximum reasonable CPU ID (current systems have far fewer CPUs than this)
288+
const maxCPUID = 8192
289+
// Maximum reasonable range size to prevent memory exhaustion
290+
const maxRangeSize = 8192
291+
282292
b := NewBuilder()
283293

284294
// Handle empty string.
@@ -291,31 +301,83 @@ func Parse(s string) (CPUSet, error) {
291301
ranges := strings.Split(s, ",")
292302

293303
for _, r := range ranges {
304+
// Validate that the range is not empty
305+
if r == "" {
306+
return NewCPUSet(), fmt.Errorf("empty range in CPU list %q", s)
307+
}
308+
294309
boundaries := strings.Split(r, "-")
295-
if len(boundaries) == 1 {
310+
311+
switch len(boundaries) {
312+
case 1:
296313
// Handle ranges that consist of only one element like "34".
297314
elem, err := strconv.Atoi(boundaries[0])
298315
if err != nil {
299316
return NewCPUSet(), err
300317
}
301318

319+
if elem < 0 {
320+
return NewCPUSet(), fmt.Errorf("invalid CPU number: %d (must be non-negative)", elem)
321+
}
322+
323+
if elem >= maxCPUID {
324+
return NewCPUSet(), fmt.Errorf("invalid CPU number: %d (must be less than %d)", elem, maxCPUID)
325+
}
326+
302327
b.Add(elem)
303-
} else if len(boundaries) == 2 {
328+
case 2:
304329
// Handle multi-element ranges like "0-5".
330+
// Check for empty boundaries (e.g., "-5", "5-", "--5")
331+
if boundaries[0] == "" || boundaries[1] == "" {
332+
return NewCPUSet(), fmt.Errorf("invalid range format in %q", r)
333+
}
334+
305335
start, err := strconv.Atoi(boundaries[0])
306336
if err != nil {
307337
return NewCPUSet(), err
308338
}
309339

340+
if start < 0 {
341+
return NewCPUSet(), fmt.Errorf("invalid start CPU number: %d (must be non-negative)", start)
342+
}
343+
344+
if start >= maxCPUID {
345+
return NewCPUSet(), fmt.Errorf("invalid start CPU number: %d (must be less than %d)", start, maxCPUID)
346+
}
347+
310348
end, err := strconv.Atoi(boundaries[1])
311349
if err != nil {
312350
return NewCPUSet(), err
313351
}
352+
353+
if end < 0 {
354+
return NewCPUSet(), fmt.Errorf("invalid end CPU number: %d (must be non-negative)", end)
355+
}
356+
357+
if end >= maxCPUID {
358+
return NewCPUSet(), fmt.Errorf("invalid end CPU number: %d (must be less than %d)", end, maxCPUID)
359+
}
360+
361+
// Validate that start <= end to avoid infinite loop
362+
if start > end {
363+
return NewCPUSet(), fmt.Errorf("invalid range %q: start (%d) must be less than or equal to end (%d)", r, start, end)
364+
}
365+
366+
// Validate range size to prevent memory exhaustion
367+
rangeSize := end - start + 1
368+
369+
if rangeSize > maxRangeSize {
370+
return NewCPUSet(), fmt.Errorf("invalid range %q: range size (%d) exceeds maximum allowed (%d)", r, rangeSize, maxRangeSize)
371+
}
372+
314373
// Add all elements to the result.
315374
// e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48].
316375
for e := start; e <= end; e++ {
317376
b.Add(e)
318377
}
378+
default:
379+
// More than 2 boundaries means multiple dashes (e.g., "1-2-3")
380+
return NewCPUSet(), fmt.Errorf("invalid range format %q: too many dashes", r)
319381
}
320382
}
321383

cpuset/cpuset_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2025 Datadog, Inc.
5+
6+
package cpuset
7+
8+
import (
9+
"testing"
10+
)
11+
12+
func FuzzParse(f *testing.F) {
13+
f.Add("0-3")
14+
f.Add("1,2,3")
15+
f.Add("0")
16+
f.Fuzz(func(t *testing.T, input string) {
17+
// Parse should either succeed or return an error, but never panic
18+
cpuset, err := Parse(input)
19+
20+
// If parsing succeeded, verify basic invariants
21+
if err == nil {
22+
// Empty string should produce empty set
23+
if input == "" && cpuset.Size() != 0 {
24+
t.Errorf("Parse(%q) produced non-empty set: %v", input, cpuset)
25+
}
26+
// Non-empty valid input should produce non-empty set
27+
// (though we can't easily determine if input is "valid" without re-parsing)
28+
}
29+
// If err != nil, that's fine - invalid input should return errors
30+
})
31+
}

docs/cpu_pressure.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ When the injector pod starts:
3838

3939
> NB: stressing 100% of the allocated cpuset DOES NOT MEAN stressing 100% of all cores allocated if the defined CPU is below 1 in Kubernetes (e.g. `100m`)
4040
> NB2: container being part of the same pods can have similar core associated, however we still need to stress each of them like if they were alone, linux CPU scheduler is the one that will throttling us appropriately
41-
41+
> NB3: there is a maximum CPU limit of 8192.
4242
<p align="center"><kbd>
4343
<img src="img/cpu/cgroup_disrupted.png" width=500 align="center" />
4444
</kbd></p>

0 commit comments

Comments
 (0)