-
Notifications
You must be signed in to change notification settings - Fork 661
add sequence statement for ordered matching #2816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,7 +167,9 @@ class CompoundStatementType: | |
| AND = "and" | ||
| OR = "or" | ||
| NOT = "not" | ||
| NOT = "not" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| OPTIONAL = "optional" | ||
| SEQUENCE = "sequence" | ||
|
|
||
|
|
||
| class StatementModel(FrozenModel): ... | ||
|
|
@@ -213,7 +215,7 @@ class StatementNode(FrozenModel): | |
|
|
||
|
|
||
| def statement_from_capa(node: capa.engine.Statement) -> Statement: | ||
| if isinstance(node, (capa.engine.And, capa.engine.Or, capa.engine.Not)): | ||
| if isinstance(node, (capa.engine.And, capa.engine.Or, capa.engine.Not, capa.engine.Sequence)): | ||
| return CompoundStatement(type=node.__class__.__name__.lower(), description=node.description) | ||
|
|
||
| elif isinstance(node, capa.engine.Some): | ||
|
|
@@ -280,6 +282,9 @@ def node_to_capa( | |
| elif node.statement.type == CompoundStatementType.OPTIONAL: | ||
| return capa.engine.Some(description=node.statement.description, count=0, children=children) | ||
|
|
||
| elif node.statement.type == CompoundStatementType.SEQUENCE: | ||
| return capa.engine.Sequence(description=node.statement.description, children=children) | ||
|
|
||
| else: | ||
| assert_never(node.statement.type) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -635,6 +635,8 @@ def build_statements(d, scopes: Scopes): | |||||
| return ceng.And(unique(build_statements(dd, scopes) for dd in d[key]), description=description) | ||||||
| elif key == "or": | ||||||
| return ceng.Or(unique(build_statements(dd, scopes) for dd in d[key]), description=description) | ||||||
| elif key == "sequence": | ||||||
| return ceng.Sequence(unique(build_statements(dd, scopes) for dd in d[key]), description=description) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using
Suggested change
|
||||||
| elif key == "not": | ||||||
| if len(d[key]) != 1: | ||||||
| raise InvalidRule("not statement must have exactly one child statement") | ||||||
|
|
@@ -1698,7 +1700,7 @@ def rec( | |||||
| # feature is found N times | ||||||
| return rec(rule_name, node.child) | ||||||
|
|
||||||
| elif isinstance(node, ceng.And): | ||||||
| elif isinstance(node, (ceng.And, ceng.Sequence)): | ||||||
| # When evaluating an AND block, all of the children need to match. | ||||||
| # | ||||||
| # So when we index rules, we want to pick the most uncommon feature(s) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # limitations under the License. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import capa.features.address | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from capa.engine import Or, And, Not, Some, Range | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from capa.engine import Or, And, Not, Some, Range, Sequence | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from capa.features.insn import Number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADDR1 = capa.features.address.AbsoluteVirtualAddress(0x401001) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -155,3 +155,145 @@ def test_eval_order(): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Or([Number(1), Number(2)]).evaluate({Number(2): {ADDR1}}).children[1].statement == Number(2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Or([Number(1), Number(2)]).evaluate({Number(2): {ADDR1}}).children[1].statement != Number(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_sequence(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 before 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert bool(Sequence([Number(1), Number(2)]).evaluate({Number(1): {ADDR1}, Number(2): {ADDR2}})) is True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2 before 1 (fail) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert bool(Sequence([Number(1), Number(2)]).evaluate({Number(1): {ADDR2}, Number(2): {ADDR1}})) is False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 same as 2 (fail) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert bool(Sequence([Number(1), Number(2)]).evaluate({Number(1): {ADDR1}, Number(2): {ADDR1}})) is False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 before 2 before 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sequence([Number(1), Number(2), Number(3)]).evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {Number(1): {ADDR1}, Number(2): {ADDR2}, Number(3): {ADDR3}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 before 2 before 3 (fail, 3 is early) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sequence([Number(1), Number(2), Number(3)]).evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {Number(1): {ADDR1}, Number(2): {ADDR4}, Number(3): {ADDR3}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 before 2 before 3 (fail, 2 is late) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sequence([Number(1), Number(2), Number(3)]).evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {Number(1): {ADDR1}, Number(2): {ADDR4}, Number(3): {ADDR3}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+178
to
+196
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test case is duplicated. Additionally, the comment
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # multiple locations for matches | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 at 1, 2 at 2 (match) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 also at 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert bool(Sequence([Number(1), Number(2)]).evaluate({Number(1): {ADDR1, ADDR3}, Number(2): {ADDR2}})) is True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # greedy matching? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 at 2, 2 at 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 matches at 2, so min_loc becomes 2. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2 matches at 3, > 2. Match. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # But wait, 1 also matches at 4. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If we picked 4, 1 > 2 would fail? No. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # The heuristic is: pick the *smallest* location for the current child (that satisfies previous constraint). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # CASE: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 matches at 10. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2 matches at 5 and 15. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # if 2 picks 5, 5 > 10 is False. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # if 2 picks 15, 15 > 10 is True. Match. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sequence([Number(1), Number(2)]).evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Number(1): {capa.features.address.AbsoluteVirtualAddress(10)}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Number(2): { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| capa.features.address.AbsoluteVirtualAddress(5), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| capa.features.address.AbsoluteVirtualAddress(15), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # CASE: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 matches at 10 and 20. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2 matches at 15. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 should pick 10. 10 < 15. Match. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sequence([Number(1), Number(2)]).evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Number(1): { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| capa.features.address.AbsoluteVirtualAddress(10), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| capa.features.address.AbsoluteVirtualAddress(20), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Number(2): {capa.features.address.AbsoluteVirtualAddress(15)}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # CASE: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 matched at 10. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2 matched at 15. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 3 matched at 12. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1 -> 10. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2 -> 15 (> 10). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 3 -> 12 (not > 15). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Fail. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sequence([Number(1), Number(2), Number(3)]).evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Number(1): {capa.features.address.AbsoluteVirtualAddress(10)}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Number(2): {capa.features.address.AbsoluteVirtualAddress(15)}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Number(3): {capa.features.address.AbsoluteVirtualAddress(12)}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_location_propagation(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # regression tests for issue where Or/And/Some statements | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # failed to propagate match locations to their results, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # causing Sequence evaluation to fail. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Or | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Or([Number(1)]).evaluate({Number(1): {ADDR1}}).locations == {ADDR1} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Or([Number(1), Number(2)]).evaluate({Number(1): {ADDR1}, Number(2): {ADDR2}}).locations == { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADDR1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } # short_circuit=True returns first match | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Or([Number(1), Number(2)]).evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {Number(1): {ADDR1}, Number(2): {ADDR2}}, short_circuit=False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ).locations == {ADDR1, ADDR2} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # And | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert And([Number(1)]).evaluate({Number(1): {ADDR1}}).locations == {ADDR1} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert And([Number(1), Number(2)]).evaluate({Number(1): {ADDR1}, Number(2): {ADDR2}}).locations == {ADDR1, ADDR2} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Some | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Some(1, [Number(1)]).evaluate({Number(1): {ADDR1}}).locations == {ADDR1} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Some(1, [Number(1), Number(2)]).evaluate({Number(1): {ADDR1}, Number(2): {ADDR2}}).locations == { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADDR1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } # short_circuit=True returns first sufficient set | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert Some(2, [Number(1), Number(2)]).evaluate({Number(1): {ADDR1}, Number(2): {ADDR2}}).locations == { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADDR1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADDR2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for finding the next valid location can be simplified to be more readable and concise by using a generator expression with
next(). This avoids the use of a boolean flag (found) and repeated code, making the implementation easier to maintain.