diff --git a/interpolate.go b/interpolate.go index bf6556d..f093966 100644 --- a/interpolate.go +++ b/interpolate.go @@ -5,7 +5,10 @@ import ( "strings" ) -const maxInterpolationPasses = 10 +const ( + maxInterpolationPasses = 10 + maxInterpolatedLength = 1 << 20 // 1 MiB +) var exprRE = regexp.MustCompile(`\$\{([^}]+)\}`) @@ -18,15 +21,26 @@ func interpolate(s string, props map[string]string) string { } for range maxInterpolationPasses { changed := false + capped := false + growth := 0 + baseLen := len(s) s = exprRE.ReplaceAllStringFunc(s, func(m string) string { + if capped { + return m + } name := m[2 : len(m)-1] if v, ok := lookup(props, name); ok { + growth += len(v) - len(m) + if baseLen+growth > maxInterpolatedLength { + capped = true + return m + } changed = true return v } return m }) - if !changed || !strings.Contains(s, "${") { + if capped || !changed || !strings.Contains(s, "${") { break } } diff --git a/interpolate_test.go b/interpolate_test.go index e133a42..1758523 100644 --- a/interpolate_test.go +++ b/interpolate_test.go @@ -36,6 +36,17 @@ func TestInterpolate(t *testing.T) { } } +func TestInterpolateAmplificationCapped(t *testing.T) { + props := map[string]string{ + "bomb": "${bomb}${bomb}${bomb}${bomb}${bomb}", + } + result := interpolate("${bomb}", props) + // Without the cap, 10 passes of 5x self-reference would produce ~68 MiB. + if len(result) > maxInterpolatedLength { + t.Fatalf("interpolated length %d exceeds cap %d", len(result), maxInterpolatedLength) + } +} + func TestFirstExpr(t *testing.T) { tests := []struct { in string