Skip to content

Instantly share code, notes, and snippets.

@wrowe
Created January 13, 2022 03:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wrowe/73f655d13bbe0f12030aa4557e804d8a to your computer and use it in GitHub Desktop.
Save wrowe/73f655d13bbe0f12030aa4557e804d8a to your computer and use it in GitHub Desktop.
Enable pcre2 for httpd-2.4 branch
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 <ppisar redhat.com>, rjung]
Backports: 1773454 1773741 1773742 1773839 1773870 1773882 1814662 1881478
Index: CHANGES
===================================================================
--- CHANGES (revision 1896867)
+++ CHANGES (working copy)
@@ -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 <ppisar redhat.com>, Rainer Jung]
+
Changes with Apache 2.4.52
*) SECURITY: CVE-2021-44790: Possible buffer overflow when parsing
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt (revision 1896867)
+++ CMakeLists.txt (working copy)
@@ -47,7 +47,13 @@
# 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_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_bin_pdb ${install_bin_pdb} $<TARGET_PDB_FILE:libhttpd>)
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} -D{PCRE_CFLAGS} -DBIN_NAME=libhttpd.dll ${EXTRA_COMPILE_FLAGS}")
ADD_DEPENDENCIES(libhttpd test_char_header)
########### HTTPD EXECUTABLES ##########
Index: configure.in
===================================================================
--- configure.in (revision 1896867)
+++ configure.in (working copy)
@@ -214,19 +214,24 @@
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])
;;
@@ -233,9 +238,9 @@
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 @@
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 <pcre.h>
+#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([])
Index: server/util_pcre.c
===================================================================
--- server/util_pcre.c (revision 1896867)
+++ server/util_pcre.c (working copy)
@@ -55,10 +55,18 @@
#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(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(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);
+#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 @@
{
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;
+ options |= PCREn(NOTEOL);
- ((ap_regex_t *)preg)->re_erroffset = (apr_size_t)(-1); /* Only has meaning after compile */
-
+#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 @@
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;
+ }
+
+#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 @@
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;
Index: .
===================================================================
--- . (revision 1896867)
+++ . (working copy)
Property changes on: .
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /httpd/httpd/trunk:r1773454,1773741-1773742,1773839,1773870,1773882,1814662,1881478
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment