DomainMatcher: Fix Match() result slice aliasing race (#5959)

Fixes https://github.com/XTLS/Xray-core/pull/5814
This commit is contained in:
Meow
2026-04-18 06:07:58 +08:00
committed by GitHub
parent cb1106c2fb
commit d42c981f9c
5 changed files with 34 additions and 6 deletions
+4 -4
View File
@@ -271,11 +271,11 @@ func (s *DNS) sortClients(domain string) []*Client {
// Priority domain matching // Priority domain matching
hasMatch := false hasMatch := false
MatchSlice := s.domainMatcher.Match(strings.ToLower(domain)) matchSlice := s.domainMatcher.Match(strings.ToLower(domain))
sort.Slice(MatchSlice, func(i, j int) bool { sort.Slice(matchSlice, func(i, j int) bool {
return MatchSlice[i] < MatchSlice[j] return matchSlice[i] < matchSlice[j]
}) })
for _, match := range MatchSlice { for _, match := range matchSlice {
info := s.matcherInfos[match] info := s.matcherInfos[match]
client := s.clients[info.clientIdx] client := s.clients[info.clientIdx]
domainRule := info.domainRule domainRule := info.domainRule
+4
View File
@@ -11,7 +11,11 @@ import (
) )
type DomainMatcher interface { type DomainMatcher interface {
// Match returns the indices of all rules that match the input domain.
// The returned slice is owned by the caller and may be safely modified.
// Note: the slice may contain duplicates and the order is unspecified.
Match(input string) []uint32 Match(input string) []uint32
MatchAny(input string) bool MatchAny(input string) bool
} }
+22
View File
@@ -48,3 +48,25 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
t.Fatalf("Match() = %v, want %v", got, want) t.Fatalf("Match() = %v, want %v", got, want)
} }
} }
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
matcher, err := (&MphDomainMatcherFactory{}).BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
})
if err != nil {
t.Fatalf("BuildMatcher() failed: %v", err)
}
got := matcher.Match("example.com")
if !reflect.DeepEqual(got, []uint32{0, 1}) {
t.Fatalf("Match() = %v, want %v", got, []uint32{0, 1})
}
got[0] = 1
gotAgain := matcher.Match("example.com")
if !reflect.DeepEqual(gotAgain, []uint32{0, 1}) {
t.Fatalf("Match() after caller mutation = %v, want %v", gotAgain, []uint32{0, 1})
}
}
+2 -2
View File
@@ -3,6 +3,7 @@ package strmatcher
import ( import (
"errors" "errors"
"regexp" "regexp"
"slices"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@@ -253,13 +254,12 @@ func AddMatcherToGroup(g MatcherGroup, matcher Matcher, value uint32) error {
} }
// CompositeMatches flattens the matches slice to produce a single matched indices slice. // CompositeMatches flattens the matches slice to produce a single matched indices slice.
// It is designed to avoid new memory allocation as possible.
func CompositeMatches(matches [][]uint32) []uint32 { func CompositeMatches(matches [][]uint32) []uint32 {
switch len(matches) { switch len(matches) {
case 0: case 0:
return nil return nil
case 1: case 1:
return matches[0] return slices.Clone(matches[0])
default: default:
result := make([]uint32, 0, 5) result := make([]uint32, 0, 5)
for i := 0; i < len(matches); i++ { for i := 0; i < len(matches); i++ {
+2
View File
@@ -62,6 +62,7 @@ type IndexMatcher interface {
// Match returns the indices of all matchers that matches the input. // Match returns the indices of all matchers that matches the input.
// * Empty array is returned if no such matcher exists. // * Empty array is returned if no such matcher exists.
// * The order of returned matchers should follow priority specification. // * The order of returned matchers should follow priority specification.
// * The returned slice is owned by the caller and may be safely modified.
// Priority specification: // Priority specification:
// 1. Priority between matcher types: full > domain > substr > regex. // 1. Priority between matcher types: full > domain > substr > regex.
// 2. Priority of same-priority matchers matching at same position: the early added takes precedence. // 2. Priority of same-priority matchers matching at same position: the early added takes precedence.
@@ -89,6 +90,7 @@ type ValueMatcher interface {
// * Empty array is returned if no such matcher exists. // * Empty array is returned if no such matcher exists.
// * The order of returned values should follow priority specification. // * The order of returned values should follow priority specification.
// * Same value may appear multiple times if multiple matched matchers were added with that value. // * Same value may appear multiple times if multiple matched matchers were added with that value.
// * The returned slice is owned by the caller and may be safely modified.
// Priority specification: // Priority specification:
// 1. Priority between matcher types: full > domain > substr > regex. // 1. Priority between matcher types: full > domain > substr > regex.
// 2. Priority of same-priority matchers matching at same position: the early added takes precedence. // 2. Priority of same-priority matchers matching at same position: the early added takes precedence.