Merge pull request #18639 from alexmchughdev/promql-duration-overflow-nan

promql: reject NaN/Inf and fix overflow bound in duration expressions
This commit is contained in:
Julien 2026-05-08 14:18:56 +02:00 committed by GitHub
commit 6299449664
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 1 deletions

View File

@ -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

View File

@ -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 {