fix(pii): Prevent cycles in rule collection - #6344
Conversation
| rules.insert(rule.clone()); // insert to break cycles | ||
| for rule_id in &m.rules { | ||
| collect_rules(config, rules, rule_id, parent.clone()); | ||
| } | ||
| rules.remove(&rule); // don't persist intermediates | ||
| } | ||
| RuleType::Alias(ref a) => { | ||
| let parent = if a.hide_inner { | ||
| Some(rule.clone()) | ||
| } else { | ||
| None | ||
| }; | ||
| rules.insert(rule.clone()); // insert to break cycles | ||
| collect_rules(config, rules, &a.rule, parent); |
There was a problem hiding this comment.
collect_rules has a path-only cycle guard and no traversal bound
An attacker-controlled PII rule graph can define nested Multiple nodes that repeatedly reference the same non-leaf child. Because collect_rules removes intermediates after each branch, the cycle set provides no memoization or work bound: a linear-size graph can trigger exponential traversal, while a long Alias chain can exhaust the worker stack and abort processing.
Evidence
PiiConfigdeserializes attacker-controlledrulesandapplications(relay-pii/src/config.rs:236-247); production project-event processing compiles the project PII config atrelay-server/src/processing/utils/event.rs:424-430, andrelay_validate_pii_configalso compiles externally supplied JSON atrelay-cabi/src/processing.rs:314-316.collect_ruleschecksrules.containsand inserts eachMultiple/Aliasonly while it is active (relay-pii/src/compiledconfig.rs:95-118), then removes the intermediate at lines 109 and 119; completed non-leaf nodes are not memoized.- A graph where each
Multiplenode containsrules: ["next", "next"]re-walks the same child twice at every level, producing Θ(2^N) traversal from O(N) rule definitions. A linear Alias chain still creates one recursive frame per rule because the path-only set does not bound acyclic depth. - Compilation occurs on the shared envelope processor pool (
relay-server/src/service.rs:113-127), andPiiConfig::compiledcaches only after the first unbounded compilation (relay-pii/src/config.rs:274-283); there is no depth, step, or graph-size guard before the traversal.
Also found at 2 additional locations
relay-pii/src/compiledconfig.rs:105-119relay-pii/src/compiledconfig.rs:206-242
Identified by Warden · wrdn-dos-review · Z2S-FSJ
| } else { | ||
| None | ||
| }; | ||
| rules.insert(rule.clone()); // insert to break cycles |
There was a problem hiding this comment.
Would it be possible to insert before the match and remove with a drop impl?
There was a problem hiding this comment.
See mstange/pdb-addr2line#71 for some prior art for this, though it might be overkill.
When collecting rules from a PII config, temporarily insert non-leaf nodes into the set of seen nodes to prevent cycles.
Fixes INGEST-1127