mirror of
https://github.com/coredns/coredns.git
synced 2026-01-08 01:41:22 -05:00
fix(plugins): add regex length limit (#7802)
This commit is contained in:
@@ -29,7 +29,7 @@ errors {
|
||||
|
||||
Option `stacktrace` will log a stacktrace during panic recovery.
|
||||
|
||||
Option `consolidate` allows collecting several error messages matching the regular expression **REGEXP** during **DURATION**. After the **DURATION** since receiving the first such message, the consolidated message will be printed to standard output with
|
||||
Option `consolidate` allows collecting several error messages matching the regular expression **REGEXP** during **DURATION**. **REGEXP** must not exceed 10000 characters. After the **DURATION** since receiving the first such message, the consolidated message will be printed to standard output with
|
||||
log level, which is configurable by optional option **LEVEL**. Supported options for **LEVEL** option are `warning`,`error`,`info` and `debug`.
|
||||
~~~
|
||||
2 errors like '^read udp .* i/o timeout$' occurred in last 30s
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"github.com/coredns/coredns/plugin"
|
||||
)
|
||||
|
||||
// maxRegexpLen is a hard limit on the length of a regex pattern to prevent
|
||||
// OOM during regex compilation with malicious input.
|
||||
const maxRegexpLen = 10000
|
||||
|
||||
func init() { plugin.Register("errors", setup) }
|
||||
|
||||
func setup(c *caddy.Controller) error {
|
||||
@@ -78,6 +82,9 @@ func parseConsolidate(c *caddy.Controller) (*pattern, error) {
|
||||
if err != nil {
|
||||
return nil, c.Err(err.Error())
|
||||
}
|
||||
if len(args[1]) > maxRegexpLen {
|
||||
return nil, c.Errf("regex pattern too long: %d > %d", len(args[1]), maxRegexpLen)
|
||||
}
|
||||
re, err := regexp.Compile(args[1])
|
||||
if err != nil {
|
||||
return nil, c.Err(err.Error())
|
||||
|
||||
@@ -2,6 +2,7 @@ package errors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
golog "log"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -250,3 +251,19 @@ func TestShowFirstOption(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorsParseLargeRegex(t *testing.T) {
|
||||
largeRegex := strings.Repeat("a", maxRegexpLen+1)
|
||||
config := fmt.Sprintf(`errors {
|
||||
consolidate 1m %s
|
||||
}`, largeRegex)
|
||||
|
||||
c := caddy.NewTestController("dns", config)
|
||||
_, err := errorsParse(c)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for large regex, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "too long") {
|
||||
t.Errorf("Expected 'too long' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ The match type, e.g., `exact`, `substring`, etc., triggers rewrite:
|
||||
* **substring**: on a partial match of the name in the question section of a request
|
||||
* **prefix**: when the name begins with the matching string
|
||||
* **suffix**: when the name ends with the matching string
|
||||
* **regex**: when the name in the question section of a request matches a regular expression
|
||||
* **regex**: when the name in the question section of a request matches a regular expression.
|
||||
Regex patterns must not exceed 10000 characters.
|
||||
|
||||
If the match type is omitted, the `exact` match type is assumed. If OPTIONS are
|
||||
given, the type must be specified.
|
||||
|
||||
@@ -144,6 +144,9 @@ func newCNAMERule(nextAction string, args ...string) (Rule, error) {
|
||||
Upstream: upstream.New(),
|
||||
}
|
||||
if rewriteType == RegexMatch {
|
||||
if len(paramFromTarget) > maxRegexpLen {
|
||||
return nil, fmt.Errorf("regex pattern too long in a cname rule: %d > %d", len(paramFromTarget), maxRegexpLen)
|
||||
}
|
||||
re, err := regexp.Compile(paramFromTarget)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid cname rewrite regex pattern: %w", err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
@@ -263,3 +264,14 @@ func TestCNAMETargetRewrite_upstreamFailurePaths(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCNAMERuleLargeRegex(t *testing.T) {
|
||||
largeRegex := strings.Repeat("a", maxRegexpLen+1)
|
||||
_, err := newCNAMERule("stop", "regex", largeRegex, "replacement")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for large regex, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "too long") {
|
||||
t.Errorf("Expected 'too long' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// maxRegexpLen is a hard limit on the length of a regex pattern to prevent
|
||||
// OOM during regex compilation with malicious input.
|
||||
const maxRegexpLen = 10000
|
||||
|
||||
// stringRewriter rewrites a string
|
||||
type stringRewriter interface {
|
||||
rewriteString(src string) string
|
||||
@@ -438,6 +442,9 @@ func getSubExprUsage(s string) int {
|
||||
|
||||
// isValidRegexPattern returns a regular expression for pattern matching or errors, if any.
|
||||
func isValidRegexPattern(rewriteFrom, rewriteTo string) (*regexp.Regexp, error) {
|
||||
if len(rewriteFrom) > maxRegexpLen {
|
||||
return nil, fmt.Errorf("regex pattern too long: %d > %d", len(rewriteFrom), maxRegexpLen)
|
||||
}
|
||||
rewriteFromPattern, err := regexp.Compile(rewriteFrom)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regex matching pattern: %s", rewriteFrom)
|
||||
|
||||
@@ -368,3 +368,14 @@ func TestNewNameRule(t *testing.T) {
|
||||
t.Fatalf("Test %d: FAIL, expected fail=%t, but received fail=%t: (%s) %s, rule=%v", i, tc.expectedFail, failed, tc.next, tc.args, rule)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNameRuleLargeRegex(t *testing.T) {
|
||||
largeRegex := strings.Repeat("a", maxRegexpLen+1)
|
||||
_, err := newNameRule("stop", "regex", largeRegex, "replacement")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for large regex, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "too long") {
|
||||
t.Errorf("Expected 'too long' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,9 @@ func newRCodeRule(nextAction string, args ...string) (Rule, error) {
|
||||
plugin.Name(args[1]).Normalize(),
|
||||
}, nil
|
||||
case RegexMatch:
|
||||
if len(args[1]) > maxRegexpLen {
|
||||
return nil, fmt.Errorf("regex pattern too long in a rcode rule: %d > %d", len(args[1]), maxRegexpLen)
|
||||
}
|
||||
regexPattern, err := regexp.Compile(args[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regex pattern in a rcode rule: %s", args[1])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package rewrite
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
@@ -70,3 +71,14 @@ func TestRCodeRewrite(t *testing.T) {
|
||||
t.Fatalf("RCode rewrite did not apply changes, request=%#v, err=%v", request.Req, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRCodeRuleLargeRegex(t *testing.T) {
|
||||
largeRegex := strings.Repeat("a", maxRegexpLen+1)
|
||||
_, err := newRCodeRule("stop", "regex", largeRegex, "SERVFAIL", "NXDOMAIN")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for large regex, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "too long") {
|
||||
t.Errorf("Expected 'too long' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,9 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) {
|
||||
plugin.Name(args[1]).Normalize(),
|
||||
}, nil
|
||||
case RegexMatch:
|
||||
if len(args[1]) > maxRegexpLen {
|
||||
return nil, fmt.Errorf("regex pattern too long in a ttl rule: %d > %d", len(args[1]), maxRegexpLen)
|
||||
}
|
||||
regexPattern, err := regexp.Compile(args[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regex pattern in a ttl rule: %s", args[1])
|
||||
|
||||
@@ -3,6 +3,7 @@ package rewrite
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
@@ -156,3 +157,14 @@ func doTTLTests(t *testing.T, rules []Rule) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTTLRuleLargeRegex(t *testing.T) {
|
||||
largeRegex := strings.Repeat("a", maxRegexpLen+1)
|
||||
_, err := newTTLRule("stop", "regex", largeRegex, "300")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for large regex, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "too long") {
|
||||
t.Errorf("Expected 'too long' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ template CLASS TYPE [ZONE...] {
|
||||
* **TYPE** the query type (A, PTR, ... can be ANY to match all types).
|
||||
* **ZONE** the zone scope(s) for this template. Defaults to the server zones.
|
||||
* `match` **REGEX** [Go regexp](https://golang.org/pkg/regexp/) that are matched against the incoming question name.
|
||||
Specifying no regex matches everything (default: `.*`). First matching regex wins.
|
||||
Specifying no regex matches everything (default: `.*`). First matching regex wins. Regex patterns
|
||||
must not exceed 10000 characters.
|
||||
* `answer|additional|authority` **RR** A [RFC 1035](https://tools.ietf.org/html/rfc1035#section-5) style resource record fragment
|
||||
built by a [Go template](https://golang.org/pkg/text/template/) that contains the reply. Specifying no answer will result
|
||||
in a response with an empty answer section.
|
||||
|
||||
@@ -13,6 +13,10 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// maxRegexpLen is a hard limit on the length of a regex pattern to prevent
|
||||
// OOM during regex compilation with malicious input.
|
||||
const maxRegexpLen = 10000
|
||||
|
||||
func init() { plugin.Register("template", setupTemplate) }
|
||||
|
||||
func setupTemplate(c *caddy.Controller) error {
|
||||
@@ -67,6 +71,9 @@ func templateParse(c *caddy.Controller) (handler Handler, err error) {
|
||||
return handler, c.ArgErr()
|
||||
}
|
||||
for _, regex := range args {
|
||||
if len(regex) > maxRegexpLen {
|
||||
return handler, c.Errf("regex pattern too long: %d > %d", len(regex), maxRegexpLen)
|
||||
}
|
||||
r, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return handler, c.Errf("could not parse regex: %s, %v", regex, err)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/caddy"
|
||||
@@ -198,3 +200,19 @@ func TestSetupParse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupParseLargeRegex(t *testing.T) {
|
||||
largeRegex := strings.Repeat("a", maxRegexpLen+1)
|
||||
config := fmt.Sprintf(`template ANY A example.com {
|
||||
match %s
|
||||
}`, largeRegex)
|
||||
|
||||
c := caddy.NewTestController("dns", config)
|
||||
_, err := templateParse(c)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for large regex, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "too long") {
|
||||
t.Errorf("Expected 'too long' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user