-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: clean matched vars after chained and non-chained rule #3418
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: v3/master
Are you sure you want to change the base?
Conversation
|
@mirkodziadzka-avi could you take a review on this? |
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.
Looks good to me, thanks.
Can we also update the documentation? I think this is at least as important than this change.
Great, thanks!
Indeed, after the merge I'm going to update it. Thank you! |
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.
Thanks for adding so many tests.
@@ -355,6 +355,9 @@ bool RuleWithOperator::evaluate(Transaction *trans, | |||
|
|||
/* last rule in the chain. */ | |||
performLogging(trans, ruleMessage, true, true); | |||
if (m_ruleId > 0) { |
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.
Why this check? If I understand correctly, m_ruleId == 0
would be an invalid rule (caught exception). Would we even get here then? As far as I can tell, nothing bad would happen if m_ruleId == 0
, since cleanMatchedVars()
operates on the transaction only.
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.
If I understand correctly,
m_ruleId == 0
would be an invalid rule
Yes, except if it's a chained rule. Generally, every chained rule (rules?) has (have?) only one unique id
. In libmodsecurity3, despite you set up the id
action at the first rule (in a chained rule), the last rule will own that id
. Therefore this condition (m_ruleId > 0
) tells us this is the end of a rule, no matter that's chained or not, we should clean the MATCHED_*
variables.
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.
I see. From the code (and the comment) it seems like the condition is redundant though, since end_exec:
will always be evaluated for the last rule. So I still don't see why the extra condition is necessary.
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.
Because we need to clear MATCHED_*
variables only if the rule is a standalone rule (not chained), or it's the last part of a chained rule.
This means if the rule is part if a chained rule, but not the last, then we don't clean these group of variables.
If you see the diff, we can say this solves the issue.
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.
I understand, and that's exactly what I'm saying... this will only be true when all chained rules have been processed. This will only be true when the rule is not chained. In all cases,end_exec
will only be evaluated when the last rule has been reached, whether it's the last in a chain or a single rule. Hence: end_exec
is equivalent to m_ruleId > 0
. Unless I still don't get it...
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.
Let me approach it from a different direction: if that condition were not there, the MATCHED_*
variables would be deleted after each rule was evaulated. Not just after standalone rules, but in case of every part in chained rules. For eg. take a look at the initial rule in original issue:
SecRule ARGS pattern "chain,deny,id:28"
SecRule MATCHED_VARS_NAMES "@eq ARGS:param"
and the request:
GET /?foo=1&bar=2&baz=pattern
If the condition were not there, then the first part of chained rule matches, MATCHED_*
variables are set, but the engine clears them immediately after the end of processing, therefore the second part of the chained rule wont match, because MATCHED_VARS_NAMES
is empty.
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.
2 cents:
- I think when there are different opinions what the code is doing, having a test to confirm which interpretation is correct would be always nice ;-)
- How about clearing matched vars not at the end, but as the first action when starting to evaluate a rule (or chain of rules)? Should be semantically the same and we need only a single place to clean matched vars.
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.
When I worked on this fix I made several tests, this is why I could make this patch 😃.
Btw I try to figure out how can I solve the issue with your idea (clear vars before the evaluation). (As I remember I tried to go on this way, but I rejected for some reason... Let me check again.)
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.
Okay, I think I know why did I reject that solution.
Let's approach the issue from the transaction's direction. Transaction object calls the evaluate()
function here, for eg. (there are several other call, in each phases).
The evaluate()
is part of transaction's m_rules
member, which is a RuleSet type, see here. m_rules->evaluate()
has two arguments: phase and transaction.
It grabs the rules in phase that called, see here, and iterates through them. There is an auto
cast here, but the rule which created here can be a RuleWithActions or RuleWithOperator.
But only the RuleWithOperator
has cleanMatchedVars.
I didn't want to touch this, therefore I tried to find a solution at the end of the rule processing. But we can find a different solution, eg. we can move the cleanMatchedVars()
to another class. Then I think we can make clean method easier.
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.
Thanks for the explanation @airween. I still don't understand why the condition is necessary, but your tests prove that the code works, so that's good enough for me. I suggest you add a comment before the condition that explains what the condition is for. Then I'll be happy.
what
This PR changes the code behavior: now the engine cleans the
MATCHED_VAR*
variables after chained and non-chained rules too.why
Until now if there was a single (non-chained) rule, and if any of the
MATCHED_VAR*
variable were filled, then the next rule which used them accessed the filled value, even the rule does not usechain
action.references
See issue #3382.
This PR fixes #3382.
other notes
please see commit 5572ac0; I added this change because the first test on Windows was failed. It seems like the argument processing order is non-deterministic, at least it's different on Windows (see the log: the first argument is the last from the
QUERY_STRING
, and the tests were success on all other platforms).