@@ -1074,7 +1074,25 @@ pub fn build_container_spec_for_image(
10741074 openshell_core:: config:: DEFAULT_SSH_PORT
10751075 ) ,
10761076 ] ,
1077- interval : config. health_check_interval_secs * 1_000_000_000 ,
1077+ interval : {
1078+ const NS_PER_S : u64 = 1_000_000_000 ;
1079+ let max_secs = ( i64:: MAX as u64 ) / NS_PER_S ;
1080+ if config. health_check_interval_secs > max_secs {
1081+ return Err ( ComputeDriverError :: InvalidArgument ( format ! (
1082+ "health_check_interval_secs {} exceeds maximum allowed nanoseconds" ,
1083+ config. health_check_interval_secs
1084+ ) ) ) ;
1085+ }
1086+ config
1087+ . health_check_interval_secs
1088+ . checked_mul ( NS_PER_S )
1089+ . ok_or_else ( || {
1090+ ComputeDriverError :: InvalidArgument ( format ! (
1091+ "health_check_interval_secs {} exceeds maximum allowed nanoseconds" ,
1092+ config. health_check_interval_secs
1093+ ) )
1094+ } ) ?
1095+ } ,
10781096 timeout : 2_000_000_000 ,
10791097 retries : 10 ,
10801098 start_period : 5_000_000_000 ,
@@ -1211,8 +1229,13 @@ fn parse_cpu_to_microseconds(quantity: &str) -> Option<u64> {
12111229 if cores <= 0.0 || cores. is_nan ( ) || cores. is_infinite ( ) {
12121230 return None ;
12131231 }
1232+ let micros_f = cores * 100_000.0 ;
1233+ #[ allow( clippy:: cast_precision_loss) ]
1234+ if !micros_f. is_finite ( ) || micros_f >= u64:: MAX as f64 {
1235+ return None ;
1236+ }
12141237 #[ allow( clippy:: cast_possible_truncation, clippy:: cast_sign_loss) ]
1215- let val = ( cores * 100_000.0 ) as u64 ;
1238+ let val = micros_f as u64 ;
12161239 val
12171240 } ;
12181241 // A quota of 0 microseconds is invalid — treat as no limit.
@@ -1287,6 +1310,56 @@ mod tests {
12871310 assert_eq ! ( parse_cpu_to_microseconds( "0.5" ) , Some ( 50_000 ) ) ;
12881311 }
12891312
1313+ #[ test]
1314+ fn parse_cpu_huge_value_returns_none_instead_of_overflow ( ) {
1315+ // A finite f64 whose product with 100_000 overflows to infinity.
1316+ assert_eq ! ( parse_cpu_to_microseconds( "1e300" ) , None ) ;
1317+ }
1318+
1319+ #[ test]
1320+ fn parse_cpu_rejects_u64_max_boundary ( ) {
1321+ // 184_467_440_737_095.51616 cores * 100_000 rounds exactly to 2^64,
1322+ // which used to pass the > check and silently saturate to u64::MAX.
1323+ // It must now be rejected. The previous value (184467440737095520)
1324+ // was ~1000x larger and did not exercise the equality boundary.
1325+ assert_eq ! ( parse_cpu_to_microseconds( "184467440737095.51616" ) , None ) ;
1326+
1327+ // Just below the boundary is still valid.
1328+ assert_eq ! (
1329+ parse_cpu_to_microseconds( "184467440737095" ) ,
1330+ Some ( 18_446_744_073_709_500_416 )
1331+ ) ;
1332+ }
1333+
1334+ #[ test]
1335+ fn container_spec_rejects_health_check_interval_overflow ( ) {
1336+ let sandbox = test_sandbox ( "test-id" , "test-name" ) ;
1337+ let mut config = test_config ( ) ;
1338+ // i64::MAX nanoseconds / 1_000_000_000 ns/s = 9_223_372_036 seconds.
1339+ // One second over must be rejected before it can saturate.
1340+ config. health_check_interval_secs = 9_223_372_037 ;
1341+ let err = try_build_container_spec_with_token ( & sandbox, & config, None ) . unwrap_err ( ) ;
1342+ assert ! (
1343+ matches!( err, ComputeDriverError :: InvalidArgument ( _) ) ,
1344+ "expected InvalidArgument, got {err:?}"
1345+ ) ;
1346+ assert ! ( format!( "{err}" ) . contains( "health_check_interval_secs" ) ) ;
1347+ }
1348+
1349+ #[ test]
1350+ fn container_spec_accepts_health_check_interval_at_boundary ( ) {
1351+ let sandbox = test_sandbox ( "test-id" , "test-name" ) ;
1352+ let mut config = test_config ( ) ;
1353+ // i64::MAX nanoseconds / 1_000_000_000 ns/s = 9_223_372_036 seconds.
1354+ const NS_PER_S : u64 = 1_000_000_000 ;
1355+ config. health_check_interval_secs = ( i64:: MAX as u64 ) / NS_PER_S ;
1356+ let spec = try_build_container_spec_with_token ( & sandbox, & config, None ) . unwrap ( ) ;
1357+ let interval = spec[ "healthconfig" ] [ "Interval" ]
1358+ . as_u64 ( )
1359+ . expect ( "healthcheck interval should be a u64" ) ;
1360+ assert_eq ! ( interval, config. health_check_interval_secs * NS_PER_S ) ;
1361+ }
1362+
12901363 #[ test]
12911364 fn parse_memory_binary_suffixes ( ) {
12921365 assert_eq ! ( parse_memory_to_bytes( "256Mi" ) , Some ( 256 * 1024 * 1024 ) ) ;
0 commit comments