Using a value such as `--cpuset-mems=1-9223372036854775807` would cause
`dockerd` to run out of memory allocating a map of the values in the
validation code. Set limits to the normal limit of the number of CPUs,
and improve the error handling.
Reported by Huawei PSIRT.
Signed-off-by: Justin Cormack <justin.cormack@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -482,14 +482,14 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi |
| 482 | 482 |
} |
| 483 | 483 |
cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(resources.CpusetCpus) |
| 484 | 484 |
if err != nil {
|
| 485 |
- return warnings, fmt.Errorf("Invalid value %s for cpuset cpus", resources.CpusetCpus)
|
|
| 485 |
+ return warnings, errors.Wrapf(err, "Invalid value %s for cpuset cpus", resources.CpusetCpus) |
|
| 486 | 486 |
} |
| 487 | 487 |
if !cpusAvailable {
|
| 488 | 488 |
return warnings, fmt.Errorf("Requested CPUs are not available - requested %s, available: %s", resources.CpusetCpus, sysInfo.Cpus)
|
| 489 | 489 |
} |
| 490 | 490 |
memsAvailable, err := sysInfo.IsCpusetMemsAvailable(resources.CpusetMems) |
| 491 | 491 |
if err != nil {
|
| 492 |
- return warnings, fmt.Errorf("Invalid value %s for cpuset mems", resources.CpusetMems)
|
|
| 492 |
+ return warnings, errors.Wrapf(err, "Invalid value %s for cpuset mems", resources.CpusetMems) |
|
| 493 | 493 |
} |
| 494 | 494 |
if !memsAvailable {
|
| 495 | 495 |
return warnings, fmt.Errorf("Requested memory nodes are not available - requested %s, available: %s", resources.CpusetMems, sysInfo.Mems)
|
| ... | ... |
@@ -18,6 +18,24 @@ func ParseKeyValueOpt(opt string) (string, string, error) {
|
| 18 | 18 |
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil |
| 19 | 19 |
} |
| 20 | 20 |
|
| 21 |
+// ParseUintListMaximum parses and validates the specified string as the value |
|
| 22 |
+// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be |
|
| 23 |
+// one of the formats below. Note that duplicates are actually allowed in the |
|
| 24 |
+// input string. It returns a `map[int]bool` with available elements from `val` |
|
| 25 |
+// set to `true`. Values larger than `maximum` cause an error if max is non zero, |
|
| 26 |
+// in order to stop the map becoming excessively large. |
|
| 27 |
+// Supported formats: |
|
| 28 |
+// 7 |
|
| 29 |
+// 1-6 |
|
| 30 |
+// 0,3-4,7,8-10 |
|
| 31 |
+// 0-0,0,1-7 |
|
| 32 |
+// 03,1-3 <- this is gonna get parsed as [1,2,3] |
|
| 33 |
+// 3,2,1 |
|
| 34 |
+// 0-2,3,1 |
|
| 35 |
+func ParseUintListMaximum(val string, maximum int) (map[int]bool, error) {
|
|
| 36 |
+ return parseUintList(val, maximum) |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 21 | 39 |
// ParseUintList parses and validates the specified string as the value |
| 22 | 40 |
// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be |
| 23 | 41 |
// one of the formats below. Note that duplicates are actually allowed in the |
| ... | ... |
@@ -32,6 +50,10 @@ func ParseKeyValueOpt(opt string) (string, string, error) {
|
| 32 | 32 |
// 3,2,1 |
| 33 | 33 |
// 0-2,3,1 |
| 34 | 34 |
func ParseUintList(val string) (map[int]bool, error) {
|
| 35 |
+ return parseUintList(val, 0) |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+func parseUintList(val string, maximum int) (map[int]bool, error) {
|
|
| 35 | 39 |
if val == "" {
|
| 36 | 40 |
return map[int]bool{}, nil
|
| 37 | 41 |
} |
| ... | ... |
@@ -46,6 +68,9 @@ func ParseUintList(val string) (map[int]bool, error) {
|
| 46 | 46 |
if err != nil {
|
| 47 | 47 |
return nil, errInvalidFormat |
| 48 | 48 |
} |
| 49 |
+ if maximum != 0 && v > maximum {
|
|
| 50 |
+ return nil, fmt.Errorf("value of out range, maximum is %d", maximum)
|
|
| 51 |
+ } |
|
| 49 | 52 |
availableInts[v] = true |
| 50 | 53 |
} else {
|
| 51 | 54 |
split := strings.SplitN(r, "-", 2) |
| ... | ... |
@@ -60,6 +85,9 @@ func ParseUintList(val string) (map[int]bool, error) {
|
| 60 | 60 |
if max < min {
|
| 61 | 61 |
return nil, errInvalidFormat |
| 62 | 62 |
} |
| 63 |
+ if maximum != 0 && max > maximum {
|
|
| 64 |
+ return nil, fmt.Errorf("value of out range, maximum is %d", maximum)
|
|
| 65 |
+ } |
|
| 63 | 66 |
for i := min; i <= max; i++ {
|
| 64 | 67 |
availableInts[i] = true |
| 65 | 68 |
} |
| ... | ... |
@@ -68,3 +68,16 @@ func TestParseUintList(t *testing.T) {
|
| 68 | 68 |
} |
| 69 | 69 |
} |
| 70 | 70 |
} |
| 71 |
+ |
|
| 72 |
+func TestParseUintListMaximumLimits(t *testing.T) {
|
|
| 73 |
+ v := "10,1000" |
|
| 74 |
+ if _, err := ParseUintListMaximum(v, 0); err != nil {
|
|
| 75 |
+ t.Fatalf("Expected not to fail, got %v", err)
|
|
| 76 |
+ } |
|
| 77 |
+ if _, err := ParseUintListMaximum(v, 1000); err != nil {
|
|
| 78 |
+ t.Fatalf("Expected not to fail, got %v", err)
|
|
| 79 |
+ } |
|
| 80 |
+ if out, err := ParseUintListMaximum(v, 100); err == nil {
|
|
| 81 |
+ t.Fatalf("Expected failure with %s but got %v", v, out)
|
|
| 82 |
+ } |
|
| 83 |
+} |
| ... | ... |
@@ -117,11 +117,19 @@ func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) {
|
| 117 | 117 |
} |
| 118 | 118 |
|
| 119 | 119 |
func isCpusetListAvailable(provided, available string) (bool, error) {
|
| 120 |
- parsedProvided, err := parsers.ParseUintList(provided) |
|
| 120 |
+ parsedAvailable, err := parsers.ParseUintList(available) |
|
| 121 | 121 |
if err != nil {
|
| 122 | 122 |
return false, err |
| 123 | 123 |
} |
| 124 |
- parsedAvailable, err := parsers.ParseUintList(available) |
|
| 124 |
+ // 8192 is the normal maximum number of CPUs in Linux, so accept numbers up to this |
|
| 125 |
+ // or more if we actually have more CPUs. |
|
| 126 |
+ max := 8192 |
|
| 127 |
+ for m := range parsedAvailable {
|
|
| 128 |
+ if m > max {
|
|
| 129 |
+ max = m |
|
| 130 |
+ } |
|
| 131 |
+ } |
|
| 132 |
+ parsedProvided, err := parsers.ParseUintListMaximum(provided, max) |
|
| 125 | 133 |
if err != nil {
|
| 126 | 134 |
return false, err |
| 127 | 135 |
} |