Repository: kubernetes
Issue: No overflow validation when using MilliValue()
Labels: kind/bug,sig/scheduling,sig/api-machinery,triage/accepted

### What happened?

There is a function called [`MilliValue()`](https://github.com/kubernetes/kubernetes/blob/b5e64567958aae5c2e5befae000d3186384c151b/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go#L817C1-L822C1) to represent values in milli units and its comment says "this could **overflow** an int64;  if that's a concern, call `Value()` first to verify the number is small enough." 
```go
// staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go
// MilliValue returns the value of ceil(q * 1000); this could overflow an int64;
// if that's a concern, call Value() first to verify the number is small enough.
func (q *Quantity) MilliValue() int64 {
	return q.ScaledValue(Milli)
}
```
But actually almost [all call to this function](https://github.com/search?q=repo%3Akubernetes%2Fkubernetes%20MilliValue&type=code) don't verify the number is small enough first.

Here are a few unexpected behaviors caused by this overflow.

### 1. Pod with extremely large cpu request is treated as with 0 cpu request
**Trigger**: create a pod using the following yaml file, with the cpu resource request is set as a very large quantity.
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod
  namespace: default
spec:
  containers:
  - name: test-container
    image: nginx
    resources:
      limits:
        cpu: 16Pi
      requests:
        cpu: 16Pi
# 16Pi = 2^4*2^50 = 2^54, 16Pi * 1000 > 2*63 > 2^63 - 1 = maxInt64, so represent this value in milli unit will cause a overflow
```

**Consequence:** The scheduler will treat this pod with 0 cpu request, so it can be scheduled to any nodes ignoring the node's cpu resource usage. 

**Cause**:  [type.go](https://github.com/kubernetes/kubernetes/blob/a28f14089cfa47ef9c57f9f283e1504a68f616d6/pkg/scheduler/framework/types.go#L854C1-L873C2)
The `noderesource` scheduler plugin uses `SetMaxResource()` to pre-calculate the pod resource request.
```go
// SetMaxResource compares with ResourceList and takes max value for each Resource.
func (r *Resource) SetMaxResource(rl v1.ResourceList) {
	if r == nil {
		return
	}

	for rName, rQuantity := range rl {
		switch rName {
		case v1.ResourceMemory:
			r.Memory = max(r.Memory, rQuantity.Value())
		case v1.ResourceCPU:
->			r.MilliCPU = max(r.MilliCPU, rQuantity.MilliValue())
		case v1.ResourceEphemeralStorage:
			r.EphemeralStorage = max(r.EphemeralStorage, rQuantity.Value())
		default:
			if schedutil.IsScalarResourceName(rName) {
				r.SetScalar(rName, max(r.ScalarResources[rName], rQuantity.Value()))
			}
		}
	}
}
```
1. In `SetMaxResource()`, calling `.MilliValue()` directly on a large `Quantity` without first validating it's small enough will lead to an overflow, causing `rQuantity.MilliValue()` to return a negative number.

2. `SetMaxResource()` iteratively compares the provided value with the existing one and retains the larger value (likely designed to handle cases that a resource is specified multiple times, retain the largest one). 
In this case, the CPU request is specified only once, and the default request value is `0`. 
Therefore, `max(0, negative_number)` returns `0`, resulting in the `noderesource` plugin considers this pod's cpu request is 0.

### 2. Pod with extremely large memory / ephemeral-storage request will trigger an unexpected warning
**Trigger**: create a pod using the following yaml file, with the ephemeral-storage resource request is a very large quantity.
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
    resources:
      requests:
        ephemeral-storage: 9Pi
      limits:
        ephemeral-storage: 9Pi

```
**Consequence:** When creating this pod, 
It's obvious here `9Pi` is an integer, but Kubernetes will output a confusing warning message: `fractional byte value "9Pi" is invalid, must be an integer.`
**Cause**:  [warning.go](https://github.com/kubernetes/kubernetes/blob/175a5b9c4690c63b7d41c3c295402269780b3a27/pkg/api/pod/warnings.go#L237C3-L248C4)
```go
if value, ok := c.Resources.Limits[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
    warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceMemory)), value.String()))
}
```
Because the quantity is very quite, calling `value.MilliValue()` will overflow and return a value that cannot be modded by 1000, triggers the warning.

### 3. Node with extremely large allocatable cpu becomes unschedulable.
**Trigger**: create a node using the following yaml file (in [KWOK](https://kwok.sigs.k8s.io/)), with the allocatable cpu resource is a very large quantity.
```yaml
apiVersion: v1
kind: Node
metadata:
  name: node-1
  namespace: default
status:
  allocatable:
    cpu: 1000Pi
  capacity:
    cpu: 1000Pi
```
**Consequence:** The node will be created but the scheduler thinks its allocatable cpu resource is negative, making this node unschedulable to any pod.

**Cause:** [types.go](https://github.com/kubernetes/kubernetes/blob/2caf4eddd8fc1ab7236ed608c1b548404dbc6bcf/pkg/scheduler/framework/types.go#L799C1-L820C2)
```go
// NewResource creates a Resource from ResourceList
func NewResource(rl v1.ResourceList) *Resource {
	r := &Resource{}
	r.Add(rl)
	return r
}

// Add adds ResourceList into Resource.
func (r *Resource) Add(rl v1.ResourceList) {
	if r == nil {
		return
	}

	for rName, rQuant := range rl {
		switch rName {
		case v1.ResourceCPU:
->			r.MilliCPU += rQuant.MilliValue()
		case v1.ResourceMemory:
			r.Memory += rQuant.Value()
		case v1.ResourcePods:
			r.AllowedPodNumber += int(rQuant.Value())
		case v1.ResourceEphemeralStorage:
			r.EphemeralStorage += rQuant.Value()
		default:
			if schedutil.IsScalarResourceName(rName) {
				r.AddScalar(rName, rQuant.Value())
			}
		}
	}
}
```
The call to `MilliValue` at the pointed line will cause an overflow, making the node's CPU resource negative. And `nodereourse` scheduler plugin will conclude this node is ineligible for any pod.


### What did you expect to happen?

Although it's unlikely that we will encounter these extremely large values in real world, there is no constraint on the range of this value. Therefore we expect no overflow happens regardless of the user's input.

1. Since most calls to `MilliValue()` do not verify that the number is small enough first, we think the simplest fix might be let `MilliValue()` or `ScaledValue()` handle overflow automatically. For example, capping the return value at maxInt64 when overflow occurs.

2. Another possible fix can be to add a range constraint of [MaxMilliValue](https://github.com/kubernetes/kubernetes/blob/847be850000a902bcd82fb4a02bada5d948595a0/staging/src/k8s.io/apimachinery/pkg/api/resource/math.go#L49) for values will be interpreted as milli unit.


### How can we reproduce it (as minimally and precisely as possible)?

Use the yaml file above.


### Anything else we need to know?

/sig scheduling api-machinery

### Kubernetes version

1.31.2

### Cloud provider

<details>

</details>


### OS version

<details>

```console
# On Linux:
$ cat /etc/os-release
# paste output here
$ uname -a
# paste output here

# On Windows:
C:\> wmic os get Caption, Version, BuildNumber, OSArchitecture
# paste output here
```

</details>


### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>