@@ -13,6 +13,7 @@ import (
1313 "net/http"
1414 "net/url"
1515 "os"
16+ "reflect"
1617 "strings"
1718 "syscall"
1819 "time"
@@ -220,19 +221,60 @@ func probeResources(opts healthOptions) []checkResult {
220221 if err := syscall .Statfs (wd , & stat ); err != nil {
221222 results = append (results , checkResult {Name : "Disk space" , Warning : true , Detail : "unavailable: " + err .Error ()})
222223 } else {
223- free := stat .Bavail * uint64 (stat .Bsize )
224- detail := fmt .Sprintf ("%s free at %s" , humanBytes (free ), wd )
225- if free < opts .MinFreeDiskBytes {
226- results = append (results , checkResult {Name : "Disk space" , Detail : detail + " (below the minimum)" })
224+ free , ok := availableDiskBytes (stat .Bavail , stat .Bsize )
225+ if ! ok {
226+ results = append (results , checkResult {Name : "Disk space" , Warning : true , Detail : "unavailable: invalid filesystem capacity" })
227227 } else {
228- results = append (results , checkResult {Name : "Disk space" , OK : true , Detail : detail })
228+ detail := fmt .Sprintf ("%s free at %s" , humanBytes (free ), wd )
229+ if free < opts .MinFreeDiskBytes {
230+ results = append (results , checkResult {Name : "Disk space" , Detail : detail + " (below the minimum)" })
231+ } else {
232+ results = append (results , checkResult {Name : "Disk space" , OK : true , Detail : detail })
233+ }
229234 }
230235 }
231236
232237 results = append (results , probeMemory ())
233238 return results
234239}
235240
241+ // availableDiskBytes converts filesystem block counts to bytes without
242+ // accepting negative values or wrapping on multiplication.
243+ func availableDiskBytes (availableBlocks , blockSize any ) (uint64 , bool ) {
244+ blocks , ok := filesystemCountToUint64 (availableBlocks )
245+ if ! ok {
246+ return 0 , false
247+ }
248+ size , ok := filesystemCountToUint64 (blockSize )
249+ if ! ok || size == 0 {
250+ return 0 , false
251+ }
252+
253+ if blocks != 0 && size > ^ uint64 (0 )/ blocks {
254+ return 0 , false
255+ }
256+ return blocks * size , true
257+ }
258+
259+ // filesystemCountToUint64 normalizes the signed and unsigned statfs field
260+ // types used by supported operating systems. Signed values are validated
261+ // before conversion, so a malformed filesystem response cannot wrap.
262+ func filesystemCountToUint64 (value any ) (uint64 , bool ) {
263+ v := reflect .ValueOf (value )
264+ switch v .Kind () {
265+ case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
266+ signed := v .Int ()
267+ if signed < 0 {
268+ return 0 , false
269+ }
270+ return uint64 (signed ), true
271+ case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
272+ return v .Uint (), true
273+ default :
274+ return 0 , false
275+ }
276+ }
277+
236278// probeMemory reports available memory from the kernel where it is exposed.
237279func probeMemory () checkResult {
238280 data , err := os .ReadFile ("/proc/meminfo" )
0 commit comments