/* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ * Copyright (c) 2004-2022 Trustwave Holdings, Inc. (http://www.trustwave.com/) * * You may not use this file except in compliance with * the License.  You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * If any of the files related to licensing are missing or if you have any * other questions related to licensing please contact Trustwave Holdings, Inc. * directly using the email address security@modsecurity.org. */ #include "msc_pcre.h" #include "apr_strings.h" /** * Releases the resources used by a single regular expression pattern. */ static apr_status_t msc_pcre_cleanup(msc_regex_t *regex) { if (regex != NULL) { #ifndef WITH_PCRE if (regex->match_context != NULL) { pcre2_match_context_free(regex->match_context); regex->match_context = NULL; } if (regex->re != NULL) { pcre2_code_free(regex->re); regex->re = NULL; } #else if (regex->pe != NULL) { pcre_free(regex->pe); regex->pe = NULL; } if (regex->re != NULL) { pcre_free(regex->re); regex->re = NULL; } #endif } return APR_SUCCESS; } /** * Compiles the provided regular expression pattern. The _err* * parameters are optional, but if they are provided and an error * occurs they will contain the error message and the offset in * the pattern where the offending part of the pattern begins. The * match_limit* parameters are optional and if >0, then will set * match limits. */ void *msc_pregcomp_ex(apr_pool_t *pool, const char *pattern, int options, const char **_errptr, int *_erroffset, int match_limit, int match_limit_recursion) #ifndef WITH_PCRE { msc_regex_t *regex = NULL; PCRE2_SPTR pcre2_pattern; uint32_t pcre2_options; int error_number = 0; PCRE2_SIZE error_offset = 0; pcre2_match_context *match_context = NULL; regex = apr_pcalloc(pool, sizeof(msc_regex_t)); if (regex == NULL) return NULL; regex->pattern = pattern; pcre2_pattern = (PCRE2_SPTR)pattern; pcre2_options = (uint32_t)options; regex->re = pcre2_compile(pcre2_pattern, PCRE2_ZERO_TERMINATED, pcre2_options, &error_number, &error_offset, NULL); if (regex->re == NULL) { if (_erroffset != NULL) { *_erroffset = (int)error_offset; } PCRE2_UCHAR buffer[256]; // Get the error message from the error code pcre2_get_error_message(error_number, buffer, sizeof(buffer)); #ifdef DEBUG_CONF * _errptr = apr_psprintf(pool, "%s - pattern = %s", buffer, pattern); #else * _errptr = apr_pstrdup(pool, buffer); #endif return NULL; } #ifdef WITH_PCRE_JIT regex->jit_compile_rc = pcre2_jit_compile(regex->re, PCRE2_JIT_COMPLETE); #endif /* Setup the pcre2 match context */ regex->match_context = NULL; match_context = pcre2_match_context_create(NULL); if (match_context == NULL) { return NULL; } /* Prefer the match limit passed as an arg; else use compilation default */ { uint32_t final_match_limit = 0; if (match_limit > 0) { final_match_limit = match_limit; pcre2_set_match_limit(match_context, final_match_limit); } #ifdef MODSEC_PCRE_MATCH_LIMIT else { final_match_limit = MODSEC_PCRE_MATCH_LIMIT; pcre2_set_match_limit(match_context, final_match_limit); } #endif /* MODSEC_PCRE_MATCH_LIMIT */ } /* Prefer the depth limit passed as an arg; else use compilation default */ { uint32_t final_match_limit_recursion = 0; if (match_limit_recursion > 0) { final_match_limit_recursion = match_limit_recursion; pcre2_set_depth_limit(match_context, final_match_limit_recursion); } #ifdef MODSEC_PCRE_MATCH_LIMIT_RECURSION else { final_match_limit_recursion = MODSEC_PCRE_MATCH_LIMIT_RECURSION; pcre2_set_depth_limit(match_context, final_match_limit_recursion); } #endif /* MODSEC_PCRE_MATCH_LIMIT_RECURSION */ } regex->match_context = match_context; apr_pool_cleanup_register(pool, (void *)regex, (apr_status_t (*)(void *))msc_pcre_cleanup, apr_pool_cleanup_null); return regex; } #else /* not WITH_PCRE2 */ { const char *errptr = NULL; int erroffset; msc_regex_t *regex; pcre_extra *pe = NULL; regex = apr_pcalloc(pool, sizeof(msc_regex_t)); if (regex == NULL) return NULL; regex->pattern = pattern; if ((_errptr == NULL)||(_erroffset == NULL)) { regex->re = pcre_compile(pattern, options, &errptr, &erroffset, NULL); } else { regex->re = pcre_compile(pattern, options, _errptr, _erroffset, NULL); } if (regex->re == NULL) return NULL; #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 /* Setup the pcre_extra record if pcre_study did not already do it */ if (pe == NULL) { pe = (pcre_extra*)pcre_malloc(sizeof(pcre_extra)); if (pe == NULL) { return NULL; } memset(pe, 0, sizeof(pcre_extra)); } #ifdef PCRE_EXTRA_MATCH_LIMIT /* If match limit is available, then use it */ /* Use ModSecurity runtime defaults */ if (match_limit > 0) { pe->match_limit = match_limit; pe->flags |= PCRE_EXTRA_MATCH_LIMIT; } #ifdef MODSEC_PCRE_MATCH_LIMIT /* Default to ModSecurity compiled defaults */ else { pe->match_limit = MODSEC_PCRE_MATCH_LIMIT; pe->flags |= PCRE_EXTRA_MATCH_LIMIT; } #endif /* MODSEC_PCRE_MATCH_LIMIT */ #else #pragma message ( "This PCRE version does not support match limits! Upgrade to at least PCRE v6.5." ) #endif /* PCRE_EXTRA_MATCH_LIMIT */ #ifdef PCRE_EXTRA_MATCH_LIMIT_RECURSION /* If match limit recursion is available, then use it */ /* Use ModSecurity runtime defaults */ if (match_limit_recursion > 0) { pe->match_limit_recursion = match_limit_recursion; pe->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION; } #ifdef MODSEC_PCRE_MATCH_LIMIT_RECURSION /* Default to ModSecurity compiled defaults */ else { pe->match_limit_recursion = MODSEC_PCRE_MATCH_LIMIT_RECURSION; pe->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION; } #endif /* MODSEC_PCRE_MATCH_LIMIT_RECURSION */ #else #pragma message ( "This PCRE version does not support match recursion limits! Upgrade to at least PCRE v6.5." ) #endif /* PCRE_EXTRA_MATCH_LIMIT_RECURSION */ regex->pe = pe; apr_pool_cleanup_register(pool, (void *)regex, (apr_status_t (*)(void *))msc_pcre_cleanup, apr_pool_cleanup_null); return regex; } #endif /* WITH_PCRE2 */ /** * Compiles the provided regular expression pattern. Calls msc_pregcomp_ex() * with default limits. */ void *msc_pregcomp(apr_pool_t *pool, const char *pattern, int options, const char **_errptr, int *_erroffset) { return msc_pregcomp_ex(pool, pattern, options, _errptr, _erroffset, 0, 0); } /** * Executes regular expression with extended options (or match context) * Returns PCRE_ERROR_NOMATCH (or PCRE2_ERROR_NOMATCH), * error code < -1 on errors, and a value > 0 when there is a match. */ int msc_regexec_ex(msc_regex_t *regex, const char *s, unsigned int slen, int startoffset, int options, int *ovector, int ovecsize, char **error_msg) { if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */ *error_msg = NULL; #ifndef WITH_PCRE { PCRE2_SPTR pcre2_s; int pcre2_ret; pcre2_match_data *match_data; PCRE2_SIZE *pcre2_ovector = NULL; pcre2_s = (PCRE2_SPTR)s; match_data = pcre2_match_data_create_from_pattern(regex->re, NULL); #ifdef WITH_PCRE_JIT if (regex->jit_compile_rc == 0) { pcre2_ret = pcre2_jit_match(regex->re, pcre2_s, slen, (PCRE2_SIZE)(startoffset), (uint32_t)options, match_data, regex->match_context); } if (regex->jit_compile_rc != 0 || pcre2_ret == PCRE2_ERROR_JIT_STACKLIMIT) { pcre2_ret = pcre2_match(regex->re, pcre2_s, slen, (PCRE2_SIZE)(startoffset), (PCRE2_NO_JIT | (uint32_t)options), match_data, regex->match_context); } #else pcre2_ret = pcre2_match(regex->re, pcre2_s, slen, (PCRE2_SIZE)(startoffset), (uint32_t)options, match_data, regex->match_context); #endif if (match_data != NULL) { if (ovector != NULL) { pcre2_ovector = pcre2_get_ovector_pointer(match_data); if (pcre2_ovector != NULL) { for (int i = 0; ((i < pcre2_ret) && ((i*2) <= ovecsize)); i++) { if ((i*2) < ovecsize) { ovector[2*i] = pcre2_ovector[2*i]; ovector[2*i+1] = pcre2_ovector[2*i+1]; } } } } pcre2_match_data_free(match_data); } if ((pcre2_ret*2) > ovecsize) { return 0; } else { return pcre2_ret; } } #else return pcre_exec(regex->re, regex->pe, s, slen, startoffset, options, ovector, ovecsize); #endif } /** * Executes regular expression, capturing subexpressions in the given * vector. Returns PCRE_ERROR_NOMATCH when there is no match, error code < -1 * on errors, and a value > 0 when there is a match. */ int msc_regexec_capture(msc_regex_t *regex, const char *s, unsigned int slen, int *ovector, int ovecsize, char **error_msg) { if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */ *error_msg = NULL; return msc_regexec_ex(regex, s, slen, 0, 0, ovector, ovecsize, error_msg); } /** * Executes regular expression but ignores any of the subexpression * captures. See above for the return codes. */ int msc_regexec(msc_regex_t *regex, const char *s, unsigned int slen, char **error_msg) { if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */ *error_msg = NULL; return msc_regexec_ex(regex, s, slen, 0, 0, NULL, 0, error_msg); } /** * Gets info on a compiled regex. */ int msc_fullinfo(msc_regex_t *regex, int what, void *where) { #ifndef WITH_PCRE return pcre2_pattern_info(regex->re, (uint32_t)what, where); #else return pcre_fullinfo(regex->re, regex->pe, what, where); #endif }