@@ -27,6 +27,12 @@ type CloakedName struct {
2727 PTR []string
2828}
2929
30+ // followsTarget returns true when resolving this rule requires following its
31+ // name target. Rules mapped to at least one IP address never follow it.
32+ func (cloakedName * CloakedName ) followsTarget () bool {
33+ return ! cloakedName .isIP && cloakedName .target != ""
34+ }
35+
3036type PluginCloak struct {
3137 sync.RWMutex
3238 patternMatcher * PatternMatcher
@@ -148,25 +154,50 @@ func (plugin *PluginCloak) loadRules(lines string, patternMatcher *PatternMatche
148154 }
149155 }
150156
157+ return detectCloakingLoops (cloakedNames , patternMatcher )
158+ }
159+
160+ // detectCloakingLoops rejects rules whose name targets form a resolution loop.
161+ // A target that matches another cloaking rule is fine as long as the chain
162+ // terminates: rules mapped to IP addresses never follow their name target at
163+ // runtime, so they end the chain. Only chains that revisit a rule can loop
164+ // forever. The offender with the lowest line number is reported so the error
165+ // does not depend on map iteration order.
166+ func detectCloakingLoops (cloakedNames map [string ]* CloakedName , patternMatcher * PatternMatcher ) error {
151167 var firstRecursive * CloakedName
152168 var firstRecursivePattern string
153169 for _ , cloakedName := range cloakedNames {
154- if cloakedName .isIP || cloakedName . target == "" {
170+ if ! cloakedName .followsTarget () {
155171 continue
156172 }
157- matched , pattern , _ := patternMatcher .Eval (cloakedName .target )
158- if matched && (firstRecursive == nil || cloakedName .lineNo < firstRecursive .lineNo ) {
159- firstRecursive = cloakedName
160- firstRecursivePattern = pattern
173+ visited := map [* CloakedName ]bool {cloakedName : true }
174+ current := cloakedName
175+ for {
176+ matched , pattern , xNext := patternMatcher .Eval (current .target )
177+ if ! matched {
178+ break
179+ }
180+ next := xNext .(* CloakedName )
181+ if ! next .followsTarget () {
182+ break
183+ }
184+ if visited [next ] {
185+ if firstRecursive == nil || cloakedName .lineNo < firstRecursive .lineNo {
186+ firstRecursive = cloakedName
187+ firstRecursivePattern = pattern
188+ }
189+ break
190+ }
191+ visited [next ] = true
192+ current = next
161193 }
162194 }
163195 if firstRecursive != nil {
164196 return fmt .Errorf (
165- "recursive cloaking rule at line %d: target [%s] matches cloak pattern [%s]" ,
197+ "recursive cloaking rule at line %d: target [%s] loops back to cloak pattern [%s]" ,
166198 firstRecursive .lineNo , firstRecursive .target , firstRecursivePattern ,
167199 )
168200 }
169-
170201 return nil
171202}
172203
@@ -294,7 +325,7 @@ func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error
294325 }
295326 }
296327 synth := EmptyResponseFromMessage (msg )
297- if ! cloakedName .isIP && ((qtype == dns .TypeA && cloakedName .ipv4 == nil ) ||
328+ if cloakedName .followsTarget () && ((qtype == dns .TypeA && cloakedName .ipv4 == nil ) ||
298329 (qtype == dns .TypeAAAA && cloakedName .ipv6 == nil ) || expired ) {
299330 target := cloakedName .target
300331 plugin .RUnlock ()
0 commit comments