Skip to content
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

Memory leak if builded modsecurity with --enable-pcre-study #610

Closed
weliu opened this issue Dec 6, 2013 · 13 comments
Closed

Memory leak if builded modsecurity with --enable-pcre-study #610

weliu opened this issue Dec 6, 2013 · 13 comments
Assignees
Labels
2.x Related to ModSecurity version 2.x enhancement pending feedback
Milestone

Comments

@weliu
Copy link

weliu commented Dec 6, 2013

In apache2/msc_pcre.c line 68-74, we will get regex->pe from pcre_study() if we compile with --enable-pcre-study.

#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
pe = pcre_study(regex->re, PCRE_STUDY_JIT_COMPILE, &errptr);
#else
pe = pcre_study(regex->re, 0, &errptr);
#endif
#endif

However we will use pcre_free() or free() to free it in msc_pcre_cleanup(), the right choice here is pcre_free_study().
if (regex->pe != NULL) {

if defined(VERSION_NGINX)

        pcre_free(regex->pe);

else

        free(regex->pe);

endif

        regex->pe = NULL;
    }

This will lead to memory leak, the memory that leaked was allocated use SLJIT_MALLOC in pcre_study().

This can be a big problem if we use Apache graceful restart.

@kipras
Copy link

kipras commented Aug 17, 2017

Yes, this is a leak. In my case (ModSecurity under nginx) memory was ok (no leak) under Ubuntu 14.04, but when using under Ubuntu 16.04 - nginx would leak memory with every reload.

Applying this change fixed it. Thank you @weliu.

@weliu
Copy link
Author

weliu commented Aug 19, 2017

Glad it helped. @kipras

@marcstern
Copy link
Contributor

Adding the following code, right before the "if (regex->pe != NULL)" block, should do the work:
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
if (regex->pe != NULL) {
pcre_free_study(regex->pe);
regex->pe = NULL;
}
#endif
#endif

Correct?

@weliu
Copy link
Author

weliu commented Aug 22, 2017

I think the '#ifdef WITH_PCRE_JIT' should be removed. Only insert below code before "if (regex->pe != NULL)" block:

#ifdef WITH_PCRE_STUDY
if (regex->pe != NULL) {
pcre_free_study(regex->pe);
regex->pe = NULL;
}
#endif

Moreover, I think we should check the return value of pcre_study() in msc_pregcomp_ex():

#ifdef WITH_PCRE_STUDY
    #ifdef WITH_PCRE_JIT
            pe = pcre_study(regex->re, PCRE_STUDY_JIT_COMPILE, &errptr);
    #else
            pe = pcre_study(regex->re, 0, &errptr);
    #endif
            /* check pe here */
#endif

@marcstern
Copy link
Contributor

In case WITH_PCRE_JIT is not defined, pe is allocated with pcre_malloc() or malloc().
Freeing it with pcre_free_study() doesn't look safe...

Additional question: why using malloc() instead of pcre_malloc() under Apache?
Or should we use the memory pool instead?

@kipras
Copy link

kipras commented Aug 22, 2017

Here's how the fixed block looks like in my case:

        if (regex->pe != NULL) {
#if defined(VERSION_NGINX)
#ifdef WITH_PCRE_STUDY
            pcre_free_study(regex->pe);
#else
            pcre_free(regex->pe);
#endif
#else
            free(regex->pe);
#endif
            regex->pe = NULL;
        }

The previous unpatched version that leaked memory was:

        if (regex->pe != NULL) {
#if defined(VERSION_NGINX)
            pcre_free(regex->pe);
#else
            free(regex->pe);
#endif
            regex->pe = NULL;
        }

Although, now that i look at it, it should probably be

        if (regex->pe != NULL) {
#ifdef WITH_PCRE_STUDY
            pcre_free_study(regex->pe);
#else
#if defined(VERSION_NGINX)
            pcre_free(regex->pe);
#else
            free(regex->pe);
#endif
#endif
            regex->pe = NULL;
        }

Or is pcre_free_study() only necessary in nginx and free() is fine under apache?

Checking pe after pcre_study() call is also a good idea. If it returns NULL we would call the wrong deallocator here.

@marcstern
Copy link
Contributor

Checking pe after pcre_study() call is definitely a good idea

@weliu
Copy link
Author

weliu commented Aug 23, 2017

kipras's modification looks more reasonable to me.

@victorhora victorhora added the 2.x Related to ModSecurity version 2.x label Sep 5, 2018
@victorhora victorhora added this to the v2.9.3 milestone Sep 25, 2018
@victorhora
Copy link
Contributor

One thing worth mentioning is that the --enable-pcre-study configuration flag seems to be enabled by default on v2 since ModSec 2.5. The changelog entry from 2.5.12 also suggests it's the case.

My tests with Valgring suggests that the proposed patches actually makes the memory leak more noticeable.

My test environment was the following:

Linux debian 3.16.0-4-amd64 #1 SMP Debian 3.16.43-2+deb8u2 (2017-06-26) x86_64 GNU/Linux

Server version: Apache/2.4.10 (Debian)
Server built:   Mar 31 2018 09:39:03
Server's Module Magic Number: 20120211:37
Server loaded:  APR 1.5.1, APR-UTIL 1.5.4
Compiled using: APR 1.5.1, APR-UTIL 1.5.4
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256

ModSec flags:
./configure --with-yajl=no --with-ssdeep=no --with-lua=no --with-curl=no --enable-pcre-study --enable-pcre-jit

Apache/ModSec Configuration:

LoadModule security2_module /root/ModSecurity-v2/apache2/.libs/mod_security2.so
SecRuleEngine On
SecRequestBodyAccess On
SecStreamInBodyInspection On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyInMemoryLimit 131072
SecRequestBodyLimitAction Reject
SecPcreMatchLimit 100000
SecPcreMatchLimitRecursion 100000
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
SecTmpDir /tmp/
SecDataDir /tmp/
SecDebugLog /var/log/apache2/modsec_debug.log
SecDebugLogLevel 9
SecAuditEngine On
SecAuditLogParts ABCDEFHIJZ
SecAuditLogType Serial
SecAuditLog /var/log/apache2/modsec_audit.log
SecArgumentSeparator &
SecCookieFormat 0
SecStatusEngine On

IncludeOptional /root/owasp-modsecurity-crs-3.0/crs-setup.conf
IncludeOptional /root/owasp-modsecurity-crs-3.0/rules/*.conf

Request generated with Apache Benchmark:

ab -n 1000 -c 100 http://localhost/

Memory check:

valgrind -v --log-file=valgrind1-apache-modsec-attached-CRS-9-JIT-study-patch0-ab-1000.log --trace-children=yes --leak-check=full apache2 -k start

This is a snippet of the leak summaries I got when running Apache Benchmark + CRS with the patch applied:

==85695== LEAK SUMMARY:
==85695==    definitely lost: 461,298 bytes in 1,548 blocks
==85695==    indirectly lost: 30,992 bytes in 114 blocks
==85695==      possibly lost: 0 bytes in 0 blocks
==85695==    still reachable: 4,325 bytes in 11 blocks
==85695==         suppressed: 0 bytes in 0 blocks

And the same run without the patch:

==92740== LEAK SUMMARY:
==92740==    definitely lost: 74,304 bytes in 774 blocks
==92740==    indirectly lost: 30,992 bytes in 114 blocks
==92740==      possibly lost: 0 bytes in 0 blocks
==92740==    still reachable: 4,325 bytes in 11 blocks
==92740==         suppressed: 0 bytes in 0 blocks

Am I doing something wrong?

@zimmerle zimmerle modified the milestones: v2.9.3, v2.9.4 Nov 2, 2018
asterite3 added a commit to seclab-msu/ModSecurity that referenced this issue Feb 6, 2020
Function msc_pcre_cleanup(), which is responsible for freeing
compiled regex data, used either regular free() or pcre_free()
(depending on compilation settings) to free pcre_extra structure
(pointer to which is stored in regex->pe) created by pcre_study().
This was incorrect, structure returned by pcre_study() should be
freed by function pcre_free_study(). In case PCRE JIT is used,
pcre_study() makes some additional allocations itself (at least
for JITed executable code), which function pcre_free_study() frees.
If pcre_free_study() is not used a memory leak occurs because,
while pcre_extra structure itself might be freed by regular
free(), some additional data referenced by it is not.
Fix that by calling pcre_free_study() (instead of
free()/pcre_free()) on pointer returned by pcre_study().
Note that code creating msc_regex_t may allocate regex->pe with
malloc() or pcre_malloc() instead of pcre_study(). This case is
checked by testing if PCRE_EXTRA_EXECUTABLE_JIT flag on
regex->pe->flags is set. msc_pregcomp_ex() does not set that flag
itself (and it memsets the whole structure with zeros after
allocation) and pcre_free_study() actually does the same (it tests
for PCRE_EXTRA_EXECUTABLE_JIT flag, and, if that is zero, calls
pcre_free() on passed pointer).

Fixes owasp-modsecurity#610
asterite3 added a commit to seclab-msu/ModSecurity that referenced this issue Feb 16, 2020
Allocate pcre_extra structure with pcre_study or pcre_malloc
and free it with pcre_free_study(). Pass flag
PCRE_STUDY_EXTRA_NEEDED to pcre_study telling it to always
allocate pcre_extra because this structure is needed anyway to
configure match limits.
Until this change, pcre_extra was allocated with eigther
pcre_malloc or regular malloc (depending on whether VERSION_NGINX
is defined); function msc_pcre_cleanup(), which is responsible
for freeing compiled regex data, used either regular free() or
pcre_free() (depending VERSION_NGINX too) to free pcre_extra
structure (pointer to which is stored in regex->pe).
Freeing it like this was incorrect, structure returned by
pcre_study() should be freed by function pcre_free_study(). In
case PCRE JIT is used, pcre_study() makes some additional
allocations itself (at least for JITed executable code), which
function pcre_free_study() frees. If pcre_free_study() is not
used a memory leak occurs because, while pcre_extra structure
itself might be freed, some additional data referenced by it is
not. Fix that by calling pcre_free_study() (instead of
free()/pcre_free()) on pointer returned by pcre_study().
There also seems to be no reason to allocate pcre_extra with
regular malloc (and de-allocate it with free()) - there is a
function pcre_malloc(), which is a function pcre_study() itself
would use to allocate that memory, and, in default case,
pcre_malloc and pcre_free will be set to regular malloc and free.
Usage of malloc() seems to be a remaining of old code where
manual allocation of pcre_extra was always done with malloc().
So, remove "#if defined(VERSION_NGINX)" branches and always use
pcre_malloc() for pcre_extra allocation in case pcre_study did not
allocate it yet and always free is with pcre_free_study() (btw.
'pcreapi' man page recommends to replace pcre_free() usages to
deallocate pcre_extra with pcre_free_study()).

Fixes owasp-modsecurity#610
@zimmerle
Copy link
Contributor

zimmerle commented Dec 1, 2020

Closing due to no feedback.

@zimmerle zimmerle closed this as completed Dec 1, 2020
@marcstern
Copy link
Contributor

No feedback?
There's a pull request waiting: #2263
Do you close an issue because the development doesn't give any feedback?
Sorry but I cannot understand your logic!

@zimmerle
Copy link
Contributor

zimmerle commented Dec 1, 2020

Issue #2263 has a pretty write up and details the issue and how to reproduce. No need to keep this one. It will only fragment any discussion on the subject.

@marcstern
Copy link
Contributor

Then it's because it's a duplicate, OK

marcstern added a commit that referenced this issue Feb 1, 2024
airween added a commit that referenced this issue Feb 7, 2024
airween added a commit that referenced this issue Feb 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.x Related to ModSecurity version 2.x enhancement pending feedback
Projects
None yet
Development

No branches or pull requests

5 participants