CVE-2021-32699 (published 2021-06-22T20:15Z)

Wings is the control plane software for the open source Pterodactyl game management system. All versions of Pterodactyl Wings prior to `1.4.4` are vulnerable to system resource exhaustion due to improper container process limits being defined. A malicious user can consume more resources than intended and cause downstream impacts to other clients on the same hardware, eventually causing the physical server to stop responding. Users should upgrade to `1.4.4` to mitigate the issue. There is no non-code based workaround for impacted versions of the software. Users running customized versions of this software can manually set a PID limit for containers created.

Severity: LOW
CVSS v2 base score: 2.1 (AV:L/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-770
cwe_name: Allocation of Resources Without Limits or Throttling
description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
extended_description: Code frequently has to work with limited resources, so programmers must be careful to ensure that resources are not consumed too quickly, or too easily.  Without use of quotas, resource limits, or other protection mechanisms, it can be easy for an attacker to consume many resources by rapidly making many requests, or causing larger resources to be used than is needed. When too many resources are allocated, or if a single resource is too large, then it can prevent the code from working correctly, possibly leading to a denial of service.
         

Fix information:
repo_url: https://github.com/pterodactyl/wings
commit_details: repo_url: https://github.com/pterodactyl/wings
author: Dane Everitt
author_date: 2021-06-20 16:54:00-07:00
msg: [security] enforce process limits at a per-container level to avoid abusive clients impacting other instances
repository: repo_url: https://github.com/pterodactyl/wings
repo_name: pterodactyl/wings
description: The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind.
repo_language: Go
owner: pterodactyl
file_changes: filename: config_docker.go
change_type: ModificationType.MODIFY
diff: @@ -55,6 +55,12 @@ type DockerConfiguration struct {
 	// utilizes host memory for this value, and that we do not keep track of the space used here
 	// so avoid allocating too much to a server.
 	TmpfsSize uint `default:"100" json:"tmpfs_size" yaml:"tmpfs_size"`
+
+	// ContainerPidLimit sets the total number of processes that can be active in a container
+	// at any given moment. This is a security concern in shared-hosting environments where a
+	// malicious process could create enough processes to cause the host node to run out of
+	// available pids and crash.
+	ContainerPidLimit int64 `default:"256" json:"container_pid_limit" yaml:"container_pid_limit"`
 }
 
 // RegistryConfiguration defines the authentication credentials for a given

programming_language: Go
filename: container.go
change_type: ModificationType.MODIFY
diff: @@ -486,6 +486,7 @@ func (e *Environment) convertMounts() []mount.Mount {
 
 func (e *Environment) resources() container.Resources {
 	l := e.Configuration.Limits()
+	pids := l.ProcessLimit()
 
 	return container.Resources{
 		Memory:            l.BoundedMemoryLimit(),
@@ -497,5 +498,6 @@ func (e *Environment) resources() container.Resources {
 		BlkioWeight:       l.IoWeight,
 		OomKillDisable:    &l.OOMDisabled,
 		CpusetCpus:        l.Threads,
+		PidsLimit:         &pids,
 	}
 }

programming_language: Go
filename: settings.go
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,7 @@ import (
 	"strconv"
 
 	"github.com/apex/log"
+	"github.com/pterodactyl/wings/config"
 )
 
 type Mount struct {
@@ -28,8 +29,8 @@ type Mount struct {
 	ReadOnly bool `json:"read_only"`
 }
 
-// The build settings for a given server that impact docker container creation and
-// resource limits for a server instance.
+// Limits is the build settings for a given server that impact docker container
+// creation and resource limits for a server instance.
 type Limits struct {
 	// The total amount of memory in megabytes that this server is allowed to
 	// use on the host system.
@@ -56,9 +57,9 @@ type Limits struct {
 	OOMDisabled bool `json:"oom_disabled"`
 }
 
-// Converts the CPU limit for a server build into a number that can be better understood
-// by the Docker environment. If there is no limit set, return -1 which will indicate to
-// Docker that it has unlimited CPU quota.
+// ConvertedCpuLimit converts the CPU limit for a server build into a number
+// that can be better understood by the Docker environment. If there is no limit
+// set, return -1 which will indicate to Docker that it has unlimited CPU quota.
 func (r *Limits) ConvertedCpuLimit() int64 {
 	if r.CpuLimit == 0 {
 		return -1
@@ -67,9 +68,10 @@ func (r *Limits) ConvertedCpuLimit() int64 {
 	return r.CpuLimit * 1000
 }
 
-// Set the hard limit for memory usage to be 5% more than the amount of memory assigned to
-// the server. If the memory limit for the server is < 4G, use 10%, if less than 2G use
-// 15%. This avoids unexpected crashes from processes like Java which run over the limit.
+// MemoryOverheadMultiplier sets the hard limit for memory usage to be 5% more
+// than the amount of memory assigned to the server. If the memory limit for the
+// server is < 4G, use 10%, if less than 2G use 15%. This avoids unexpected
+// crashes from processes like Java which run over the limit.
 func (r *Limits) MemoryOverheadMultiplier() float64 {
 	if r.MemoryLimit <= 2048 {
 		return 1.15
@@ -84,9 +86,9 @@ func (r *Limits) BoundedMemoryLimit() int64 {
 	return int64(math.Round(float64(r.MemoryLimit) * r.MemoryOverheadMultiplier() * 1_000_000))
 }
 
-// Returns the amount of swap available as a total in bytes. This is returned as the amount
-// of memory available to the server initially, PLUS the amount of additional swap to include
-// which is the format used by Docker.
+// ConvertedSwap returns the amount of swap available as a total in bytes. This
+// is returned as the amount of memory available to the server initially, PLUS
+// the amount of additional swap to include which is the format used by Docker.
 func (r *Limits) ConvertedSwap() int64 {
 	if r.Swap < 0 {
 		return -1
@@ -95,12 +97,19 @@ func (r *Limits) ConvertedSwap() int64 {
 	return (r.Swap * 1_000_000) + r.BoundedMemoryLimit()
 }
 
+// ProcessLimit returns the process limit for a container. This is currently
+// defined at a system level and not on a per-server basis.
+func (r *Limits) ProcessLimit() int64 {
+	return config.Get().Docker.ContainerPidLimit
+}
+
 type Variables map[string]interface{}
 
-// Ugly hacky function to handle environment variables that get passed through as not-a-string
-// from the Panel. Ideally we'd just say only pass strings, but that is a fragile idea and if a
-// string wasn't passed through you'd cause a crash or the server to become unavailable. For now
-// try to handle the most likely values from the JSON and hope for the best.
+// Get is an ugly hacky function to handle environment variables that get passed
+// through as not-a-string from the Panel. Ideally we'd just say only pass
+// strings, but that is a fragile idea and if a string wasn't passed through
+// you'd cause a crash or the server to become unavailable. For now try to
+// handle the most likely values from the JSON and hope for the best.
 func (v Variables) Get(key string) string {
 	val, ok := v[key]
 	if !ok {

programming_language: Go