diff --git a/pkg/device/nvidia/device.go b/pkg/device/nvidia/device.go index 20e3bec1b6..78aa6585d5 100644 --- a/pkg/device/nvidia/device.go +++ b/pkg/device/nvidia/device.go @@ -20,6 +20,7 @@ import ( "errors" "flag" "fmt" + "math" "sort" "strconv" "strings" @@ -521,17 +522,38 @@ func (dev *NvidiaGPUDevices) GenerateResourceRequests(ctr *corev1.Container) dev } if ok { if n, ok := v.AsInt64(); ok { + if n <= 0 || n > math.MaxInt32 { + klog.ErrorS(nil, "nvidia device count request is out of range", "container", ctr.Name, "request", n) + return device.ContainerDeviceRequest{} + } memnum := 0 mem, ok := ctr.Resources.Limits[resourceMem] if !ok { mem, ok = ctr.Resources.Requests[resourceMem] } if ok { + // Negative quantities such as -1m return ok=false from AsInt64, so reject by sign first. + if mem.Sign() < 0 { + klog.ErrorS(nil, "nvidia device memory request is negative", "container", ctr.Name, "request", mem.String()) + return device.ContainerDeviceRequest{} + } memnums, ok := mem.AsInt64() if ok { + // nvidia memory is in MB, so an over-int32 value such as a byte quantity 16Gi is a wrong-unit mistake. + if memnums > math.MaxInt32 { + klog.ErrorS(nil, "nvidia device memory request is out of range; memory unit is treated as MB not Byte, so a quantity such as 16Gi is invalid, request 16384 for 16GB instead", + "container", ctr.Name, "request", mem.String()) + return device.ContainerDeviceRequest{} + } if dev.config.MemoryFactor > 1 { rawMemnums := memnums + // memnums is bounded by math.MaxInt32 and MemoryFactor is int32, so this product cannot overflow int64. memnums = memnums * int64(dev.config.MemoryFactor) + if memnums > math.MaxInt32 { + klog.ErrorS(nil, "nvidia device memory request overflows int32 after applying memory factor", + "container", ctr.Name, "raw", rawMemnums, "scaled", memnums, "factor", dev.config.MemoryFactor) + return device.ContainerDeviceRequest{} + } klog.V(4).Infof("Update memory request. before %d, after %d, factor %d", rawMemnums, memnums, dev.config.MemoryFactor) } memnum = int(memnums) @@ -573,6 +595,10 @@ func (dev *NvidiaGPUDevices) GenerateResourceRequests(ctr *corev1.Container) dev if ok { corenums, ok := core.AsInt64() if ok { + if corenums < 0 || corenums > math.MaxInt32 { + klog.ErrorS(nil, "nvidia device core request is out of range", "container", ctr.Name, "request", core.String()) + return device.ContainerDeviceRequest{} + } corenum = int32(corenums) } } diff --git a/pkg/device/nvidia/device_test.go b/pkg/device/nvidia/device_test.go index 9483962c0c..a0cbdbe6fd 100644 --- a/pkg/device/nvidia/device_test.go +++ b/pkg/device/nvidia/device_test.go @@ -1915,6 +1915,128 @@ func TestGenerateResourceRequests_MemoryFactor(t *testing.T) { assert.Equal(t, result.Memreq, int32(2048)) } +// Test_GenerateResourceRequests_OutOfRangeValues checks that out-of-range values are rejected, not silently wrapped. +func Test_GenerateResourceRequests_OutOfRangeValues(t *testing.T) { + config := NvidiaConfig{ + ResourceCountName: "nvidia.com/gpu", + ResourceMemoryName: "nvidia.com/gpumem", + ResourceCoreName: "nvidia.com/gpucores", + ResourceMemoryPercentageName: "nvidia.com/gpumem-percentage", + MemoryFactor: 1, + } + dev := InitNvidiaDevice(config) + + tests := []struct { + name string + ctr *corev1.Container + want device.ContainerDeviceRequest + }{ + { + // 16Gi in bytes wraps to 0 when narrowed to int32; nvidia memory is counted in MB. + name: "memory requested in bytes exceeds int32 range", + ctr: &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "nvidia.com/gpu": *resource.NewQuantity(1, resource.BinarySI), + "nvidia.com/gpumem": resource.MustParse("16Gi"), + }, + }, + }, + want: device.ContainerDeviceRequest{}, + }, + { + // -1m makes AsInt64 return ok=false, so it must be rejected by sign, not defaulted. + name: "negative fractional memory request", + ctr: &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "nvidia.com/gpu": *resource.NewQuantity(1, resource.BinarySI), + "nvidia.com/gpumem": resource.MustParse("-1m"), + }, + }, + }, + want: device.ContainerDeviceRequest{}, + }, + { + // A plain whole -100 passes AsInt64 (ok=true), so it must be rejected by sign before the int32 narrowing. + name: "negative whole memory request", + ctr: &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "nvidia.com/gpu": *resource.NewQuantity(1, resource.BinarySI), + "nvidia.com/gpumem": *resource.NewQuantity(-100, resource.DecimalSI), + }, + }, + }, + want: device.ContainerDeviceRequest{}, + }, + { + name: "oversized device count exceeds int32 range", + ctr: &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "nvidia.com/gpu": *resource.NewQuantity(2200000000, resource.DecimalSI), + }, + }, + }, + want: device.ContainerDeviceRequest{}, + }, + { + name: "negative core request", + ctr: &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "nvidia.com/gpu": *resource.NewQuantity(1, resource.BinarySI), + "nvidia.com/gpucores": *resource.NewQuantity(-1, resource.DecimalSI), + }, + }, + }, + want: device.ContainerDeviceRequest{}, + }, + { + name: "oversized core request exceeds int32 range", + ctr: &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "nvidia.com/gpu": *resource.NewQuantity(1, resource.BinarySI), + "nvidia.com/gpucores": *resource.NewQuantity(2200000000, resource.DecimalSI), + }, + }, + }, + want: device.ContainerDeviceRequest{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := dev.GenerateResourceRequests(tt.ctr) + assert.DeepEqual(t, result, tt.want) + }) + } +} + +// Test_GenerateResourceRequests_MemoryFactorOverflow covers a value that fits int32 but overflows after MemoryFactor. +func Test_GenerateResourceRequests_MemoryFactorOverflow(t *testing.T) { + config := NvidiaConfig{ + ResourceCountName: "nvidia.com/gpu", + ResourceMemoryName: "nvidia.com/gpumem", + ResourceCoreName: "nvidia.com/gpucores", + ResourceMemoryPercentageName: "nvidia.com/gpumem-percentage", + MemoryFactor: 10, + } + dev := InitNvidiaDevice(config) + ctr := &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + // 300000000 fits int32 but 300000000*10 overflows it. + "nvidia.com/gpu": *resource.NewQuantity(1, resource.BinarySI), + "nvidia.com/gpumem": *resource.NewQuantity(300000000, resource.DecimalSI), + }, + }, + } + result := dev.GenerateResourceRequests(ctr) + assert.DeepEqual(t, result, device.ContainerDeviceRequest{}) +} + func TestGenerateResourceRequests_DefaultMemory(t *testing.T) { config := NvidiaConfig{ ResourceCountName: "nvidia.com/gpu",