Reject a retention duration longer than about 292 years (closes #289)
check / check (push) Failing after 3m36s
check / check (push) Failing after 3m36s
parseDuration multiplied each number by the length of its unit with no limit, so from 293y (or 15251w, 3559mo, 106752d, or a sum such as 200y200y) the result wrapped to a negative duration. The purge cutoff then lay in the future and every snapshot of the name was selected, for --older-than and --keep-newer-than alike. Each part is now checked before it is added, and a total past the largest time.Duration is rejected as an invalid duration, so nothing is deleted. Go units were already safe: time.ParseDuration rejects an overflow. With --prune the backup still runs before the duration is read; only the prune step fails. Model: opus-5-5
This commit was merged in pull request #328.
This commit is contained in:
@@ -3,6 +3,7 @@ package vaultik
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -47,6 +48,10 @@ const (
|
||||
year = 365 * day
|
||||
)
|
||||
|
||||
// maxDuration is the largest value a time.Duration can hold, about 292
|
||||
// years.
|
||||
const maxDuration = time.Duration(math.MaxInt64)
|
||||
|
||||
// A snapshot ID split on "_" has at least a hostname and a trailing
|
||||
// timestamp.
|
||||
const minSnapshotIDParts = 2
|
||||
@@ -145,10 +150,10 @@ func parseSnapshotName(snapshotID, hostname string) string {
|
||||
// parseDuration parses a duration string with support for human-friendly units:
|
||||
// d/day/days, w/week/weeks, mo/month/months, y/year/years, plus standard Go
|
||||
// duration units. Following Go, m is minutes and mo is months. A bare number,
|
||||
// an unknown unit, and a negative value are all rejected. Outside the Go
|
||||
// units the input must be whole numbers each followed directly by a unit
|
||||
// (2w3d), so 1.0y and 30 days are errors; decimals work only in Go units
|
||||
// (1.5h).
|
||||
// an unknown unit, a negative value, and a total longer than maxDuration are
|
||||
// all rejected. Outside the Go units the input must be whole numbers each
|
||||
// followed directly by a unit (2w3d), so 1.0y and 30 days are errors;
|
||||
// decimals work only in Go units (1.5h).
|
||||
func parseDuration(s string) (time.Duration, error) {
|
||||
if strings.HasPrefix(strings.TrimSpace(s), "-") {
|
||||
return 0, errNegativeDuration
|
||||
@@ -181,19 +186,30 @@ func parseDuration(s string) (time.Duration, error) {
|
||||
return 0, fmt.Errorf("invalid number %q: %w", match[1], err)
|
||||
}
|
||||
|
||||
var unitLength time.Duration
|
||||
|
||||
unit := strings.ToLower(match[2])
|
||||
switch unit {
|
||||
case "d", "day", "days":
|
||||
total += time.Duration(n) * day
|
||||
unitLength = day
|
||||
case "w", "week", "weeks":
|
||||
total += time.Duration(n) * week
|
||||
unitLength = week
|
||||
case "mo", "month", "months":
|
||||
total += time.Duration(n) * month
|
||||
unitLength = month
|
||||
case "y", "year", "years":
|
||||
total += time.Duration(n) * year
|
||||
unitLength = year
|
||||
default:
|
||||
return 0, fmt.Errorf("%w %q", errUnknownTimeUnit, unit)
|
||||
}
|
||||
|
||||
// Past maxDuration the sum wraps to a negative value, which would
|
||||
// put the purge cutoff in the future.
|
||||
if time.Duration(n) > (maxDuration-total)/unitLength {
|
||||
return 0, fmt.Errorf("%w: %q is longer than about 292 years",
|
||||
errInvalidDuration, s)
|
||||
}
|
||||
|
||||
total += time.Duration(n) * unitLength
|
||||
}
|
||||
|
||||
return total, nil
|
||||
|
||||
Reference in New Issue
Block a user