Commit aafb17f
committed
Fix exponential expansion based DoS in merge key processing
Resolves #897
Supercedes #916
### Summary
The merge key (`<<`) constructor implementation in
`SafeConstructor.flatten_mapping()` was vulnerable to an
exponential time and memory complexity Denial of Service (DoS)
vulnerability. When mapping/sequence nodes are merged using
anchors/aliases, duplicate references to the same alias point
to the same MappingNode instance in Python. During merge key
processing, the node values are copied and extended in-place.
If the same node appears multiple times at different levels,
this causes exponential amplification of the elements list:
`2^(n+1) - 1`.
A small document under 1 KB can trigger millions of element
list extensions, exhausting CPU and memory during safe loading.
### Hardened Fix
This commit resolves the vulnerability and hardens it against
secondary vectors:
1. Tracks node identity using object ID (`id(node)`) in a single
`seen` set scoped to the parent mapping's `flatten_mapping()`
execution.
2. Checks and skips duplicate node references inside SequenceNode
merge keys (resolving PR-916).
3. Checks and skips duplicate node references across separate,
independent MappingNode merge keys in the same mapping (e.g.,
repeating `<<: *anchor` multiple times).
4. Ensures C-based loaders (e.g., `CSafeLoader`, `CLoader`) are
also protected since they inherit constructor logic from
`SafeConstructor`.
### Performance Impact
- Sequence-nested merge duplicates: Loading a 22-level nested
document drops from 3.76s to 0.0028s (O(N) linear complexity).
- Mapping-level merge duplicates: Loading a 20-level nested
document drops from 0.93s to 0.0026s.
### Tests
- Added regression tests to
`tests/legacy_tests/data/construct-merge.data` and
`tests/legacy_tests/data/construct-merge.code` covering both
duplicate sequence merges and duplicate direct merges.1 parent b46710a commit aafb17f
3 files changed
Lines changed: 17 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
180 | 180 | | |
181 | 181 | | |
182 | 182 | | |
| 183 | + | |
183 | 184 | | |
184 | 185 | | |
185 | 186 | | |
186 | 187 | | |
187 | 188 | | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
188 | 192 | | |
189 | 193 | | |
190 | 194 | | |
191 | 195 | | |
192 | | - | |
193 | 196 | | |
194 | 197 | | |
195 | 198 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
| 11 | + | |
10 | 12 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
0 commit comments