From c602ba14811ede722017c4e59e4e30d9990227b4 Mon Sep 17 00:00:00 2001 From: "William A. Rowe Jr" Date: Thu, 24 Feb 2022 22:18:42 +0000 Subject: [PATCH] Replace PCRE with PCRE2 where it is available PCRE 8.45 from May '21 is at end-of-life and will not receive security vulnerability attention. pcre2-10.x replaces this and has been updated (as of this time) as recently as Oct '21. This patch removes the needless assignment of re_erroffset in the conf pool by the worker threads; such mistakes break the shared copy-on-write pages of memory that should have remained common between all httpd worker processes. Two de-optimizations are inherent in this patch, the former ovector-on-stack opportunity is lost. This is by design of pcre2, more serious exploits were available with stack array underrun/overrun manipulation. Heap for all pcre array processing is the recommendation of the implementor, enforced by API. Safer that we either create a new general context using pool allocation in heap (requires thread-pool args we don't have in our api), or recycle a per-req, per-pool, TLS match_data buffer on heap of some arbitrary 10 elts or so, for these most common cases. Since this can't be done portably in C89 we may revisit this optimization in post-2.4 releases. This logic refuses to consider --without-pcre, which is nonsequitor. Submitted by: [wrowe, Petr Pisar , rjung] Backports: 1773454 1773741 1773742 1773839 1773870 1773882 1814662 1881478 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1898399 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 4 ++ CMakeLists.txt | 11 ++- STATUS | 34 +--------- configure.in | 43 +++++++++--- server/util_pcre.c | 162 ++++++++++++++++++++++++++++++++------------- 5 files changed, 163 insertions(+), 91 deletions(-) diff --git a/CHANGES b/CHANGES index e71733779a2..092ec73e7f7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -*- coding: utf-8 -*- Changes with Apache 2.4.53 + *) Support pcre2 (10.x) library in place of the now end-of-life pcre (8.x) + for regular expression evaluation. This depends on locating pcre2-config. + [William Rowe, Petr Pisar , Rainer Jung] + *) Add the ldap function to the expression API, allowing LDAP filters and distinguished names based on expressions to be escaped correctly to guard against LDAP injection. [Graham Leggett] diff --git a/CMakeLists.txt b/CMakeLists.txt index dfef19e8e48..ef8da3f31cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,13 @@ ENDIF() # PCRE names its libraries differently for debug vs. release builds. # We can't query our own CMAKE_BUILD_TYPE at configure time. # If the debug version exists in PREFIX/lib, default to that one. -IF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/pcred.lib") +IF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/pcre2-8d.lib") + SET(default_pcre_libraries ${CMAKE_INSTALL_PREFIX}/lib/pcre2-8d.lib) + SET(default_pcre_cflags "-DHAVE_PCRE2") +ELSEIF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/pcre2-8.lib") + SET(default_pcre_libraries ${CMAKE_INSTALL_PREFIX}/lib/pcre2-8.lib) + SET(default_pcre_cflags "-DHAVE_PCRE2") +ELSEIF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/pcred.lib") SET(default_pcre_libraries ${CMAKE_INSTALL_PREFIX}/lib/pcred.lib) ELSE() SET(default_pcre_libraries ${CMAKE_INSTALL_PREFIX}/lib/pcre.lib) @@ -75,6 +81,7 @@ SET(APR_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE STRING "Direct SET(APR_LIBRARIES ${default_apr_libraries} CACHE STRING "APR libraries to link with") SET(NGHTTP2_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE STRING "Directory with NGHTTP2 include files within nghttp2 subdirectory") SET(NGHTTP2_LIBRARIES ${default_nghttp2_libraries} CACHE STRING "NGHTTP2 libraries to link with") +SET(PCRE_CFLAGS "${default_pcre_cflags}" CACHE STRING "PCRE flags for util_pcre.c compilation") SET(PCRE_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE STRING "Directory with PCRE include files") SET(PCRE_LIBRARIES ${default_pcre_libraries} CACHE STRING "PCRE libraries to link with") SET(LIBXML2_ICONV_INCLUDE_DIR "" CACHE STRING "Directory with iconv include files for libxml2") @@ -851,7 +858,7 @@ SET(install_targets ${install_targets} libhttpd) SET(install_bin_pdb ${install_bin_pdb} $) TARGET_LINK_LIBRARIES(libhttpd ${EXTRA_LIBS} ${APR_LIBRARIES} ${PCRE_LIBRARIES} ${HTTPD_SYSTEM_LIBS}) DEFINE_WITH_BLANKS(define_long_name "LONG_NAME" "Apache HTTP Server Core") -SET_TARGET_PROPERTIES(libhttpd PROPERTIES COMPILE_FLAGS "-DAP_DECLARE_EXPORT ${define_long_name} -DBIN_NAME=libhttpd.dll ${EXTRA_COMPILE_FLAGS}") +SET_TARGET_PROPERTIES(libhttpd PROPERTIES COMPILE_FLAGS "-DAP_DECLARE_EXPORT ${define_long_name} ${PCRE_CFLAGS} -DBIN_NAME=libhttpd.dll ${EXTRA_COMPILE_FLAGS}") ADD_DEPENDENCIES(libhttpd test_char_header) ########### HTTPD EXECUTABLES ########## diff --git a/STATUS b/STATUS index bb8dda40d1e..258fa827dcf 100644 --- a/STATUS +++ b/STATUS @@ -147,38 +147,6 @@ RELEASE SHOWSTOPPERS: PATCHES ACCEPTED TO BACKPORT FROM TRUNK: [ start all new proposals below, under PATCHES PROPOSED. ] - *) Support PCRE2 (10.x) in place of PCRE (8.x). - Submitted by: wrowe, Petr Pisar [ppisar redhat.com], rjung - trunk patches: - http://svn.apache.org/r1773454 - http://svn.apache.org/r1773741 - http://svn.apache.org/r1773742 - http://svn.apache.org/r1773839 - http://svn.apache.org/r1773870 - http://svn.apache.org/r1773882 - http://svn.apache.org/r1814662 - http://svn.apache.org/r1881478 - summarized/conflicts resolved: - https://gist.github.com/wrowe/73f655d13bbe0f12030aa4557e804d8a - +1: wrowe, rpluem, ylavic - wrowe notes that the current code drops optimizations, owing to the fact - that the ovector is a required allocation and is no longer allocated on - the stack, by design. The correct fix is an apr userdata allocation on - the appropriate pool, which would be thread-safe, but the actual API of - ap_regexec[_len]() offers us no pool and isn't suitable for httpd-2.4.x. - At this time, PCRE 8.45 is EOL and will not receive security updates, - and taking ovector and other arrays off the stack was in direct reaction - to the patterns of abuse of previous pcre exploits. So this patch doesn't - wait for httpd-2.4 to be retired, it will need to be adopted without the - existing optimiation. - jorton: Adding ap_pregexec/_len which pass a pool would also work - for internal users of this api; not sure if performance - impact is significant from using malloc here. - ylavic: A follow up backport (based on this one) using thread locals to - reuse ap_regex*() contexts: https://github.com/apache/httpd/pull/289 - Proposed for backport below, in place of or after this one. - rpluem: +1 on PR 289 as well. - *) Support PCRE2 with buffers reuse through Thread Local Storage. trunk patches: http://svn.apache.org/r1897240 @@ -208,7 +176,7 @@ PATCHES ACCEPTED TO BACKPORT FROM TRUNK: 2.4.x patches: https://github.com/apache/httpd/pull/289.diff (PR: https://github.com/apache/httpd/pull/289) - +1: ylavic, rpluem, covener, steffenal + +1: ylavic, rpluem, covener, steffenal, wrowe ylavic: This backport proposal inludes the PCRE2 backport already accepted, we can apply this one instead or the original one first and then this one (I'd have to rebase the github PR first). diff --git a/configure.in b/configure.in index 831a683b052..38c1d0a64a5 100644 --- a/configure.in +++ b/configure.in @@ -214,28 +214,33 @@ fi AC_ARG_WITH(pcre, APACHE_HELP_STRING(--with-pcre=PATH,Use external PCRE library)) - -AC_PATH_PROG(PCRE_CONFIG, pcre-config, false) -if test -d "$with_pcre" && test -x "$with_pcre/bin/pcre-config"; then - PCRE_CONFIG=$with_pcre/bin/pcre-config -elif test -x "$with_pcre"; then - PCRE_CONFIG=$with_pcre +if test "x$with_pcre" = "x" || test "$with_pcre" = "yes"; then + with_pcre="$PATH" +else if which $with_pcre 2>/dev/null; then :; else + with_pcre="$with_pcre/bin:$with_pcre" +fi fi -if test "$PCRE_CONFIG" != "false"; then +AC_CHECK_TARGET_TOOLS(PCRE_CONFIG, [pcre2-config pcre-config], + [`which $with_pcre 2>/dev/null`], $with_pcre) + +if test "x$PCRE_CONFIG" != "x"; then if $PCRE_CONFIG --version >/dev/null 2>&1; then :; else - AC_MSG_ERROR([Did not find pcre-config script at $PCRE_CONFIG]) + AC_MSG_ERROR([Did not find working script at $PCRE_CONFIG]) fi case `$PCRE_CONFIG --version` in + [1[0-9].*]) + AC_DEFINE(HAVE_PCRE2, 1, [Detected PCRE2]) + ;; [[1-5].*]) AC_MSG_ERROR([Need at least pcre version 6.0]) ;; esac AC_MSG_NOTICE([Using external PCRE library from $PCRE_CONFIG]) APR_ADDTO(PCRE_INCLUDES, [`$PCRE_CONFIG --cflags`]) - APR_ADDTO(PCRE_LIBS, [`$PCRE_CONFIG --libs`]) + APR_ADDTO(PCRE_LIBS, [`$PCRE_CONFIG --libs8 2>/dev/null || $PCRE_CONFIG --libs`]) else - AC_MSG_ERROR([pcre-config for libpcre not found. PCRE is required and available from http://pcre.org/]) + AC_MSG_ERROR([pcre(2)-config for libpcre not found. PCRE is required and available from http://pcre.org/]) fi APACHE_SUBST(PCRE_LIBS) @@ -263,6 +268,24 @@ APR_ADDTO(INCLUDES, $APU_INCLUDES) dnl Add in path to PCRE includes APR_ADDTO(INCLUDES, $PCRE_INCLUDES) +save_CPPFLAGS="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $PCRE_INCLUDES" +AC_EGREP_CPP(yes, +[ +#ifdef HAVE_PCRE2 +yes +#else +#include +#ifdef PCRE_DUPNAMES +yes +#endif +#endif +],pcre_have_dupnames=yes,pcre_have_dupnames=no) +if test "$pcre_have_dupnames" != "yes"; then + AC_MSG_ERROR([pcre version does not support PCRE_DUPNAMES]) +fi +CPPFLAGS="$save_CPPFLAGS" + AC_MSG_NOTICE([]) AC_MSG_NOTICE([Applying OS-specific hints for httpd...]) AC_MSG_NOTICE([]) diff --git a/server/util_pcre.c b/server/util_pcre.c index 78fc983536d..aa0b442d0ea 100644 --- a/server/util_pcre.c +++ b/server/util_pcre.c @@ -55,10 +55,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "httpd.h" #include "apr_strings.h" #include "apr_tables.h" + +#ifdef HAVE_PCRE2 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include "pcre2.h" +#define PCREn(x) PCRE2_ ## x +#else #include "pcre.h" +#define PCREn(x) PCRE_ ## x +#endif /* PCRE_DUPNAMES is only present since version 6.7 of PCRE */ -#ifndef PCRE_DUPNAMES +#if !defined(PCRE_DUPNAMES) && !defined(HAVE_PCRE2) #error PCRE Version 6.7 or later required! #else @@ -115,7 +123,11 @@ AP_DECLARE(apr_size_t) ap_regerror(int errcode, const ap_regex_t *preg, AP_DECLARE(void) ap_regfree(ap_regex_t *preg) { +#ifdef HAVE_PCRE2 + pcre2_code_free(preg->re_pcre); +#else (pcre_free)(preg->re_pcre); +#endif } @@ -168,39 +180,53 @@ AP_DECLARE(int) ap_regcomp_default_cflag_by_name(const char *name) */ AP_DECLARE(int) ap_regcomp(ap_regex_t * preg, const char *pattern, int cflags) { +#ifdef HAVE_PCRE2 + uint32_t capcount; + size_t erroffset; +#else const char *errorptr; int erroffset; +#endif int errcode = 0; - int options = PCRE_DUPNAMES; + int options = PCREn(DUPNAMES); if ((cflags & AP_REG_NO_DEFAULT) == 0) cflags |= default_cflags; if ((cflags & AP_REG_ICASE) != 0) - options |= PCRE_CASELESS; + options |= PCREn(CASELESS); if ((cflags & AP_REG_NEWLINE) != 0) - options |= PCRE_MULTILINE; + options |= PCREn(MULTILINE); if ((cflags & AP_REG_DOTALL) != 0) - options |= PCRE_DOTALL; + options |= PCREn(DOTALL); if ((cflags & AP_REG_DOLLAR_ENDONLY) != 0) - options |= PCRE_DOLLAR_ENDONLY; + options |= PCREn(DOLLAR_ENDONLY); - preg->re_pcre = - pcre_compile2(pattern, options, &errcode, &errorptr, &erroffset, NULL); - preg->re_erroffset = erroffset; +#ifdef HAVE_PCRE2 + preg->re_pcre = pcre2_compile((const unsigned char *)pattern, + PCRE2_ZERO_TERMINATED, options, &errcode, + &erroffset, NULL); +#else + preg->re_pcre = pcre_compile2(pattern, options, &errcode, + &errorptr, &erroffset, NULL); +#endif + preg->re_erroffset = erroffset; if (preg->re_pcre == NULL) { - /* - * There doesn't seem to be constants defined for compile time error - * codes. 21 is "failed to get memory" according to pcreapi(3). - */ + /* Internal ERR21 is "failed to get memory" according to pcreapi(3) */ if (errcode == 21) return AP_REG_ESPACE; return AP_REG_INVARG; } +#ifdef HAVE_PCRE2 + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_CAPTURECOUNT, &capcount); + preg->re_nsub = capcount; +#else pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_CAPTURECOUNT, &(preg->re_nsub)); + PCRE_INFO_CAPTURECOUNT, &(preg->re_nsub)); +#endif return 0; } @@ -232,17 +258,38 @@ AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff, { int rc; int options = 0; - int *ovector = NULL; + apr_size_t nlim; +#ifdef HAVE_PCRE2 + pcre2_match_data *matchdata; + size_t *ovector; +#else int small_ovector[POSIX_MALLOC_THRESHOLD * 3]; int allocated_ovector = 0; + int *ovector = NULL; +#endif if ((eflags & AP_REG_NOTBOL) != 0) - options |= PCRE_NOTBOL; + options |= PCREn(NOTBOL); if ((eflags & AP_REG_NOTEOL) != 0) - options |= PCRE_NOTEOL; - - ((ap_regex_t *)preg)->re_erroffset = (apr_size_t)(-1); /* Only has meaning after compile */ - + options |= PCREn(NOTEOL); + +#ifdef HAVE_PCRE2 + /* TODO: create a generic TLS matchdata buffer of some nmatch limit, + * e.g. 10 matches, to avoid a malloc-per-call. If it must be alloced, + * implement a general context using palloc and no free implementation. + */ + nlim = ((apr_size_t)preg->re_nsub + 1) > nmatch + ? ((apr_size_t)preg->re_nsub + 1) : nmatch; + matchdata = pcre2_match_data_create(nlim, NULL); + if (matchdata == NULL) + return AP_REG_ESPACE; + ovector = pcre2_get_ovector_pointer(matchdata); + rc = pcre2_match((const pcre2_code *)preg->re_pcre, + (const unsigned char *)buff, len, + 0, options, matchdata, NULL); + if (rc == 0) + rc = nlim; /* All captured slots were filled in */ +#else if (nmatch > 0) { if (nmatch <= POSIX_MALLOC_THRESHOLD) { ovector = &(small_ovector[0]); @@ -254,52 +301,63 @@ AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff, allocated_ovector = 1; } } - rc = pcre_exec((const pcre *)preg->re_pcre, NULL, buff, (int)len, 0, options, ovector, nmatch * 3); - if (rc == 0) rc = nmatch; /* All captured slots were filled in */ +#endif if (rc >= 0) { apr_size_t i; - for (i = 0; i < (apr_size_t)rc; i++) { + nlim = (apr_size_t)rc < nmatch ? (apr_size_t)rc : nmatch; + for (i = 0; i < nlim; i++) { pmatch[i].rm_so = ovector[i * 2]; pmatch[i].rm_eo = ovector[i * 2 + 1]; } - if (allocated_ovector) - free(ovector); for (; i < nmatch; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1; - return 0; } +#ifdef HAVE_PCRE2 + pcre2_match_data_free(matchdata); +#else + if (allocated_ovector) + free(ovector); +#endif + + if (rc >= 0) { + return 0; + } else { - if (allocated_ovector) - free(ovector); +#ifdef HAVE_PCRE2 + if (rc <= PCRE2_ERROR_UTF8_ERR1 && rc >= PCRE2_ERROR_UTF8_ERR21) + return AP_REG_INVARG; +#endif switch (rc) { - case PCRE_ERROR_NOMATCH: + case PCREn(ERROR_NOMATCH): return AP_REG_NOMATCH; - case PCRE_ERROR_NULL: + case PCREn(ERROR_NULL): return AP_REG_INVARG; - case PCRE_ERROR_BADOPTION: + case PCREn(ERROR_BADOPTION): return AP_REG_INVARG; - case PCRE_ERROR_BADMAGIC: + case PCREn(ERROR_BADMAGIC): return AP_REG_INVARG; - case PCRE_ERROR_UNKNOWN_NODE: - return AP_REG_ASSERT; - case PCRE_ERROR_NOMEMORY: + case PCREn(ERROR_NOMEMORY): return AP_REG_ESPACE; -#ifdef PCRE_ERROR_MATCHLIMIT - case PCRE_ERROR_MATCHLIMIT: +#if defined(HAVE_PCRE2) || defined(PCRE_ERROR_MATCHLIMIT) + case PCREn(ERROR_MATCHLIMIT): return AP_REG_ESPACE; #endif -#ifdef PCRE_ERROR_BADUTF8 - case PCRE_ERROR_BADUTF8: +#if defined(PCRE_ERROR_UNKNOWN_NODE) + case PCRE_ERROR_UNKNOWN_NODE: + return AP_REG_ASSERT; +#endif +#if defined(PCRE_ERROR_BADUTF8) + case PCREn(ERROR_BADUTF8): return AP_REG_INVARG; #endif -#ifdef PCRE_ERROR_BADUTF8_OFFSET - case PCRE_ERROR_BADUTF8_OFFSET: +#if defined(PCRE_ERROR_BADUTF8_OFFSET) + case PCREn(ERROR_BADUTF8_OFFSET): return AP_REG_INVARG; #endif default: @@ -312,17 +370,29 @@ AP_DECLARE(int) ap_regname(const ap_regex_t *preg, apr_array_header_t *names, const char *prefix, int upper) { + char *nametable; + +#ifdef HAVE_PCRE2 + uint32_t namecount; + uint32_t nameentrysize; + uint32_t i; + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_NAMECOUNT, &namecount); + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_NAMEENTRYSIZE, &nameentrysize); + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_NAMETABLE, &nametable); +#else int namecount; int nameentrysize; int i; - char *nametable; - pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_NAMECOUNT, &namecount); + PCRE_INFO_NAMECOUNT, &namecount); pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_NAMEENTRYSIZE, &nameentrysize); + PCRE_INFO_NAMEENTRYSIZE, &nameentrysize); pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_NAMETABLE, &nametable); + PCRE_INFO_NAMETABLE, &nametable); +#endif for (i = 0; i < namecount; i++) { const char *offset = nametable + i * nameentrysize; -- 2.47.2