Closed WGH- closed 3 years ago
Well noticed. Let me give a little bit of background on that particular change.
The idea is that 3.1 will be able to reload the rules in place, without the server to restart. For that, it was necessary to reduce the memory footprint of the RuleObject and adjacent. At a certain time, rules will be allocated twice in memory: (a) Adress the active requests; (b) Adress the new ones. That new feature also makes it necessary to clean up the memory properly after a new rule set is un-load.
Having said that, in terms of actions (cited in this issue), we have different approaches: some stuff became a rule property (logging) and some are actions to be used in run time with parameters inherited from the parser. To exemplify: Instead to be an object: nolog, log, auditlog and noauditlog became a bit properties of the class Rule:
Further, those will become a two-bit property, that will be queried with a bitwise operation: | Porporty | Value |
---|---|---|
noLog | 00 | |
log | 01 | |
noAuditLog | 10 | |
auditLog | 11 |
The thing with run-time-actions, the ones that are classes, is that they belong to a rule. Still, they can also belong to a rule in a second (or nth) virtual host (a consequence of a merging). Atop of that, there are the DefaultActions, which we are aiming to pre-compute in rules load time -- avoid any but necessary processing at runtime.
The smart pointers have an important role in that architecture as we are offloading the responsibility to disposable the action object to C++. I will have a look to understand better what is going on and let you know.
Thanks for testing and reporting this ;) appreciated!
The dynamic_cast<ActionDisruptive *>(a) != NULL
issue was already addressed on v3.1-experimental.
Might not be the same crash you mentioned in https://github.com/SpiderLabs/ModSecurity/pull/2374#issuecomment-671348276, but it's likely related.
Commit b32182940d
To reproduce, use this string in benchmark.cc
:
char request_uri[] = "/foobar?message=%22AAAAAAAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAA%3DBBBBB%3BAAAAAAAAAAAAAA%3DBBBBB%3BloadAAAAAAAA%3DBBBBB%3BAAAAAAAAAAAAAAA%22"
(gdb) bt
#0 modsecurity::RuleWithActions::getMessage[abi:cxx11](modsecurity::Transaction*) const (this=this@entry=0x555556175e00, t=t@entry=0x5555565bb3e0)
at rule_with_actions.cc:399
#1 0x00007ffff7e49494 in modsecurity::variables::Rule_DictElement::msg (t=0x5555565bb3e0, rule=0x555556175e00, l=0x7fffffffc720)
at /usr/include/c++/9/bits/shared_ptr_base.h:1312
#2 0x00007ffff7e4970c in non-virtual thunk to modsecurity::variables::Rule_DictElement::evaluate(modsecurity::Transaction*, std::vector<modsecurity::VariableValue const*, std::allocator<modsecurity::VariableValue const*> >*) () at ../src/variables/rule.h:165
#3 0x00007ffff7ea79ae in modsecurity::RunTimeString::evaluate[abi:cxx11](modsecurity::Transaction*) (this=0x555556177050, transaction=transaction@entry=0x5555565bb3e0)
at /usr/include/c++/9/bits/shared_ptr_base.h:1309
#4 0x00007ffff7edae66 in modsecurity::actions::ActionWithRunTimeString::getEvaluatedRunTimeString[abi:cxx11](modsecurity::Transaction*) const (
transaction=0x5555565bb3e0, this=0x555556171e00) at /usr/include/c++/9/bits/unique_ptr.h:721
#5 modsecurity::actions::SetVar::execute (this=0x555556171e00, t=0x5555565bb3e0) at actions/set_var.cc:50
#6 0x00007ffff7eaaafc in modsecurity::RuleWithActions::executeActionsIndependentOfChainedRuleResult (this=this@entry=0x5555565a1970, trans=trans@entry=0x5555565bb3e0)
at rule_with_actions.cc:207
#7 0x00007ffff7eb33dd in modsecurity::RuleWithOperator::evaluate (this=0x5555565a1970, trans=0x5555565bb3e0) at rule_with_operator.cc:340
#8 0x00007ffff7e9fb7f in modsecurity::RulesSet::evaluate (this=0x5555555aac80, phase=phase@entry=3, t=t@entry=0x5555565bb3e0)
at /usr/include/c++/9/bits/shared_ptr_base.h:1309
#9 0x00007ffff7e8bbb5 in modsecurity::Transaction::processRequestBody (this=0x5555565bb3e0) at transaction.cc:985
#10 0x0000555555556f2c in main (argc=<optimized out>, argv=<optimized out>) at benchmark.cc:136
==95724==ERROR: AddressSanitizer: heap-use-after-free on address 0x61600006f6f0 at pc 0x7f6f3a19bf27 bp 0x7fffcb419890 sp 0x7fffcb419880
READ of size 8 at 0x61600006f6f0 thread T0
#0 0x7f6f3a19bf26 in modsecurity::variables::Rule_DictElement::msg(modsecurity::Transaction*, modsecurity::RuleWithActions const*, std::vector<modsecurity::VariableValue const*, std::allocator<modsecurity::VariableValue const*> >*) (/home/wgh/ModSecurity-upstream/src/.libs/libmodsecurity.so.3+0x285f26)
#1 0x7f6f3a19c62b in non-virtual thunk to modsecurity::variables::Rule_DictElement::evaluate(modsecurity::Transaction*, std::vector<modsecurity::VariableValue const*, std::allocator<modsecurity::VariableValue const*> >*) (/home/wgh/ModSecurity-upstream/src/.libs/libmodsecurity.so.3+0x28662b)
#2 0x7f6f3a3015c2 in modsecurity::RunTimeString::evaluate[abi:cxx11](modsecurity::Transaction*) /home/wgh/ModSecurity-upstream/src/run_time_string.cc:58
#3 0x7f6f3a3d41d1 in modsecurity::actions::ActionWithRunTimeString::getEvaluatedRunTimeString[abi:cxx11](modsecurity::Transaction*) const ../src/actions/action_with_run_time_string.h:53
#4 0x7f6f3a3d41d1 in modsecurity::actions::SetVar::execute(modsecurity::Transaction*) const actions/set_var.cc:50
#5 0x7f6f3a30fb56 in modsecurity::RuleWithActions::executeActionsIndependentOfChainedRuleResult(modsecurity::Transaction*) const /home/wgh/ModSecurity-upstream/src/rule_with_actions.cc:207
#6 0x7f6f3a338662 in modsecurity::RuleWithOperator::evaluate(modsecurity::Transaction*) const /home/wgh/ModSecurity-upstream/src/rule_with_operator.cc:340
#7 0x7f6f3a2de49f in modsecurity::RulesSet::evaluate(int, modsecurity::Transaction*) /home/wgh/ModSecurity-upstream/src/rules_set.cc:300
#8 0x7f6f3a290092 in modsecurity::Transaction::processRequestBody() /home/wgh/ModSecurity-upstream/src/transaction.cc:985
#9 0x56129356b4d7 in main /home/wgh/ModSecurity-upstream/test/benchmark/benchmark.cc:136
#10 0x7f6f399bf0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#11 0x56129343d67d in _start (/home/wgh/ModSecurity-upstream/test/benchmark/.libs/benchmark+0xb67d)
0x61600006f6f0 is located 112 bytes inside of 576-byte region [0x61600006f680,0x61600006f8c0)
freed by thread T0 here:
#0 0x56129352b3df in operator delete(void*) (/home/wgh/ModSecurity-upstream/test/benchmark/.libs/benchmark+0xf93df)
#1 0x7f6f3a26259b in std::default_delete<modsecurity::RuleWithActions>::operator()(modsecurity::RuleWithActions*) const /usr/include/c++/9/bits/unique_ptr.h:81
#2 0x7f6f3a26259b in std::_Sp_counted_deleter<modsecurity::RuleWithActions*, std::default_delete<modsecurity::RuleWithActions>, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/include/c++/9/bits/shared_ptr_base.h:471
#3 0x7f6f3a254204 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/9/bits/shared_ptr_base.h:155
#4 0x7f6f3a254204 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/9/bits/shared_ptr_base.h:730
#5 0x7f6f3a254204 in std::__shared_ptr<modsecurity::Rule, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/9/bits/shared_ptr_base.h:1169
#6 0x7f6f3a254204 in std::shared_ptr<modsecurity::Rule>::~shared_ptr() /usr/include/c++/9/bits/shared_ptr.h:103
#7 0x7f6f3a254204 in void std::_Destroy<std::shared_ptr<modsecurity::Rule> >(std::shared_ptr<modsecurity::Rule>*) /usr/include/c++/9/bits/stl_construct.h:98
#8 0x7f6f3a254204 in void std::_Destroy_aux<false>::__destroy<std::shared_ptr<modsecurity::Rule>*>(std::shared_ptr<modsecurity::Rule>*, std::shared_ptr<modsecurity::Rule>*) /usr/include/c++/9/bits/stl_construct.h:108
#9 0x7f6f3a254204 in void std::_Destroy<std::shared_ptr<modsecurity::Rule>*>(std::shared_ptr<modsecurity::Rule>*, std::shared_ptr<modsecurity::Rule>*) /usr/include/c++/9/bits/stl_construct.h:137
#10 0x7f6f3a254204 in void std::_Destroy<std::shared_ptr<modsecurity::Rule>*, std::shared_ptr<modsecurity::Rule> >(std::shared_ptr<modsecurity::Rule>*, std::shared_ptr<modsecurity::Rule>*, std::allocator<std::shared_ptr<modsecurity::Rule> >&) /usr/include/c++/9/bits/stl_construct.h:206
#11 0x7f6f3a254204 in std::vector<std::shared_ptr<modsecurity::Rule>, std::allocator<std::shared_ptr<modsecurity::Rule> > >::~vector() /usr/include/c++/9/bits/stl_vector.h:677
#12 0x7f6f3a254204 in modsecurity::Rules::~Rules() ../headers/modsecurity/rules.h:46
#13 0x7f6f3a254204 in std::array<modsecurity::Rules, 7ul>::~array() /usr/include/c++/9/array:94
#14 0x7f6f3a254204 in modsecurity::RulesSetPhases::~RulesSetPhases() ../headers/modsecurity/rules_set_phases.h:44
#15 0x7f6f3a254204 in modsecurity::Parser::Driver::~Driver() parser/driver.cc:36
#16 0x7f6f3a254ebe in modsecurity::Parser::Driver::~Driver() parser/driver.cc:42
#17 0x7f6f3a2e8c0e in modsecurity::RulesSet::loadFromUri(char const*) /home/wgh/ModSecurity-upstream/src/rules_set.cc:102
#18 0x56129356a41c in main /home/wgh/ModSecurity-upstream/test/benchmark/benchmark.cc:82
#19 0x7f6f399bf0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
previously allocated by thread T0 here:
#0 0x56129352a447 in operator new(unsigned long) (/home/wgh/ModSecurity-upstream/test/benchmark/.libs/benchmark+0xf8447)
#1 0x7f6f3a0d8637 in yy::seclang_parser::parse() /home/wgh/ModSecurity-upstream/src/seclang-parser.yy:1096
#2 0x7f6f3a26094b in modsecurity::Parser::Driver::parse(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) parser/driver.cc:210
#3 0x7f6f3a261abb in modsecurity::Parser::Driver::parseFile(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) parser/driver.cc:254
#4 0x7f6f3a2e8b49 in modsecurity::RulesSet::loadFromUri(char const*) /home/wgh/ModSecurity-upstream/src/rules_set.cc:95
#5 0x56129356a41c in main /home/wgh/ModSecurity-upstream/test/benchmark/benchmark.cc:82
#6 0x7f6f399bf0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
SUMMARY: AddressSanitizer: heap-use-after-free (/home/wgh/ModSecurity-upstream/src/.libs/libmodsecurity.so.3+0x285f26) in modsecurity::variables::Rule_DictElement::msg(modsecurity::Transaction*, modsecurity::RuleWithActions const*, std::vector<modsecurity::VariableValue const*, std::allocator<modsecurity::VariableValue const*> >*)
Perfect! I am going to investigate.
RunTimeStrings are strings that accept variables as macro expansions. Those variables sometimes need to have the knowledge of the Rule that it belongs (XML or RULE). In this case, a new variable instance needs to be created for every rule. That scenario became especially confused when applied to rule merging. I will try to create a new set of tests to stress the rule merging focusing on reproduce this kind of error. As collateral, this test suit will highlight if such errors appear as regression.
@WGH- ping. Can you assist me with some testing on that matter? I think we have already mitigated that issue + a couple of changes for the benefit of performance && usability. It would be wonderful if I can count on you to test this again.
I think that it is no longer an issue in v3.1-experimetal. Therefore I am closing it.
The problem was related to a broken copy constructor for an Action that wasn't updating the reference to a new rule. Leading to an invalid memory access.
Still crashes for me, although in different place now.
(gdb) where
f#0 0x00007ffff7c7fee2 in __dynamic_cast () from /lib/x86_64-linux-gnu/libstdc++.so.6
#1 0x00007ffff7ebaf1e in modsecurity::RuleWithActions::executeTransformations (this=0x555556642510,
trans=0x555564d64f70, in=..., results=std::__cxx11::list = {...}) at rule_with_actions.cc:386
#2 0x00007ffff7eb2c32 in modsecurity::RuleWithOperator::evaluate (this=<optimized out>, trans=<optimized out>)
at rule_with_operator.cc:302
#3 0x00007ffff7ebd353 in modsecurity::RulesSet::evaluate (this=0x5555555bccd0, phase=<optimized out>,
t=0x555564d64f70) at rules_set.cc:300
#4 0x00007ffff7ecd724 in modsecurity::Transaction::processRequestBody (this=0x555564d64f70) at transaction.cc:956
#5 0x0000555555559b14 in process_transaction (it=0x7fffffffe1c0, modsecTransaction=0x555564d64f70, j=...)
at benchmark.cc:103
#6 main (argc=<optimized out>, argv=<optimized out>) at benchmark.cc:194
(gdb) frame 1
#1 0x00007ffff7ebaf1e in modsecurity::RuleWithActions::executeTransformations (this=0x555556642510,
trans=0x555564d64f70, in=..., results=std::__cxx11::list = {...}) at rule_with_actions.cc:386
386 if (dynamic_cast<actions::transformations::None *>(t)) {
(gdb) l
381 // FIXME: It can't be something different from transformation. Sort this
382 // on rules compile time.
383 auto range = trans->m_rules->m_exceptions.m_action_transformation_update_target_by_id.equal_range(m_ruleId);
384 for (auto it = range.first; it != range.second; ++it) {
385 Transformation *t = it->second.get();
386 if (dynamic_cast<actions::transformations::None *>(t)) {
387 none++;
388 }
389 }
390
8aa3e3439d697e70b09b029b85e8a83a6edb7bc4 is the first certainly bad commit, and 67b08dfe43633e63dfe6d1e386879519d634185f crashes due to apparently unrelated problem:
#0 modsecurity::RuleWithActions::getMessage[abi:cxx11](modsecurity::Transaction*) const (
this=this@entry=0x5555557ae4c0, t=t@entry=0x555564ceca60) at rule_with_actions.cc:496
#1 0x00007ffff7e5b164 in modsecurity::variables::Rule_DictElement::msg (t=0x555564ceca60, rule=0x5555557ae4c0,
l=0x7fffffffc710) at /usr/include/c++/9/bits/shared_ptr_base.h:1312
#2 0x00007ffff7e5b3dc in non-virtual thunk to modsecurity::variables::Rule_DictElement::evaluate(modsecurity::Transaction*, std::vector<modsecurity::VariableValue const*, std::allocator<modsecurity::VariableValue const*> >*) ()
at ../src/variables/rule.h:165
#3 0x00007ffff7eb6414 in modsecurity::RunTimeString::evaluate[abi:cxx11](modsecurity::Transaction*) (
this=0x5555557af760, transaction=transaction@entry=0x555564ceca60)
at /usr/include/c++/9/bits/shared_ptr_base.h:1309
#4 0x00007ffff7ee4c21 in modsecurity::actions::ActionWithRunTimeString::getEvaluatedRunTimeString[abi:cxx11](modsecurity::Transaction*) const (transaction=0x555564ceca60, this=0x5555557af6f0) at /usr/include/c++/9/bits/unique_ptr.h:721
#5 modsecurity::actions::SetVar::execute (this=0x5555557af6f0, rule=<optimized out>, t=0x555564ceca60)
at actions/set_var.cc:50
#6 0x00007ffff7eb9e62 in modsecurity::RuleWithActions::executeActionsIndependentOfChainedRuleResult (
this=this@entry=0x55555660f690, trans=trans@entry=0x555564ceca60) at rule_with_actions.cc:260
#7 0x00007ffff7ec19e7 in modsecurity::RuleWithOperator::evaluate (this=0x55555660f690, trans=0x555564ceca60)
at rule_with_operator.cc:340
#8 0x00007ffff7eae1d6 in modsecurity::RulesSet::evaluate (this=0x5555555bccd0, phase=phase@entry=3,
t=t@entry=0x555564ceca60) at /usr/include/c++/9/bits/shared_ptr_base.h:1309
#9 0x00007ffff7e9cdc3 in modsecurity::Transaction::processRequestBody (this=0x555564ceca60) at transaction.cc:964
#10 0x0000555555559b4c in process_transaction (it=0x7fffffffe1c0, modsecTransaction=0x555564ceca60, j=...)
at benchmark.cc:103
#11 main (argc=<optimized out>, argv=<optimized out>) at benchmark.cc:194
Thanks @WGH-, is this the benchmark utility for ModSec? Which rules are loaded?
Good to know that it is in a different piece of the code, it means that we have solve the first issue.
No, it's my own private code. I'll try to give you either good ASAN diagnostic, or a simple test case, hopefully tomorrow UTCish.
On October 30, 2020 1:35:04 AM GMT+03:00, Felipe Zimmerle notifications@github.com wrote:
Thanks @WGH-, is this the benchmark utility for ModSec?
No, it's my own private code. I'll try to give you either good ASAN diagnostic, or a simple test case, hopefully tomorrow UTCish. … On October 30, 2020 1:35:04 AM GMT+03:00, Felipe Zimmerle @.*> wrote: Thanks @WGH-, is this the benchmark** utility for ModSec?
perfect! thank you. I am merging some other stuff that should not impact with those.
Making asan give proper diagnostics was harder than I expected. Turned out I have to
1) compile C++ library itself with asan, otherwise non-instrumented dynamic_cast
implementation crashes without giving proper diagnostics,
2) increase quarantine memory size with ASAN_OPTIONS=quarantine_size_mb=4096
.
==3085008==ERROR: AddressSanitizer: heap-use-after-free on address 0x6060000f50c0 at pc 0x7ff1432b53fd bp 0x7ffdc1a1d630 sp 0x7ffdc1a1d628
READ of size 8 at 0x6060000f50c0 thread T0
#0 0x7ff1432b53fc in __dynamic_cast /home/wgh/prefix_test/usr/src/debug/sys-libs/libcxxabi-11.0.0/libcxxabi/src/private_typeinfo.cpp:618:21
#1 0x7ff1438a59f7 in modsecurity::RuleWithActions::executeTransformations(modsecurity::Transaction*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::list<modsecurity::TransformationResult, std::__1::allocator<modsecurity::TransformationResult> >&) const /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rule_with_actions.cc:386:13
#2 0x7ff1438bc8e3 in modsecurity::RuleWithOperator::evaluate(modsecurity::Transaction*) const /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rule_with_operator.cc:302:13
#3 0x7ff14388c9f0 in modsecurity::RulesSet::evaluate(int, modsecurity::Transaction*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rules_set.cc:300:19
#4 0x7ff143865eb0 in modsecurity::Transaction::processRequestBody() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/transaction.cc:956:20
#5 0x408280 in process_transaction(nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&, modsecurity::Transaction*, modsecurity::ModSecurityIntervention_t*) /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:103:24
#6 0x408280 in main /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:194:13
#7 0x7ff142f1fe09 in __libc_start_main (/home/wgh/prefix_test/lib64/libc.so.6+0x23e09)
#8 0x406879 in _start (/home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark+0x406879)
0x6060000f50c0 is located 0 bytes inside of 64-byte region [0x6060000f50c0,0x6060000f5100)
freed by thread T0 here:
#0 0x7ff143d1bb0d in operator delete(void*) (/home/wgh/prefix_test/usr/lib/clang/11.0.0/lib/linux/libclang_rt.asan-x86_64.so+0xcdb0d)
#1 0x7ff14390ebf5 in modsecurity::actions::transformations::None::~None() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/../src/actions/transformations/none.h:34:7
#2 0x7ff1437f0de4 in std::__1::default_delete<modsecurity::actions::Action>::operator()(modsecurity::actions::Action*) const /home/wgh/prefix_test/usr/include/c++/v1/memory:2262:5
#3 0x7ff1437fadea in std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >::reset(modsecurity::actions::Action*) /home/wgh/prefix_test/usr/include/c++/v1/memory:2517:7
#4 0x7ff1437bfe3a in std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >::~unique_ptr() /home/wgh/prefix_test/usr/include/c++/v1/memory:2471:19
#5 0x7ff1437eec8b in std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > >::destroy(std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >*) /home/wgh/prefix_test/usr/include/c++/v1/memory:1811:92
#6 0x7ff1437eec78 in void std::__1::allocator_traits<std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >::__destroy<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > >(std::__1::integral_constant<bool, true>, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > >&, std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >*) /home/wgh/prefix_test/usr/include/c++/v1/memory:1703:21
#7 0x7ff1437eec68 in void std::__1::allocator_traits<std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >::destroy<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > >(std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > >&, std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >*) /home/wgh/prefix_test/usr/include/c++/v1/memory:1544:14
#8 0x7ff1437eec16 in std::__1::__vector_base<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >::__destruct_at_end(std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >*) /home/wgh/prefix_test/usr/include/c++/v1/vector:428:9
#9 0x7ff1437eeb8b in std::__1::__vector_base<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >::clear() /home/wgh/prefix_test/usr/include/c++/v1/vector:371:29
#10 0x7ff1437ee9dc in std::__1::__vector_base<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >::~__vector_base() /home/wgh/prefix_test/usr/include/c++/v1/vector:465:9
#11 0x7ff1437ee905 in std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >::~vector() /home/wgh/prefix_test/usr/include/c++/v1/vector:557:5
#12 0x7ff1437ee8c5 in std::__1::default_delete<std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > > >::operator()(std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >*) const /home/wgh/prefix_test/usr/include/c++/v1/memory:2262:5
#13 0x7ff1437ee7ea in std::__1::unique_ptr<std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >, std::__1::default_delete<std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > > > >::reset(std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >*) /home/wgh/prefix_test/usr/include/c++/v1/memory:2517:7
#14 0x7ff1437a4dda in std::__1::unique_ptr<std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > >, std::__1::default_delete<std::__1::vector<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> >, std::__1::allocator<std::__1::unique_ptr<modsecurity::actions::Action, std::__1::default_delete<modsecurity::actions::Action> > > > > >::~unique_ptr() /home/wgh/prefix_test/usr/include/c++/v1/memory:2471:19
#15 0x7ff143798c09 in yy::seclang_parser::parse() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/seclang-parser.yy:1515:13
#16 0x7ff14384f4ca in modsecurity::Parser::Driver::parse(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/parser/driver.cc:213:22
#17 0x7ff14384f904 in modsecurity::Parser::Driver::parseFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/parser/driver.cc:257:12
#18 0x7ff143889725 in modsecurity::RulesSet::loadFromUri(char const*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rules_set.cc:95:17
#19 0x406faf in main /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:180:16
#20 0x7ff142f1fe09 in __libc_start_main (/home/wgh/prefix_test/lib64/libc.so.6+0x23e09)
previously allocated by thread T0 here:
#0 0x7ff143d1b2ad in operator new(unsigned long) (/home/wgh/prefix_test/usr/lib/clang/11.0.0/lib/linux/libclang_rt.asan-x86_64.so+0xcd2ad)
#1 0x7ff1437924f7 in yy::seclang_parser::parse() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/seclang-parser.yy:2928:9
#2 0x7ff14384f4ca in modsecurity::Parser::Driver::parse(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/parser/driver.cc:213:22
#3 0x7ff14384f904 in modsecurity::Parser::Driver::parseFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/parser/driver.cc:257:12
#4 0x7ff143889725 in modsecurity::RulesSet::loadFromUri(char const*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rules_set.cc:95:17
#5 0x406faf in main /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:180:16
#6 0x7ff142f1fe09 in __libc_start_main (/home/wgh/prefix_test/lib64/libc.so.6+0x23e09)
Thank you @WGH-. Your feedback is very important. Going to approach that with a different perspective.
Ideally (long-term-plan) we won't have t:none computation on runtime. Rather we want to have it pre-computed on load time. Only exception if the action was added in run-time. The issues that you are facing is maybe a collateral from a middle state. Let me finish that t:none pre-computation and I will ask you to test again.
It can be easily reproduced with test/benchmark/benchmark.cc
and the following rule set:
SecRule ARGS "@rx foo" "id:1"
SecRuleUpdateActionById 1 "t:none,t:utf8toUnicode,t:urlDecodeUni"
This is the fix:
diff --git a/src/rules_exceptions.cc b/src/rules_exceptions.cc
index 97bbdc2e..07623e9c 100644
--- a/src/rules_exceptions.cc
+++ b/src/rules_exceptions.cc
@@ -46,7 +46,7 @@ bool RulesExceptions::loadUpdateActionById(RuleId id,
}
if (dynamic_cast<actions::transformations::Transformation *>(a.get())) {
- actions::transformations::Transformation *t = dynamic_cast<actions::transformations::Transformation *>(a.get());
+ actions::transformations::Transformation *t = dynamic_cast<actions::transformations::Transformation *>(a.release());
m_action_transformation_update_target_by_id.emplace(
std::pair<RuleId,
std::unique_ptr<actions::transformations::Transformation>>(id, std::unique_ptr<actions::transformations::Transformation>(t))
Hi @WGH- ,
I came to the same conclusion as you a few minutes ago.
The current code essentially results in two different unique_ptr smart pointers 'owning' the same object.
thank you guys 👍
Fixing that, I have a null pointer dereference crash now:
0x00007ffff71e7d0e in modsecurity::RuleWithActions::executeActionsAfterFullMatch(modsecurity::Transaction*) const::$_2::operator()(modsecurity::Transaction*, std::__1::shared_ptr<modsecurity::actions::ActionWithExecution> const&) const (this=<optimized out>, t=0x624000182100, a=...)
at rule_with_actions.cc:322
322 a->execute(t);
I debugged it a bit, and addGenericMatchAction(std::dynamic_pointer_cast<ActionWithExecution>(a));
line in RuleWithActions::RuleWithActions
seems to insert a null pointer into the m_executeIfMatchActions;
list.
Here's a rule set to reproduce it with test/benchmark/benchmark
:
SecRule REQBODY_PROCESSOR "!@rx (?:URLENCODED|MULTIPART|XML|JSON)" \
"id:901340,\
ctl:forceRequestBodyVariable=On
Hi @WGH- ,
Thanks for calling attention to that additional problem. I can reproduce it, and I think I see the underlying problem.
Just to note that this most recent issue also applies to ctl:auditEngine
Regarding the v3.1-experimental crashes caused by ctl:forceRequestBodyVariable and ctl:auditEngine ...
These have never been properly supported in v3, and yet in current v3 code, the standard '... is not supported ...' error behaviour is not triggered. The crash in v3.1 is merely a more obvious symptom than failing silently.
The immediate plan is to have both of these actions trigger the standard '... is not supported ...' error behaviour (like sanitiseArg, and others currently do)
Both crashed are addressed by @martinhsv, under QA here - https://travis-ci.org/github/SpiderLabs/ModSecurity/builds/741862888
It no longer crashes in my benchmark :+1:
It seems to leak memory like crazy though, which is also reported by ASAN (to clarify, it certainly leaks without ASAN as well). It's not just one-time leak on e.g. rule loading, the memory consumption keeps increasing with every processed transaction.
The commit I tested is 15fc0481f17d0527b54dca7987041cdc633c05e5
There's lots of leaks with slightly different stack traces, but I believe these are the three main types:
Direct leak of 58575456 byte(s) in 406774 object(s) allocated from:
#0 0x7fef091032ad in operator new(unsigned long) (/home/wgh/prefix_test/usr/lib/clang/11.0.0/lib/linux/libclang_rt.asan-x86_64.so+0xcd2ad)
#1 0x7fef08c2d4bb in modsecurity::TransactionRuleMessageManagement::messageNew() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/transaction.cc:88:31
#2 0x7fef08c2d303 in modsecurity::TransactionRuleMessageManagement::logMatchLastRuleOnTheChain(modsecurity::RuleWithActions const*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/transaction.cc:83:9
#3 0x7fef08c9170a in modsecurity::RuleWithOperator::evaluate(modsecurity::Transaction*) const /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rule_with_operator.cc:400:12
#4 0x7fef08c60d30 in modsecurity::RulesSet::evaluate(int, modsecurity::Transaction*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rules_set.cc:300:19
#5 0x7fef08c3240d in modsecurity::Transaction::processRequestHeaders() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/transaction.cc:593:20
#6 0x407f47 in process_transaction(nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&, modsecurity::Transaction*, modsecurity::ModSecurityIntervention_t*) /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:87:24
#7 0x407f47 in main /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:194:13
#8 0x7fef082e4e09 in __libc_start_main (/home/wgh/prefix_test/lib64/libc.so.6+0x23e09)
Direct leak of 9914352 byte(s) in 413098 object(s) allocated from:
#0 0x7fef091032ad in operator new(unsigned long) (/home/wgh/prefix_test/usr/lib/clang/11.0.0/lib/linux/libclang_rt.asan-x86_64.so+0xcd2ad)
#1 0x7fef08c8e507 in modsecurity::RuleWithOperator::updateMatchedVars(modsecurity::Transaction*, modsecurity::VariableValue const*, bpstd::basic_string_view<char, std::__1::char_traits<char> > const&) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rule_with_operator.cc:85:30
#2 0x7fef08c91082 in modsecurity::RuleWithOperator::evaluate(modsecurity::Transaction*) const /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rule_with_operator.cc:357:21
#3 0x7fef08c60d30 in modsecurity::RulesSet::evaluate(int, modsecurity::Transaction*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rules_set.cc:300:19
#4 0x7fef08c3240d in modsecurity::Transaction::processRequestHeaders() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/transaction.cc:593:20
#5 0x407f47 in process_transaction(nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&, modsecurity::Transaction*, modsecurity::ModSecurityIntervention_t*) /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:87:24
#6 0x407f47 in main /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:194:13
#7 0x7fef082e4e09 in __libc_start_main (/home/wgh/prefix_test/lib64/libc.so.6+0x23e09)
Direct leak of 6208800 byte(s) in 258700 object(s) allocated from:
#0 0x7fef091032ad in operator new(unsigned long) (/home/wgh/prefix_test/usr/lib/clang/11.0.0/lib/linux/libclang_rt.asan-x86_64.so+0xcd2ad)
#1 0x7fef08ba3a05 in modsecurity::VariableValue::VariableValue(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/../headers/modsecurity/variable_value.h:101:17
#2 0x7fef08ba391e in std::__1::__compressed_pair_elem<modsecurity::VariableValue, 1, false>::__compressed_pair_elem<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&, 0ul, 1ul>(std::__1::piecewise_construct_t, std::__1::tuple<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&>, std::__1::__tuple_indices<0ul, 1ul>) /home/wgh/prefix_test/usr/include/c++/v1/memory:2113:9
#3 0x7fef08ba3318 in std::__1::__compressed_pair<std::__1::allocator<modsecurity::VariableValue>, modsecurity::VariableValue>::__compressed_pair<std::__1::allocator<modsecurity::VariableValue>&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&>(std::__1::piecewise_construct_t, std::__1::tuple<std::__1::allocator<modsecurity::VariableValue>&>, std::__1::tuple<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&>) /home/wgh/prefix_test/usr/include/c++/v1/memory:2197:9
#4 0x7fef08ba2cc6 in std::__1::__shared_ptr_emplace<modsecurity::VariableValue, std::__1::allocator<modsecurity::VariableValue> >::__shared_ptr_emplace<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*>(std::__1::allocator<modsecurity::VariableValue>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&) /home/wgh/prefix_test/usr/include/c++/v1/memory:3470:16
#5 0x7fef08ba0a6b in std::__1::enable_if<!(is_array<modsecurity::VariableValue>::value), std::__1::shared_ptr<modsecurity::VariableValue> >::type std::__1::make_shared<modsecurity::VariableValue, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*&&) /home/wgh/prefix_test/usr/include/c++/v1/memory:4291:26
#6 0x7fef08ba0544 in modsecurity::variables::VariableModificatorCount::evaluate(modsecurity::Transaction const*, std::__1::vector<std::__1::shared_ptr<modsecurity::VariableValue const>, std::__1::allocator<std::__1::shared_ptr<modsecurity::VariableValue const> > >*) const /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/../src/variables/variable.h:776:22
#7 0x7fef08c90abb in modsecurity::RuleWithOperator::evaluate(modsecurity::Transaction*) const /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rule_with_operator.cc:284:14
#8 0x7fef08c60d30 in modsecurity::RulesSet::evaluate(int, modsecurity::Transaction*) /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/rules_set.cc:300:19
#9 0x7fef08c3240d in modsecurity::Transaction::processRequestHeaders() /home/wgh/prefix_test/modsec/ModSecurity-upstream/src/transaction.cc:593:20
#10 0x407f47 in process_transaction(nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&, modsecurity::Transaction*, modsecurity::ModSecurityIntervention_t*) /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:87:24
#11 0x407f47 in main /home/wgh/prefix_test/modsec/modsecurity-benchmark/benchmark.cc:194:13
#12 0x7fef082e4e09 in __libc_start_main (/home/wgh/prefix_test/lib64/libc.so.6+0x23e09)
Thanks @WGH- for raising those memory leaks.
I have created a pull request (https://github.com/SpiderLabs/ModSecurity/pull/2446) for the first of the three that you mention.
notice that there is a memory leak on the matched_vars as stated here: #2428
Yes that's right. The 2nd of 3 mentioned by WGH- on Nov. 6 is a duplicate of the '// FIXME: Memory leak.' cited in: #2428
all issues reported here are now mitigated -- 587cbf39153fccb9cd129433609835fa83064bc4
You forgot to add some headers to pkginclude_HEADERS
list. Namely, anchored_set_variable_match_vars.h
, anchored_set_variable_match_vars_names.h
, and anchored_variable_match_var_name.h
.
The last leak is still not fixed. The first constructor of VariableValue
doesn't free the pointer stored in m_value
.
explicit VariableValue(const std::string *collection,
const std::string *value = nullptr)
: m_origin(),
m_value(new std::string(value != nullptr?*value:"")), // FIXME: no need for to copy here.
m_valueHolder(nullptr),
m_key(nullptr),
m_keyHolder(nullptr),
m_collection(new std::string(*collection)) // FIXME: no need for to copy here.
{ };
Thank you @WGH- going to look into those.
@WGH-
New VariableValue constructor - https://github.com/SpiderLabs/ModSecurity/blob/5f38e37f73f151db49e3f34981adc96d03633889/headers/modsecurity/variable_value.h#L98-L108
_pkgincludeHEADERS - https://github.com/SpiderLabs/ModSecurity/blob/5f38e37f73f151db49e3f34981adc96d03633889/src/Makefile.am#L37-L57
There are some others concerns being discussed on #2469. I will report here whenever we have a concrete position, meaning: testable branch.
Trying to have make check-valgrind
as a manual action on GitHub workflows. The idea of having it on a branch is not pleasing GitHub.
@zimmerle hey, do you need ASAN memory leak output regarding rules loading? I believe I had quite a lot of reported leaks related to it as well. I didn't show them because per-transaction memory leaks were more concerning at that moment.
@WGH- It will be amazing if you manage to share those. We want to mitigate all those memory related issues. Even small leaks can be very annoying depending on the deployment. thanks!
Commit 81dcf2947d1d1fd757c2cf0d8a39be98605226bf. I didn't do any analysis, but if you need assistance with debugging or fixing, please ping me.
@WGH-,
Thank you for providing the asan output; I have changed the Makefiles.am a bit to make easier to use compilation flags via _CXXFLAGS, such as -fsanitize=address -fno-omit-frame-pointer
(b7d616a40ce186e6879ec3a0d59247002ede7377) -
Remove all the leaks from the parser was an interesting task :100: ; In details -
All those changes are packed here: b345d1a5a94668e0289efcd65261f27012b9d462 and effectively solve anything related to #2381
An inclusion of a file that does not exist will lead to a leak, but, I don't want to hold that patch due to a very unlikely scenario with a minimal leak size. We can left that for the next cleanup.
I guess we are safe fro another round of tests ;) :rocket: :rocket: :rocket:
Yep, no leaks! 👍🏻
Thank you for providing the asan output; I have changed the Makefiles.am a bit to make easier to use compilation flags via CXX_FLAGS, such as -fsanitize=address -fno-omit-frame-pointer
Interesting, because I didn't have any problems with enabling sanitizers. Well, caused by ModSecurity build scripts, at least :)
Yep, no leaks! 👍🏻
Good to hear :star_struck: :nerd_face: :star_struck: :nerd_face: :star_struck:
Interesting, because I didn't have any problems with enabling sanitizers. Well, caused by ModSecurity build scripts, at least :)
I will try to have the leak check on our QA as well ;) 💯 :sunglasses:
Hello, @zimmerle! I debugged crashes found when testing https://github.com/SpiderLabs/ModSecurity/pull/2374#issuecomment-665990756 a bit.
I found two trivial bugs, here are the patches for them:
And not-so-trivial UAFs which I was unable to decipher. "freed by" and "allocated by" look really wrong here.
I think the problem might be due to incorrect use of smart pointers somewhere where the rules are parsed. For example, I found the following problematic fragment, but that wasn't enough to fix the problem.
Maybe
addAction
is also wrong, but it's hard to tell since the incoming argument is not a smart pointer.