owasp-modsecurity / owasp-modsecurity/ModSecurity

Memory leak in vg_replace_malloc.c with large ruleset

Open
#3,311 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

3.x
Dominant language
C++
Stars
9.8k
Forks
1.8k
Avg merge
2h 46m
Merged PRs (30d)
1

Description

I'm developer of WAF. My product can update its rules on the fly while running. I ran tests loading a large set of rules in a separately launched appication (custom developed service, which not a WAF).

Load large ruleset performed like this:

//std::string BaseRules 

auto base_ruleset = std::make_unique<modsecurity::RulesSet>();
base_ruleset->load(BaseRules.c_str());  

The application was launched using valgrind (https://valgrind.org/). The large set was loaded several times, with multiple operations to load small sets of rules between them. The leak was present even if the large set was loaded exactly once. If you remove the code that loads the large set and leave only operations with small sets, the leak disappears.

valgrind results (once cycle of tests: many small ruleset operations and one large ruleset load):

==338909== HEAP SUMMARY:
==338909==     in use at exit: 9,062 bytes in 244 blocks
==338909==   total heap usage: 51,768,316 allocs, 51,768,072 frees, 6,623,981,398 bytes allocated
==338909== 
==338909== 127 (24 direct, 103 indirect) bytes in 1 blocks are definitely lost in loss record 6 of 14
==338909==    at 0x4840F2F: operator new(unsigned long) (vg_replace_malloc.c:422)
==338909==    by 0x389E3B: allocate (new_allocator.h:137)
==338909==    by 0x389E3B: allocate (alloc_traits.h:464)
==338909==    by 0x389E3B: _M_get_node (stl_list.h:518)
==338909==    by 0x389E3B: _M_create_node<std::unique_ptr<modsecurity::RunTimeElementHolder, std::default_delete<modsecurity::RunTimeElementHolder> > > (stl_list.h:710)
==338909==    by 0x389E3B: _M_insert<std::unique_ptr<modsecurity::RunTimeElementHolder, std::default_delete<modsecurity::RunTimeElementHolder> > > (stl_list.h:2005)
==338909==    by 0x389E3B: push_back (stl_list.h:1311)
==338909==    by 0x389E3B: modsecurity::RunTimeString::appendText(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (run_time_string.cc:36)
==338909==    by 0x3AEDA2: yy::seclang_parser::parse() (seclang-parser.yy:3070)
==338909==    by 0x3885BC: 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&) (driver.cc:148)
==338909==    by 0x3211F6: modsecurity::RulesSet::load(char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (rules_set.cc:69)
==338909==    by 0x32185B: modsecurity::RulesSet::load(char const*) (rules_set.cc:100)
==338909==    by 0x2712B1: ..... (modsec_checker.cpp:45)
==338909==    by 0x23E215: .....
==338909==    by 0x258D92: .....
==338909== 
==338909== 8,543 (2,448 direct, 6,095 indirect) bytes in 34 blocks are definitely lost in loss record 14 of 14
==338909==    at 0x4840F2F: operator new(unsigned long) (vg_replace_malloc.c:422)
==338909==    by 0x3AA35E: yy::seclang_parser::parse() (seclang-parser.yy:2777)
==338909==    by 0x3885BC: 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&) (driver.cc:148)
==338909==    by 0x3211F6: modsecurity::RulesSet::load(char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (rules_set.cc:69)
==338909==    by 0x32185B: modsecurity::RulesSet::load(char const*) (rules_set.cc:100)
==338909==    by 0x2712B1: ..... (modsec_checker.cpp:45)
==338909==    by 0x23E215: .....
==338909==    by 0x2574A5: .....
==338909== 
==338909== LEAK SUMMARY:
==338909==    definitely lost: 2,472 bytes in 35 blocks
==338909==    indirectly lost: 6,198 bytes in 203 blocks
==338909==      possibly lost: 0 bytes in 0 blocks
==338909==    still reachable: 392 bytes in 6 blocks
==338909==         suppressed: 0 bytes in 0 blocks
==338909== Reachable blocks (those to which a pointer was found) are not shown.
  • ModSecurity v3.0.13
  • Main app: C++17 with boost ASIO
  • OS Debian 12 64bit
  • Valgrind-3.19.0 and LibVEX

Ruleset large_rules_raw.txt

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the Valgrind report by loading large_rules_raw.txt through RulesSet::load, then trace ownership from rules_set.cc and driver.cc into seclang-parser.yy and run_time_string.cc. Compare the allocations reported from parse() and RunTimeString::appendText() across load and destruction; done means the definite leaks no longer remain after the ruleset lifecycle completes.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.