test: add Cassandra/Hadoop CWE-1333 benchmarks (26 tests)
cassandra: 16 tests — PEMReader cert/key possessive fix, LogFile adjacent .* unambiguous split. All under 1s on N=10000 pathological. hadoop: 10 tests — RPC star-of-star fix. Under 2s on pathological input.
This commit is contained in:
parent
7e7ec2c3d3
commit
8a3868d583
2 changed files with 357 additions and 0 deletions
135
defects/hadoop/unit/test_hadoop_cwe1333.py
Normal file
135
defects/hadoop/unit/test_hadoop_cwe1333.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
"""
|
||||
CWE-1333 ReDoS tests for Apache Hadoop regex patterns.
|
||||
|
||||
Tests one confirmed true positive:
|
||||
1. RPC.Server.COMPLEX_SERVER_NAME_PATTERN (hadoop-0001-rpc)
|
||||
|
||||
Each test verifies:
|
||||
- Functional correctness: patched pattern still matches valid inputs
|
||||
- Complexity gate: pathological input completes in bounded time
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
import unittest
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Original (vulnerable) pattern — Python transliteration of Java source
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# (?:[^\$]*\$)*([A-Za-z][^\$]+)(?:\$\d+)?
|
||||
# In Python, $ in character class does not need escaping, but we keep it
|
||||
# faithful to the Java source which escapes \$.
|
||||
COMPLEX_SERVER_NAME_ORIG = re.compile(
|
||||
r"(?:[^\$]*\$)*([A-Za-z][^\$]+)(?:\$\d+)?"
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Patched pattern: inner * changed to +
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
COMPLEX_SERVER_NAME_FIXED = re.compile(
|
||||
r"(?:[^\$]+\$)*([A-Za-z][^\$]+)(?:\$\d+)?"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def time_match(pattern, text, timeout=5.0):
|
||||
"""Return (match_object, elapsed_seconds)."""
|
||||
start = time.monotonic()
|
||||
m = pattern.search(text)
|
||||
elapsed = time.monotonic() - start
|
||||
return m, elapsed
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# Test: RPC.Server.COMPLEX_SERVER_NAME_PATTERN (hadoop-0001-rpc)
|
||||
# ===========================================================================
|
||||
|
||||
class TestComplexServerNameReDoS(unittest.TestCase):
|
||||
"""hadoop-0001-rpc: star-of-star (?:[^\\$]*\\$)* in COMPLEX_SERVER_NAME_PATTERN."""
|
||||
|
||||
# -- Functional correctness --
|
||||
|
||||
def test_simple_class_name(self):
|
||||
"""Simple class: 'MyServer' -> group(1) = 'MyServer'"""
|
||||
m = COMPLEX_SERVER_NAME_FIXED.search("MyServer")
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), "MyServer")
|
||||
|
||||
def test_inner_class(self):
|
||||
"""Inner class: 'Outer$Inner' -> group(1) = 'Inner'"""
|
||||
m = COMPLEX_SERVER_NAME_FIXED.search("Outer$Inner")
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), "Inner")
|
||||
|
||||
def test_nested_inner_class(self):
|
||||
"""Nested: 'A$B$CD' -> group(1) = 'CD' (capture requires 2+ chars)"""
|
||||
m = COMPLEX_SERVER_NAME_FIXED.search("A$B$CD")
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), "CD")
|
||||
|
||||
def test_anonymous_inner_class(self):
|
||||
"""Anonymous: 'Outer$Inner$1' -> group(1) = 'Inner', trailing $1 consumed"""
|
||||
m = COMPLEX_SERVER_NAME_FIXED.search("Outer$Inner$1")
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), "Inner")
|
||||
|
||||
def test_protobuf_generated(self):
|
||||
"""Protobuf: 'ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2'"""
|
||||
m = COMPLEX_SERVER_NAME_FIXED.search(
|
||||
"ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2"
|
||||
)
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), "ClientNamenodeProtocol")
|
||||
|
||||
def test_deeply_nested(self):
|
||||
"""Deep nesting: 'A$B$C$D$RealName$3'"""
|
||||
m = COMPLEX_SERVER_NAME_FIXED.search("A$B$C$D$RealName$3")
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), "RealName")
|
||||
|
||||
def test_package_qualified(self):
|
||||
"""Full package path: 'org.apache.hadoop.ipc.TestRPC$TestProtocol$1'"""
|
||||
m = COMPLEX_SERVER_NAME_FIXED.search(
|
||||
"org.apache.hadoop.ipc.TestRPC$TestProtocol$1"
|
||||
)
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), "TestProtocol")
|
||||
|
||||
# -- Complexity gate --
|
||||
|
||||
def test_pathological_input_fixed_fast(self):
|
||||
"""Patched pattern must handle N=10000 chars without $ in <1s.
|
||||
|
||||
The original star-of-star tries O(2^N) empty-match partitions
|
||||
when no $ delimiter exists and the trailing anchor fails.
|
||||
With the fix ([^$]+$ requiring progress), the engine runs in O(N).
|
||||
"""
|
||||
# Input with no $ signs and a trailing char that prevents
|
||||
# the ([A-Za-z][^\$]+) group from matching cleanly
|
||||
evil = "a" * 10000 + "!"
|
||||
_, elapsed = time_match(COMPLEX_SERVER_NAME_FIXED, evil)
|
||||
self.assertLess(elapsed, 1.0,
|
||||
f"Fixed pattern took {elapsed:.3f}s on N=10000 (limit 1s)")
|
||||
|
||||
def test_pathological_no_match_fixed_fast(self):
|
||||
"""Non-alpha start forces full scan, must stay O(N)."""
|
||||
evil = "1" * 10000
|
||||
_, elapsed = time_match(COMPLEX_SERVER_NAME_FIXED, evil)
|
||||
self.assertLess(elapsed, 1.0,
|
||||
f"Fixed pattern took {elapsed:.3f}s on numeric N=10000 (limit 1s)")
|
||||
|
||||
def test_many_dollar_segments_fixed_fast(self):
|
||||
"""Many $ segments: 'a$' * N + '!' must stay O(N)."""
|
||||
evil = "a$" * 5000 + "!"
|
||||
_, elapsed = time_match(COMPLEX_SERVER_NAME_FIXED, evil)
|
||||
self.assertLess(elapsed, 2.0,
|
||||
f"Fixed pattern took {elapsed:.3f}s on 5000 segments (limit 2s)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue