fix(alert): prevent divide-by-zero panic and unbounded growth on zero-duration rule

A general alert rule with Duration:0 (accepted by the API, no minimum
validation) drove fail*100/total with total==0, panicking with an integer
divide-by-zero inside checkStatus, which has no recover and would take down the
whole alert goroutine — a config-reachable DoS by any user able to create alert
rules. Skip duration<=0 rules in Check via continue (not boundCheck, which would
pollute hasPassedRule and skip sibling valid rules). Also trim the sample slice
when max<=0 in alertsentinel, otherwise it appended every tick without ever
trimming. Adds regression tests for zero-only and mixed rule sets.
This commit is contained in:
naiba
2026-06-05 02:42:49 +00:00
parent 36240f5888
commit 0759f5c801
3 changed files with 61 additions and 4 deletions
+9 -4
View File
@@ -184,10 +184,15 @@ func checkStatus() {
}
alertsPrevState[alert.ID][server.ID] = _RuleCheckPass
}
// 清理旧数据
if max > 0 && max < len(alertsStore[alert.ID][server.ID]) {
index := len(alertsStore[alert.ID][server.ID]) - max
alertsStore[alert.ID][server.ID] = alertsStore[alert.ID][server.ID][index:]
// 清理旧数据:只需保留最近 max 个采样点(各规则 Duration 的最大值)。
// max==0 表示该 alert 没有任何有效规则会回看历史(例如全部
// Duration<=0 被跳过),此时历史采样无用,必须清空——否则每个
// tick 都 append 而永不裁剪,切片会无限增长成内存泄漏。
samples := alertsStore[alert.ID][server.ID]
if max <= 0 {
alertsStore[alert.ID][server.ID] = samples[:0]
} else if max < len(samples) {
alertsStore[alert.ID][server.ID] = samples[len(samples)-max:]
}
}
}