diff --git a/promql/durations.go b/promql/durations.go index c660dbf464..3dc44fbbc6 100644 --- a/promql/durations.go +++ b/promql/durations.go @@ -87,10 +87,21 @@ func (v *durationVisitor) calculateDuration(expr parser.Expr, allowedNegative bo if err != nil { return 0, err } + // Reject NaN and infinities up front. NaN compares false against everything, + // so without this guard a NaN duration would slip past the bounds check + // below and produce an implementation-defined int64 in the time.Duration + // conversion at the end of this function. + if math.IsNaN(duration) || math.IsInf(duration, 0) { + return 0, fmt.Errorf("%d:%d: duration is NaN or infinite", expr.PositionRange().Start, expr.PositionRange().End) + } if duration <= 0 && !allowedNegative { return 0, fmt.Errorf("%d:%d: duration must be greater than 0", expr.PositionRange().Start, expr.PositionRange().End) } - if duration > 1<<63-1 || duration < -1<<63 { + // duration is in seconds; the conversion below produces nanoseconds in an + // int64, so the safe input range is +/- math.MaxInt64 / 1e9 seconds. Match + // the bound used by the parser for duration literals (see + // offset_duration_expr in generated_parser.y). + if duration > 1<<63/1e9 || duration < -(1<<63)/1e9 { return 0, fmt.Errorf("%d:%d: duration is out of range", expr.PositionRange().Start, expr.PositionRange().End) } return time.Duration(duration*1000) * time.Millisecond, nil diff --git a/promql/durations_test.go b/promql/durations_test.go index 2ade9077c8..3b31e92dd6 100644 --- a/promql/durations_test.go +++ b/promql/durations_test.go @@ -266,6 +266,37 @@ func TestCalculateDuration(t *testing.T) { }, errorMessage: "modulo by zero", }, + { + name: "NaN from negative base raised to fractional power", + expr: &parser.DurationExpr{ + LHS: &parser.NumberLiteral{Val: -1}, + RHS: &parser.NumberLiteral{Val: 0.5}, + Op: parser.POW, + }, + errorMessage: "duration is NaN or infinite", + allowedNegative: true, + }, + { + name: "infinity from huge power", + expr: &parser.DurationExpr{ + LHS: &parser.NumberLiteral{Val: 2}, + RHS: &parser.NumberLiteral{Val: 1e10}, + Op: parser.POW, + }, + errorMessage: "duration is NaN or infinite", + }, + { + name: "duration exceeds time.Duration range", + expr: &parser.NumberLiteral{Val: 1e10}, + errorMessage: "duration is out of range", + }, + { + name: "duration just below the upper bound is accepted", + expr: &parser.NumberLiteral{Val: 1e9}, + // 1e9 seconds is below 1<<63/1e9 seconds (~9.22e9), so it is + // representable as a time.Duration. The exact value is preserved. + expected: time.Duration(1e9) * time.Second, + }, } for _, tt := range tests {