From 09268f58b457dba304e4dc311d98c206d367b971 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Sat, 25 Dec 2021 01:07:10 +0300 Subject: [PATCH 01/13] Core: fixed ngx_pcre_studies cleanup. If a configuration parsing fails for some reason, ngx_regex_module_init() is not called, and ngx_pcre_studies remained set despite the fact that the pool it was allocated from is already freed. This might result in a segmentation fault during runtime regular expression compilation, such as in SSI, for example, in the single process mode, or if a worker process died and was respawned from a master process in such an inconsistent state. Fix is to clear ngx_pcre_studies from the pool cleanup handler (which is anyway used to free JIT-compiled patterns). --- src/core/ngx_regex.c | 83 +++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index 52169f6553..1e9a1b59ec 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -10,15 +10,14 @@ typedef struct { - ngx_flag_t pcre_jit; + ngx_flag_t pcre_jit; + ngx_list_t *studies; } ngx_regex_conf_t; static void * ngx_libc_cdecl ngx_regex_malloc(size_t size); static void ngx_libc_cdecl ngx_regex_free(void *p); -#if (NGX_HAVE_PCRE_JIT) -static void ngx_pcre_free_studies(void *data); -#endif +static void ngx_regex_cleanup(void *data); static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle); @@ -248,18 +247,17 @@ ngx_regex_free(void *p) } -#if (NGX_HAVE_PCRE_JIT) - static void -ngx_pcre_free_studies(void *data) +ngx_regex_cleanup(void *data) { - ngx_list_t *studies = data; +#if (NGX_HAVE_PCRE_JIT) + ngx_regex_conf_t *rcf = data; ngx_uint_t i; ngx_list_part_t *part; ngx_regex_elt_t *elts; - part = &studies->part; + part = &rcf->studies->part; elts = part->elts; for (i = 0; /* void */ ; i++) { @@ -274,56 +272,50 @@ ngx_pcre_free_studies(void *data) i = 0; } + /* + * The PCRE JIT compiler uses mmap for its executable codes, so we + * have to explicitly call the pcre_free_study() function to free + * this memory. + */ + if (elts[i].regex->extra != NULL) { pcre_free_study(elts[i].regex->extra); } } -} - #endif + /* + * On configuration parsing errors ngx_regex_module_init() will not + * be called. Make sure ngx_pcre_studies is properly cleared anyway. + */ + + ngx_pcre_studies = NULL; +} + static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle) { - int opt; - const char *errstr; - ngx_uint_t i; - ngx_list_part_t *part; - ngx_regex_elt_t *elts; + int opt; + const char *errstr; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_regex_elt_t *elts; + ngx_regex_conf_t *rcf; opt = 0; -#if (NGX_HAVE_PCRE_JIT) - { - ngx_regex_conf_t *rcf; - ngx_pool_cleanup_t *cln; - rcf = (ngx_regex_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_regex_module); +#if (NGX_HAVE_PCRE_JIT) if (rcf->pcre_jit) { opt = PCRE_STUDY_JIT_COMPILE; - - /* - * The PCRE JIT compiler uses mmap for its executable codes, so we - * have to explicitly call the pcre_free_study() function to free - * this memory. - */ - - cln = ngx_pool_cleanup_add(cycle->pool, 0); - if (cln == NULL) { - return NGX_ERROR; - } - - cln->handler = ngx_pcre_free_studies; - cln->data = ngx_pcre_studies; - } } #endif ngx_regex_malloc_init(cycle->pool); - part = &ngx_pcre_studies->part; + part = &rcf->studies->part; elts = part->elts; for (i = 0; /* void */ ; i++) { @@ -374,7 +366,8 @@ ngx_regex_module_init(ngx_cycle_t *cycle) static void * ngx_regex_create_conf(ngx_cycle_t *cycle) { - ngx_regex_conf_t *rcf; + ngx_regex_conf_t *rcf; + ngx_pool_cleanup_t *cln; rcf = ngx_pcalloc(cycle->pool, sizeof(ngx_regex_conf_t)); if (rcf == NULL) { @@ -383,11 +376,21 @@ ngx_regex_create_conf(ngx_cycle_t *cycle) rcf->pcre_jit = NGX_CONF_UNSET; - ngx_pcre_studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t)); - if (ngx_pcre_studies == NULL) { + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { return NULL; } + cln->handler = ngx_regex_cleanup; + cln->data = rcf; + + rcf->studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t)); + if (rcf->studies == NULL) { + return NULL; + } + + ngx_pcre_studies = rcf->studies; + return rcf; } From 28f8caac37fce6efbeedf75bf5688267be79a0d8 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Sat, 25 Dec 2021 01:07:12 +0300 Subject: [PATCH 02/13] Core: ngx_regex.c style cleanup. Notably, ngx_pcre_pool and ngx_pcre_studies are renamed to ngx_regex_pool and ngx_regex_studies, respectively. --- src/core/ngx_regex.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index 1e9a1b59ec..461b4be829 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -15,6 +15,9 @@ typedef struct { } ngx_regex_conf_t; +static ngx_inline void ngx_regex_malloc_init(ngx_pool_t *pool); +static ngx_inline void ngx_regex_malloc_done(void); + static void * ngx_libc_cdecl ngx_regex_malloc(size_t size); static void ngx_libc_cdecl ngx_regex_free(void *p); static void ngx_regex_cleanup(void *data); @@ -64,8 +67,8 @@ ngx_module_t ngx_regex_module = { }; -static ngx_pool_t *ngx_pcre_pool; -static ngx_list_t *ngx_pcre_studies; +static ngx_pool_t *ngx_regex_pool; +static ngx_list_t *ngx_regex_studies; void @@ -79,14 +82,14 @@ ngx_regex_init(void) static ngx_inline void ngx_regex_malloc_init(ngx_pool_t *pool) { - ngx_pcre_pool = pool; + ngx_regex_pool = pool; } static ngx_inline void ngx_regex_malloc_done(void) { - ngx_pcre_pool = NULL; + ngx_regex_pool = NULL; } @@ -112,13 +115,13 @@ ngx_regex_compile(ngx_regex_compile_t *rc) rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\"", errstr, &rc->pattern) - - rc->err.data; + - rc->err.data; } else { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\" at \"%s\"", errstr, &rc->pattern, rc->pattern.data + erroff) - - rc->err.data; + - rc->err.data; } return NGX_ERROR; @@ -133,8 +136,8 @@ ngx_regex_compile(ngx_regex_compile_t *rc) /* do not study at runtime */ - if (ngx_pcre_studies != NULL) { - elt = ngx_list_push(ngx_pcre_studies); + if (ngx_regex_studies != NULL) { + elt = ngx_list_push(ngx_regex_studies); if (elt == NULL) { goto nomem; } @@ -229,11 +232,8 @@ ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log) static void * ngx_libc_cdecl ngx_regex_malloc(size_t size) { - ngx_pool_t *pool; - pool = ngx_pcre_pool; - - if (pool) { - return ngx_palloc(pool, size); + if (ngx_regex_pool) { + return ngx_palloc(ngx_regex_pool, size); } return NULL; @@ -286,10 +286,10 @@ ngx_regex_cleanup(void *data) /* * On configuration parsing errors ngx_regex_module_init() will not - * be called. Make sure ngx_pcre_studies is properly cleared anyway. + * be called. Make sure ngx_regex_studies is properly cleared anyway. */ - ngx_pcre_studies = NULL; + ngx_regex_studies = NULL; } @@ -357,7 +357,7 @@ ngx_regex_module_init(ngx_cycle_t *cycle) ngx_regex_malloc_done(); - ngx_pcre_studies = NULL; + ngx_regex_studies = NULL; return NGX_OK; } @@ -389,7 +389,7 @@ ngx_regex_create_conf(ngx_cycle_t *cycle) return NULL; } - ngx_pcre_studies = rcf->studies; + ngx_regex_studies = rcf->studies; return rcf; } From cddb22cefe8f1c132e00553cb012ca9ab3264c18 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Sat, 25 Dec 2021 01:07:14 +0300 Subject: [PATCH 03/13] Configure: simplified PCRE compilation. Removed ICC-specific PCRE optimizations which tried to link with PCRE object files instead of the library. Made compiler-specific code minimal. --- auto/lib/pcre/conf | 73 +++++----------------------------------------- 1 file changed, 8 insertions(+), 65 deletions(-) diff --git a/auto/lib/pcre/conf b/auto/lib/pcre/conf index 5e3960fea3..ab13bf70fd 100644 --- a/auto/lib/pcre/conf +++ b/auto/lib/pcre/conf @@ -4,81 +4,24 @@ if [ $PCRE != NONE ]; then + + have=NGX_PCRE . auto/have + + if [ "$NGX_PLATFORM" = win32 ]; then + have=PCRE_STATIC . auto/have + fi + CORE_INCS="$CORE_INCS $PCRE" + CORE_DEPS="$CORE_DEPS $PCRE/pcre.h" case "$NGX_CC_NAME" in msvc | owc | bcc) - have=NGX_PCRE . auto/have - have=PCRE_STATIC . auto/have - CORE_DEPS="$CORE_DEPS $PCRE/pcre.h" LINK_DEPS="$LINK_DEPS $PCRE/pcre.lib" CORE_LIBS="$CORE_LIBS $PCRE/pcre.lib" ;; - icc) - have=NGX_PCRE . auto/have - CORE_DEPS="$CORE_DEPS $PCRE/pcre.h" - - LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a" - - echo $ngx_n "checking for PCRE library ...$ngx_c" - - if [ -f $PCRE/pcre.h ]; then - ngx_pcre_ver=`grep PCRE_MAJOR $PCRE/pcre.h \ - | sed -e 's/^.*PCRE_MAJOR.* \(.*\)$/\1/'` - - else if [ -f $PCRE/configure.in ]; then - ngx_pcre_ver=`grep PCRE_MAJOR= $PCRE/configure.in \ - | sed -e 's/^.*=\(.*\)$/\1/'` - - else - ngx_pcre_ver=`grep pcre_major, $PCRE/configure.ac \ - | sed -e 's/^.*pcre_major,.*\[\(.*\)\].*$/\1/'` - fi - fi - - echo " $ngx_pcre_ver major version found" - - # to allow -ipo optimization we link with the *.o but not library - - case "$ngx_pcre_ver" in - 4|5) - CORE_LIBS="$CORE_LIBS $PCRE/pcre.o" - ;; - - 6) - CORE_LIBS="$CORE_LIBS $PCRE/pcre_chartables.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_compile.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_exec.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_fullinfo.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_globals.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_tables.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_try_flipped.o" - ;; - - *) - CORE_LIBS="$CORE_LIBS $PCRE/pcre_chartables.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_compile.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_exec.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_fullinfo.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_globals.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_tables.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_try_flipped.o" - CORE_LIBS="$CORE_LIBS $PCRE/pcre_newline.o" - ;; - - esac - ;; - *) - have=NGX_PCRE . auto/have - - if [ "$NGX_PLATFORM" = win32 ]; then - have=PCRE_STATIC . auto/have - fi - - CORE_DEPS="$CORE_DEPS $PCRE/pcre.h" LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a" CORE_LIBS="$CORE_LIBS $PCRE/.libs/libpcre.a" ;; From c6fec0b027569a4e0b1d8aaee7dea0f2e4d6052b Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Sat, 25 Dec 2021 01:07:15 +0300 Subject: [PATCH 04/13] PCRE2 library support. The PCRE2 library is now used by default if found, instead of the original PCRE library. If needed for some reason, this can be disabled with the --without-pcre2 configure option. To make it possible to specify paths to the library and include files via --with-cc-opt / --with-ld-opt, the library is first tested without any additional paths and options. If this fails, the pcre2-config script is used. Similarly to the original PCRE library, it is now possible to build PCRE2 from sources with nginx configure, by using the --with-pcre= option. It automatically detects if PCRE or PCRE2 sources are provided. Note that compiling PCRE2 10.33 and later requires inttypes.h. When compiling on Windows with MSVC, inttypes.h is only available starting with MSVC 2013. In older versions some replacement needs to be provided ("echo '#include ' > pcre2-10.xx/src/inttypes.h" is good enough for MSVC 2010). The interface on nginx side remains unchanged. --- auto/lib/pcre/conf | 107 +++++++++++--- auto/lib/pcre/make | 152 ++++++++++++++++---- auto/options | 3 + auto/summary | 4 +- src/core/ngx_regex.c | 331 ++++++++++++++++++++++++++++++++++++++++++- src/core/ngx_regex.h | 30 +++- 6 files changed, 574 insertions(+), 53 deletions(-) diff --git a/auto/lib/pcre/conf b/auto/lib/pcre/conf index ab13bf70fd..20c1cafbe5 100644 --- a/auto/lib/pcre/conf +++ b/auto/lib/pcre/conf @@ -5,29 +5,61 @@ if [ $PCRE != NONE ]; then - have=NGX_PCRE . auto/have + if [ -f $PCRE/src/pcre2.h.generic ]; then - if [ "$NGX_PLATFORM" = win32 ]; then - have=PCRE_STATIC . auto/have - fi + PCRE_LIBRARY=PCRE2 + + have=NGX_PCRE . auto/have + have=NGX_PCRE2 . auto/have + + if [ "$NGX_PLATFORM" = win32 ]; then + have=PCRE2_STATIC . auto/have + fi + + CORE_INCS="$CORE_INCS $PCRE/src/" + CORE_DEPS="$CORE_DEPS $PCRE/src/pcre2.h" - CORE_INCS="$CORE_INCS $PCRE" - CORE_DEPS="$CORE_DEPS $PCRE/pcre.h" + case "$NGX_CC_NAME" in - case "$NGX_CC_NAME" in + msvc) + LINK_DEPS="$LINK_DEPS $PCRE/src/pcre2-8.lib" + CORE_LIBS="$CORE_LIBS $PCRE/src/pcre2-8.lib" + ;; - msvc | owc | bcc) - LINK_DEPS="$LINK_DEPS $PCRE/pcre.lib" - CORE_LIBS="$CORE_LIBS $PCRE/pcre.lib" - ;; + *) + LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre2-8.a" + CORE_LIBS="$CORE_LIBS $PCRE/.libs/libpcre2-8.a" + ;; - *) - LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a" - CORE_LIBS="$CORE_LIBS $PCRE/.libs/libpcre.a" - ;; + esac + + else + + PCRE_LIBRARY=PCRE + + have=NGX_PCRE . auto/have + + if [ "$NGX_PLATFORM" = win32 ]; then + have=PCRE_STATIC . auto/have + fi - esac + CORE_INCS="$CORE_INCS $PCRE" + CORE_DEPS="$CORE_DEPS $PCRE/pcre.h" + case "$NGX_CC_NAME" in + + msvc | owc | bcc) + LINK_DEPS="$LINK_DEPS $PCRE/pcre.lib" + CORE_LIBS="$CORE_LIBS $PCRE/pcre.lib" + ;; + + *) + LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a" + CORE_LIBS="$CORE_LIBS $PCRE/.libs/libpcre.a" + ;; + + esac + fi if [ $PCRE_JIT = YES ]; then have=NGX_HAVE_PCRE_JIT . auto/have @@ -37,8 +69,48 @@ if [ $PCRE != NONE ]; then else if [ "$NGX_PLATFORM" != win32 ]; then - PCRE=NO + fi + + if [ $PCRE = NO -a $PCRE2 != DISABLED ]; then + + ngx_feature="PCRE2 library" + ngx_feature_name="NGX_PCRE2" + ngx_feature_run=no + ngx_feature_incs="#define PCRE2_CODE_UNIT_WIDTH 8 + #include " + ngx_feature_path= + ngx_feature_libs="-lpcre2-8" + ngx_feature_test="pcre2_code *re; + re = pcre2_compile(NULL, 0, 0, NULL, NULL, NULL); + if (re == NULL) return 1" + . auto/feature + + if [ $ngx_found = no ]; then + + # pcre2-config + + ngx_pcre2_prefix=`pcre2-config --prefix 2>/dev/null` + + if [ -n "$ngx_pcre2_prefix" ]; then + ngx_feature="PCRE2 library in $ngx_pcre2_prefix" + ngx_feature_path=`pcre2-config --cflags \ + | sed -n -e 's/.*-I *\([^ ][^ ]*\).*/\1/p'` + ngx_feature_libs=`pcre2-config --libs8` + . auto/feature + fi + fi + + if [ $ngx_found = yes ]; then + have=NGX_PCRE . auto/have + CORE_INCS="$CORE_INCS $ngx_feature_path" + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" + PCRE=YES + PCRE_LIBRARY=PCRE2 + fi + fi + + if [ $PCRE = NO ]; then ngx_feature="PCRE library" ngx_feature_name="NGX_PCRE" @@ -114,6 +186,7 @@ else CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" PCRE=YES + PCRE_LIBRARY=PCRE fi if [ $PCRE = YES ]; then diff --git a/auto/lib/pcre/make b/auto/lib/pcre/make index 97c9f3ba02..839ef294bb 100644 --- a/auto/lib/pcre/make +++ b/auto/lib/pcre/make @@ -3,36 +3,138 @@ # Copyright (C) Nginx, Inc. -case "$NGX_CC_NAME" in +if [ $PCRE_LIBRARY = PCRE2 ]; then + + # PCRE2 + + if [ $NGX_CC_NAME = msvc ]; then + + # With PCRE2, it is not possible to compile all sources. + # Since list of source files changes between versions, we + # test files which might not be present. + + ngx_pcre_srcs="pcre2_auto_possess.c \ + pcre2_chartables.c \ + pcre2_compile.c \ + pcre2_config.c \ + pcre2_context.c \ + pcre2_dfa_match.c \ + pcre2_error.c \ + pcre2_jit_compile.c \ + pcre2_maketables.c \ + pcre2_match.c \ + pcre2_match_data.c \ + pcre2_newline.c \ + pcre2_ord2utf.c \ + pcre2_pattern_info.c \ + pcre2_string_utils.c \ + pcre2_study.c \ + pcre2_substitute.c \ + pcre2_substring.c \ + pcre2_tables.c \ + pcre2_ucd.c \ + pcre2_valid_utf.c \ + pcre2_xclass.c" + + ngx_pcre_test="pcre2_convert.c \ + pcre2_extuni.c \ + pcre2_find_bracket.c \ + pcre2_script_run.c \ + pcre2_serialize.c" + + for ngx_src in $ngx_pcre_test + do + if [ -f $PCRE/src/$ngx_src ]; then + ngx_pcre_srcs="$ngx_pcre_srcs $ngx_src" + fi + done + + ngx_pcre_objs=`echo $ngx_pcre_srcs \ + | sed -e "s#\([^ ]*\.\)c#\1$ngx_objext#g"` + + ngx_pcre_srcs=`echo $ngx_pcre_srcs \ + | sed -e "s/ *\([^ ][^ ]*\)/$ngx_regex_cont\1/g"` + ngx_pcre_objs=`echo $ngx_pcre_objs \ + | sed -e "s/ *\([^ ][^ ]*\)/$ngx_regex_cont\1/g"` + + cat << END >> $NGX_MAKEFILE + +PCRE_CFLAGS = -O2 -Ob1 -Oi -Gs $LIBC $CPU_OPT +PCRE_FLAGS = -DHAVE_CONFIG_H -DPCRE2_STATIC -DPCRE2_CODE_UNIT_WIDTH=8 \\ + -DHAVE_MEMMOVE + +PCRE_SRCS = $ngx_pcre_srcs +PCRE_OBJS = $ngx_pcre_objs + +$PCRE/src/pcre2.h: + cd $PCRE/src \\ + && copy /y config.h.generic config.h \\ + && copy /y pcre2.h.generic pcre2.h \\ + && copy /y pcre2_chartables.c.dist pcre2_chartables.c + +$PCRE/src/pcre2-8.lib: $PCRE/src/pcre2.h $NGX_MAKEFILE + cd $PCRE/src \\ + && cl -nologo -c \$(PCRE_CFLAGS) -I . \$(PCRE_FLAGS) \$(PCRE_SRCS) \\ + && link -lib -out:pcre2-8.lib -verbose:lib \$(PCRE_OBJS) - msvc) - ngx_makefile=makefile.msvc - ngx_opt="CPU_OPT=\"$CPU_OPT\" LIBC=$LIBC" - ngx_pcre="PCRE=\"$PCRE\"" - ;; +END + + else + + cat << END >> $NGX_MAKEFILE - owc) - ngx_makefile=makefile.owc - ngx_opt="CPU_OPT=\"$CPU_OPT\"" - ngx_pcre=`echo PCRE=\"$PCRE\" | sed -e "s/\//$ngx_regex_dirsep/g"` - ;; +$PCRE/src/pcre2.h: $PCRE/Makefile + +$PCRE/Makefile: $NGX_MAKEFILE + cd $PCRE \\ + && if [ -f Makefile ]; then \$(MAKE) distclean; fi \\ + && CC="\$(CC)" CFLAGS="$PCRE_OPT" \\ + ./configure --disable-shared $PCRE_CONF_OPT - bcc) - ngx_makefile=makefile.bcc - ngx_opt="-DCPU_OPT=\"$CPU_OPT\"" - ngx_pcre=`echo \-DPCRE=\"$PCRE\" | sed -e "s/\//$ngx_regex_dirsep/g"` - ;; +$PCRE/.libs/libpcre2-8.a: $PCRE/Makefile + cd $PCRE \\ + && \$(MAKE) libpcre2-8.la - *) - ngx_makefile= - ;; +END -esac + fi -if [ -n "$ngx_makefile" ]; then +else - cat << END >> $NGX_MAKEFILE + # PCRE + + case "$NGX_CC_NAME" in + + msvc) + ngx_makefile=makefile.msvc + ngx_opt="CPU_OPT=\"$CPU_OPT\" LIBC=$LIBC" + ngx_pcre="PCRE=\"$PCRE\"" + ;; + + owc) + ngx_makefile=makefile.owc + ngx_opt="CPU_OPT=\"$CPU_OPT\"" + ngx_pcre=`echo PCRE=\"$PCRE\" | sed -e "s/\//$ngx_regex_dirsep/g"` + ;; + + bcc) + ngx_makefile=makefile.bcc + ngx_opt="-DCPU_OPT=\"$CPU_OPT\"" + ngx_pcre=`echo \-DPCRE=\"$PCRE\" \ + | sed -e "s/\//$ngx_regex_dirsep/g"` + ;; + + *) + ngx_makefile= + ;; + + esac + + + if [ -n "$ngx_makefile" ]; then + + cat << END >> $NGX_MAKEFILE `echo "$PCRE/pcre.lib: $PCRE/pcre.h $NGX_MAKEFILE" \ | sed -e "s/\//$ngx_regex_dirsep/g"` @@ -43,9 +145,9 @@ if [ -n "$ngx_makefile" ]; then END -else + else - cat << END >> $NGX_MAKEFILE + cat << END >> $NGX_MAKEFILE $PCRE/pcre.h: $PCRE/Makefile @@ -61,4 +163,6 @@ $PCRE/.libs/libpcre.a: $PCRE/Makefile END + fi + fi diff --git a/auto/options b/auto/options index 80be906e10..48f3a1a425 100644 --- a/auto/options +++ b/auto/options @@ -146,6 +146,7 @@ PCRE=NONE PCRE_OPT= PCRE_CONF_OPT= PCRE_JIT=NO +PCRE2=YES USE_OPENSSL=NO OPENSSL=NONE @@ -357,6 +358,7 @@ use the \"--with-mail_ssl_module\" option instead" --with-pcre=*) PCRE="$value" ;; --with-pcre-opt=*) PCRE_OPT="$value" ;; --with-pcre-jit) PCRE_JIT=YES ;; + --without-pcre2) PCRE2=DISABLED ;; --with-openssl=*) OPENSSL="$value" ;; --with-openssl-opt=*) OPENSSL_OPT="$value" ;; @@ -573,6 +575,7 @@ cat << END --with-pcre=DIR set path to PCRE library sources --with-pcre-opt=OPTIONS set additional build options for PCRE --with-pcre-jit build PCRE with JIT compilation support + --without-pcre2 do not use PCRE2 library --with-zlib=DIR set path to zlib library sources --with-zlib-opt=OPTIONS set additional build options for zlib diff --git a/auto/summary b/auto/summary index 9aa776edf4..b3c07eedc2 100644 --- a/auto/summary +++ b/auto/summary @@ -16,9 +16,9 @@ if [ $USE_PCRE = DISABLED ]; then else case $PCRE in - YES) echo " + using system PCRE library" ;; + YES) echo " + using system $PCRE_LIBRARY library" ;; NONE) echo " + PCRE library is not used" ;; - *) echo " + using PCRE library: $PCRE" ;; + *) echo " + using $PCRE_LIBRARY library: $PCRE" ;; esac fi diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index 461b4be829..5ae4f780b2 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -18,8 +18,13 @@ typedef struct { static ngx_inline void ngx_regex_malloc_init(ngx_pool_t *pool); static ngx_inline void ngx_regex_malloc_done(void); +#if (NGX_PCRE2) +static void * ngx_libc_cdecl ngx_regex_malloc(size_t size, void *data); +static void ngx_libc_cdecl ngx_regex_free(void *p, void *data); +#else static void * ngx_libc_cdecl ngx_regex_malloc(size_t size); static void ngx_libc_cdecl ngx_regex_free(void *p); +#endif static void ngx_regex_cleanup(void *data); static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle); @@ -67,15 +72,24 @@ ngx_module_t ngx_regex_module = { }; -static ngx_pool_t *ngx_regex_pool; -static ngx_list_t *ngx_regex_studies; +static ngx_pool_t *ngx_regex_pool; +static ngx_list_t *ngx_regex_studies; +static ngx_uint_t ngx_regex_direct_alloc; + +#if (NGX_PCRE2) +static pcre2_compile_context *ngx_regex_compile_context; +static pcre2_match_data *ngx_regex_match_data; +static ngx_uint_t ngx_regex_match_data_size; +#endif void ngx_regex_init(void) { +#if !(NGX_PCRE2) pcre_malloc = ngx_regex_malloc; pcre_free = ngx_regex_free; +#endif } @@ -83,6 +97,7 @@ static ngx_inline void ngx_regex_malloc_init(ngx_pool_t *pool) { ngx_regex_pool = pool; + ngx_regex_direct_alloc = (pool == NULL) ? 1 : 0; } @@ -90,9 +105,146 @@ static ngx_inline void ngx_regex_malloc_done(void) { ngx_regex_pool = NULL; + ngx_regex_direct_alloc = 0; } +#if (NGX_PCRE2) + +ngx_int_t +ngx_regex_compile(ngx_regex_compile_t *rc) +{ + int n, errcode; + char *p; + u_char errstr[128]; + size_t erroff; + pcre2_code *re; + ngx_regex_elt_t *elt; + pcre2_general_context *gctx; + pcre2_compile_context *cctx; + + if (ngx_regex_compile_context == NULL) { + /* + * Allocate a compile context if not yet allocated. This uses + * direct allocations from heap, so the result can be cached + * even at runtime. + */ + + ngx_regex_malloc_init(NULL); + + gctx = pcre2_general_context_create(ngx_regex_malloc, ngx_regex_free, + NULL); + if (gctx == NULL) { + ngx_regex_malloc_done(); + goto nomem; + } + + cctx = pcre2_compile_context_create(gctx); + if (cctx == NULL) { + pcre2_general_context_free(gctx); + ngx_regex_malloc_done(); + goto nomem; + } + + ngx_regex_compile_context = cctx; + + pcre2_general_context_free(gctx); + ngx_regex_malloc_done(); + } + + ngx_regex_malloc_init(rc->pool); + + re = pcre2_compile(rc->pattern.data, rc->pattern.len, + (uint32_t) rc->options, &errcode, &erroff, + ngx_regex_compile_context); + + /* ensure that there is no current pool */ + ngx_regex_malloc_done(); + + if (re == NULL) { + pcre2_get_error_message(errcode, errstr, 128); + + if ((size_t) erroff == rc->pattern.len) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre2_compile() failed: %s in \"%V\"", + errstr, &rc->pattern) + - rc->err.data; + + } else { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre2_compile() failed: %s in \"%V\" at \"%s\"", + errstr, &rc->pattern, rc->pattern.data + erroff) + - rc->err.data; + } + + return NGX_ERROR; + } + + rc->regex = re; + + /* do not study at runtime */ + + if (ngx_regex_studies != NULL) { + elt = ngx_list_push(ngx_regex_studies); + if (elt == NULL) { + goto nomem; + } + + elt->regex = rc->regex; + elt->name = rc->pattern.data; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &rc->captures); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_CAPTURECOUNT) failed: %d"; + goto failed; + } + + if (rc->captures == 0) { + return NGX_OK; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_NAMECOUNT, &rc->named_captures); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMECOUNT) failed: %d"; + goto failed; + } + + if (rc->named_captures == 0) { + return NGX_OK; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_NAMEENTRYSIZE, &rc->name_size); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMEENTRYSIZE) failed: %d"; + goto failed; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_NAMETABLE, &rc->names); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMETABLE) failed: %d"; + goto failed; + } + + return NGX_OK; + +failed: + + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) + - rc->err.data; + return NGX_ERROR; + +nomem: + + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "regex \"%V\" compilation failed: no memory", + &rc->pattern) + - rc->err.data; + return NGX_ERROR; +} + +#else + ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc) { @@ -195,6 +347,74 @@ ngx_regex_compile(ngx_regex_compile_t *rc) return NGX_ERROR; } +#endif + + +#if (NGX_PCRE2) + +ngx_int_t +ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size) +{ + size_t *ov; + ngx_int_t rc; + ngx_uint_t n, i; + + /* + * The pcre2_match() function might allocate memory for backtracking + * frames, typical allocations are from 40k and above. So the allocator + * is configured to do direct allocations from heap during matching. + */ + + ngx_regex_malloc_init(NULL); + + if (ngx_regex_match_data == NULL + || size > ngx_regex_match_data_size) + { + /* + * Allocate a match data if not yet allocated or smaller than + * needed. + */ + + if (ngx_regex_match_data) { + pcre2_match_data_free(ngx_regex_match_data); + } + + ngx_regex_match_data_size = size; + ngx_regex_match_data = pcre2_match_data_create(size / 3, NULL); + + if (ngx_regex_match_data == NULL) { + rc = PCRE2_ERROR_NOMEMORY; + goto failed; + } + } + + rc = pcre2_match(re, s->data, s->len, 0, 0, ngx_regex_match_data, NULL); + + if (rc < 0) { + goto failed; + } + + n = pcre2_get_ovector_count(ngx_regex_match_data); + ov = pcre2_get_ovector_pointer(ngx_regex_match_data); + + if (n > size / 3) { + n = size / 3; + } + + for (i = 0; i < n; i++) { + captures[i * 2] = ov[i * 2]; + captures[i * 2 + 1] = ov[i * 2 + 1]; + } + +failed: + + ngx_regex_malloc_done(); + + return rc; +} + +#endif + ngx_int_t ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log) @@ -229,6 +449,35 @@ ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log) } +#if (NGX_PCRE2) + +static void * ngx_libc_cdecl +ngx_regex_malloc(size_t size, void *data) +{ + if (ngx_regex_pool) { + return ngx_palloc(ngx_regex_pool, size); + } + + if (ngx_regex_direct_alloc) { + return ngx_alloc(size, ngx_cycle->log); + } + + return NULL; +} + + +static void ngx_libc_cdecl +ngx_regex_free(void *p, void *data) +{ + if (ngx_regex_direct_alloc) { + ngx_free(p); + } + + return; +} + +#else + static void * ngx_libc_cdecl ngx_regex_malloc(size_t size) { @@ -246,11 +495,13 @@ ngx_regex_free(void *p) return; } +#endif + static void ngx_regex_cleanup(void *data) { -#if (NGX_HAVE_PCRE_JIT) +#if (NGX_PCRE2 || NGX_HAVE_PCRE_JIT) ngx_regex_conf_t *rcf = data; ngx_uint_t i; @@ -275,12 +526,17 @@ ngx_regex_cleanup(void *data) /* * The PCRE JIT compiler uses mmap for its executable codes, so we * have to explicitly call the pcre_free_study() function to free - * this memory. + * this memory. In PCRE2, we call the pcre2_code_free() function + * for the same reason. */ +#if (NGX_PCRE2) + pcre2_code_free(elts[i].regex); +#else if (elts[i].regex->extra != NULL) { pcre_free_study(elts[i].regex->extra); } +#endif } #endif @@ -290,6 +546,26 @@ ngx_regex_cleanup(void *data) */ ngx_regex_studies = NULL; + +#if (NGX_PCRE2) + + /* + * Free compile context and match data. If needed at runtime by + * the new cycle, these will be re-allocated. + */ + + if (ngx_regex_compile_context) { + pcre2_compile_context_free(ngx_regex_compile_context); + ngx_regex_compile_context = NULL; + } + + if (ngx_regex_match_data) { + pcre2_match_data_free(ngx_regex_match_data); + ngx_regex_match_data = NULL; + ngx_regex_match_data_size = 0; + } + +#endif } @@ -297,7 +573,9 @@ static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle) { int opt; +#if !(NGX_PCRE2) const char *errstr; +#endif ngx_uint_t i; ngx_list_part_t *part; ngx_regex_elt_t *elts; @@ -307,10 +585,16 @@ ngx_regex_module_init(ngx_cycle_t *cycle) rcf = (ngx_regex_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_regex_module); -#if (NGX_HAVE_PCRE_JIT) +#if (NGX_PCRE2 || NGX_HAVE_PCRE_JIT) + if (rcf->pcre_jit) { +#if (NGX_PCRE2) + opt = 1; +#else opt = PCRE_STUDY_JIT_COMPILE; +#endif } + #endif ngx_regex_malloc_init(cycle->pool); @@ -330,6 +614,23 @@ ngx_regex_module_init(ngx_cycle_t *cycle) i = 0; } +#if (NGX_PCRE2) + + if (opt) { + int n; + + n = pcre2_jit_compile(elts[i].regex, PCRE2_JIT_COMPLETE); + + if (n != 0) { + ngx_log_error(NGX_LOG_INFO, cycle->log, 0, + "pcre2_jit_compile() failed: %d in \"%s\", " + "ignored", + n, elts[i].name); + } + } + +#else + elts[i].regex->extra = pcre_study(elts[i].regex->code, opt, &errstr); if (errstr != NULL) { @@ -352,12 +653,16 @@ ngx_regex_module_init(ngx_cycle_t *cycle) elts[i].name); } } +#endif #endif } ngx_regex_malloc_done(); ngx_regex_studies = NULL; +#if (NGX_PCRE2) + ngx_regex_compile_context = NULL; +#endif return NGX_OK; } @@ -415,7 +720,21 @@ ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data) return NGX_CONF_OK; } -#if (NGX_HAVE_PCRE_JIT) +#if (NGX_PCRE2) + { + int r; + uint32_t jit; + + jit = 0; + r = pcre2_config(PCRE2_CONFIG_JIT, &jit); + + if (r != 0 || jit != 1) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "PCRE2 library does not support JIT"); + *fp = 0; + } + } +#elif (NGX_HAVE_PCRE_JIT) { int jit, r; diff --git a/src/core/ngx_regex.h b/src/core/ngx_regex.h index 680486c816..70bd1db9f9 100644 --- a/src/core/ngx_regex.h +++ b/src/core/ngx_regex.h @@ -12,19 +12,31 @@ #include #include -#include +#if (NGX_PCRE2) + +#define PCRE2_CODE_UNIT_WIDTH 8 +#include + +#define NGX_REGEX_NO_MATCHED PCRE2_ERROR_NOMATCH /* -1 */ +#define NGX_REGEX_CASELESS PCRE2_CASELESS -#define NGX_REGEX_NO_MATCHED PCRE_ERROR_NOMATCH /* -1 */ +typedef pcre2_code ngx_regex_t; -#define NGX_REGEX_CASELESS PCRE_CASELESS +#else + +#include +#define NGX_REGEX_NO_MATCHED PCRE_ERROR_NOMATCH /* -1 */ +#define NGX_REGEX_CASELESS PCRE_CASELESS typedef struct { pcre *code; pcre_extra *extra; } ngx_regex_t; +#endif + typedef struct { ngx_str_t pattern; @@ -49,10 +61,20 @@ typedef struct { void ngx_regex_init(void); ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc); +#if (NGX_PCRE2) + +ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, + ngx_uint_t size); +#define ngx_regex_exec_n "pcre2_match()" + +#else + #define ngx_regex_exec(re, s, captures, size) \ pcre_exec(re->code, re->extra, (const char *) (s)->data, (s)->len, 0, 0, \ captures, size) -#define ngx_regex_exec_n "pcre_exec()" +#define ngx_regex_exec_n "pcre_exec()" + +#endif ngx_int_t ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log); From 931acbf5bcd550af8613d131f4ba49e22e909efb Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Sat, 25 Dec 2021 01:07:16 +0300 Subject: [PATCH 05/13] PCRE2 and PCRE binary compatibility. With this change, dynamic modules using nginx regex interface can be used regardless of the variant of the PCRE library nginx was compiled with. If a module is compiled with different PCRE library variant, in case of ngx_regex_exec() errors it will report wrong function name in error messages. This is believed to be tolerable, given that fixing this will require interface changes. --- src/core/ngx_regex.c | 46 ++++++++++++++++++++++++++++++++++++++++---- src/core/ngx_regex.h | 17 ++++++---------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index 5ae4f780b2..991728b27e 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -118,6 +118,7 @@ ngx_regex_compile(ngx_regex_compile_t *rc) char *p; u_char errstr[128]; size_t erroff; + uint32_t options; pcre2_code *re; ngx_regex_elt_t *elt; pcre2_general_context *gctx; @@ -152,11 +153,24 @@ ngx_regex_compile(ngx_regex_compile_t *rc) ngx_regex_malloc_done(); } + options = 0; + + if (rc->options & NGX_REGEX_CASELESS) { + options |= PCRE2_CASELESS; + } + + if (rc->options & ~NGX_REGEX_CASELESS) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "regex \"%V\" compilation failed: invalid options", + &rc->pattern) + - rc->err.data; + return NGX_ERROR; + } + ngx_regex_malloc_init(rc->pool); - re = pcre2_compile(rc->pattern.data, rc->pattern.len, - (uint32_t) rc->options, &errcode, &erroff, - ngx_regex_compile_context); + re = pcre2_compile(rc->pattern.data, rc->pattern.len, options, + &errcode, &erroff, ngx_regex_compile_context); /* ensure that there is no current pool */ ngx_regex_malloc_done(); @@ -252,11 +266,26 @@ ngx_regex_compile(ngx_regex_compile_t *rc) char *p; pcre *re; const char *errstr; + ngx_uint_t options; ngx_regex_elt_t *elt; + options = 0; + + if (rc->options & NGX_REGEX_CASELESS) { + options |= PCRE_CASELESS; + } + + if (rc->options & ~NGX_REGEX_CASELESS) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "regex \"%V\" compilation failed: invalid options", + &rc->pattern) + - rc->err.data; + return NGX_ERROR; + } + ngx_regex_malloc_init(rc->pool); - re = pcre_compile((const char *) rc->pattern.data, (int) rc->options, + re = pcre_compile((const char *) rc->pattern.data, (int) options, &errstr, &erroff, NULL); /* ensure that there is no current pool */ @@ -413,6 +442,15 @@ ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size) return rc; } +#else + +ngx_int_t +ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size) +{ + return pcre_exec(re->code, re->extra, (const char *) s->data, s->len, + 0, 0, captures, size); +} + #endif diff --git a/src/core/ngx_regex.h b/src/core/ngx_regex.h index 70bd1db9f9..74e694d2e7 100644 --- a/src/core/ngx_regex.h +++ b/src/core/ngx_regex.h @@ -19,7 +19,6 @@ #include #define NGX_REGEX_NO_MATCHED PCRE2_ERROR_NOMATCH /* -1 */ -#define NGX_REGEX_CASELESS PCRE2_CASELESS typedef pcre2_code ngx_regex_t; @@ -28,7 +27,6 @@ typedef pcre2_code ngx_regex_t; #include #define NGX_REGEX_NO_MATCHED PCRE_ERROR_NOMATCH /* -1 */ -#define NGX_REGEX_CASELESS PCRE_CASELESS typedef struct { pcre *code; @@ -38,10 +36,13 @@ typedef struct { #endif +#define NGX_REGEX_CASELESS 0x00000001 + + typedef struct { ngx_str_t pattern; ngx_pool_t *pool; - ngx_int_t options; + ngx_uint_t options; ngx_regex_t *regex; int captures; @@ -61,19 +62,13 @@ typedef struct { void ngx_regex_init(void); ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc); -#if (NGX_PCRE2) - ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size); -#define ngx_regex_exec_n "pcre2_match()" +#if (NGX_PCRE2) +#define ngx_regex_exec_n "pcre2_match()" #else - -#define ngx_regex_exec(re, s, captures, size) \ - pcre_exec(re->code, re->extra, (const char *) (s)->data, (s)->len, 0, 0, \ - captures, size) #define ngx_regex_exec_n "pcre_exec()" - #endif ngx_int_t ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log); From d5f1f169bc71d32b96960266d54e189c69af00ba Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Sat, 25 Dec 2021 01:07:18 +0300 Subject: [PATCH 06/13] Core: added NGX_REGEX_MULTILINE for 3rd party modules. Notably, NAXSI is known to misuse ngx_regex_compile() with rc.options set to PCRE_CASELESS | PCRE_MULTILINE. With PCRE2 support, and notably binary compatibility changes, it is no longer possible to set PCRE[2]_MULTILINE option without using proper interface. To facilitate correct usage, this change adds the NGX_REGEX_MULTILINE option. --- src/core/ngx_regex.c | 12 ++++++++++-- src/core/ngx_regex.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index 991728b27e..bebf3b6a83 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -159,7 +159,11 @@ ngx_regex_compile(ngx_regex_compile_t *rc) options |= PCRE2_CASELESS; } - if (rc->options & ~NGX_REGEX_CASELESS) { + if (rc->options & NGX_REGEX_MULTILINE) { + options |= PCRE2_MULTILINE; + } + + if (rc->options & ~(NGX_REGEX_CASELESS|NGX_REGEX_MULTILINE)) { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "regex \"%V\" compilation failed: invalid options", &rc->pattern) @@ -275,7 +279,11 @@ ngx_regex_compile(ngx_regex_compile_t *rc) options |= PCRE_CASELESS; } - if (rc->options & ~NGX_REGEX_CASELESS) { + if (rc->options & NGX_REGEX_MULTILINE) { + options |= PCRE_MULTILINE; + } + + if (rc->options & ~(NGX_REGEX_CASELESS|NGX_REGEX_MULTILINE)) { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "regex \"%V\" compilation failed: invalid options", &rc->pattern) diff --git a/src/core/ngx_regex.h b/src/core/ngx_regex.h index 74e694d2e7..182373a22b 100644 --- a/src/core/ngx_regex.h +++ b/src/core/ngx_regex.h @@ -37,6 +37,7 @@ typedef struct { #define NGX_REGEX_CASELESS 0x00000001 +#define NGX_REGEX_MULTILINE 0x00000002 typedef struct { From f0a5ce136d8c0492123b16436467d367ed484d28 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Mon, 27 Dec 2021 19:47:05 +0300 Subject: [PATCH 07/13] Removed "aio sendfile", deprecated since 1.7.11. --- src/http/ngx_http_core_module.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index fe1da4576c..c7463dcdc6 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4568,19 +4568,6 @@ ngx_http_core_set_aio(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } -#if (NGX_HAVE_AIO_SENDFILE) - - if (ngx_strcmp(value[1].data, "sendfile") == 0) { - clcf->aio = NGX_HTTP_AIO_ON; - - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "the \"sendfile\" parameter of " - "the \"aio\" directive is deprecated"); - return NGX_CONF_OK; - } - -#endif - if (ngx_strncmp(value[1].data, "threads", 7) == 0 && (value[1].len == 7 || value[1].data[7] == '=')) { From 20c35434ef8d185cc70e8d68ef4730ce08f8b7d6 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Mon, 27 Dec 2021 19:48:33 +0300 Subject: [PATCH 08/13] Simplified sendfile(SF_NODISKIO) usage. Starting with FreeBSD 11, there is no need to use AIO operations to preload data into cache for sendfile(SF_NODISKIO) to work. Instead, sendfile() handles non-blocking loading data from disk by itself. It still can, however, return EBUSY if a page is already being loaded (for example, by a different process). If this happens, we now post an event for the next event loop iteration, so sendfile() is retried "after a short period", as manpage recommends. The limit of the number of EBUSY tolerated without any progress is preserved, but now it does not result in an alert, since on an idle system event loop iteration might be very short and EBUSY can happen many times in a row. Instead, SF_NODISKIO is simply disabled for one call once the limit is reached. With this change, sendfile(SF_NODISKIO) is now used automatically as long as sendfile() is enabled, and no longer requires "aio on;". --- auto/os/freebsd | 8 +-- src/core/ngx_buf.h | 3 - src/core/ngx_connection.h | 2 +- src/core/ngx_module.h | 2 +- src/core/ngx_output_chain.c | 32 --------- src/event/ngx_event.h | 4 -- src/http/ngx_http_copy_filter_module.c | 82 ------------------------ src/os/unix/ngx_freebsd_sendfile_chain.c | 74 ++++++++------------- 8 files changed, 33 insertions(+), 174 deletions(-) diff --git a/auto/os/freebsd b/auto/os/freebsd index 937ca204e6..870bac4b3e 100644 --- a/auto/os/freebsd +++ b/auto/os/freebsd @@ -44,12 +44,10 @@ if [ $osreldate -gt 300007 ]; then CORE_SRCS="$CORE_SRCS $FREEBSD_SENDFILE_SRCS" fi -if [ $NGX_FILE_AIO = YES ]; then - if [ $osreldate -gt 502103 ]; then - echo " + sendfile()'s SF_NODISKIO found" +if [ $osreldate -gt 1100093 ]; then + echo " + sendfile()'s SF_NODISKIO found" - have=NGX_HAVE_AIO_SENDFILE . auto/have - fi + have=NGX_HAVE_SENDFILE_NODISKIO . auto/have fi # POSIX semaphores diff --git a/src/core/ngx_buf.h b/src/core/ngx_buf.h index 4b665629c7..fdcd0cddf6 100644 --- a/src/core/ngx_buf.h +++ b/src/core/ngx_buf.h @@ -90,9 +90,6 @@ struct ngx_output_chain_ctx_s { #if (NGX_HAVE_FILE_AIO || NGX_COMPAT) ngx_output_chain_aio_pt aio_handler; -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) - ssize_t (*aio_preload)(ngx_buf_t *file); -#endif #endif #if (NGX_THREADS || NGX_COMPAT) diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 4716da492e..8cc14755ad 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -185,7 +185,7 @@ struct ngx_connection_s { unsigned need_last_buf:1; -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) +#if (NGX_HAVE_SENDFILE_NODISKIO || NGX_COMPAT) unsigned busy_count:2; #endif diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h index 8cf3210e8e..6fb455426b 100644 --- a/src/core/ngx_module.h +++ b/src/core/ngx_module.h @@ -41,7 +41,7 @@ #define NGX_MODULE_SIGNATURE_3 "0" #endif -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) +#if (NGX_HAVE_SENDFILE_NODISKIO || NGX_COMPAT) #define NGX_MODULE_SIGNATURE_4 "1" #else #define NGX_MODULE_SIGNATURE_4 "0" diff --git a/src/core/ngx_output_chain.c b/src/core/ngx_output_chain.c index fd4603b197..41267eefff 100644 --- a/src/core/ngx_output_chain.c +++ b/src/core/ngx_output_chain.c @@ -29,10 +29,6 @@ static ngx_inline ngx_int_t ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf); -#if (NGX_HAVE_AIO_SENDFILE) -static ngx_int_t ngx_output_chain_aio_setup(ngx_output_chain_ctx_t *ctx, - ngx_file_t *file); -#endif static ngx_int_t ngx_output_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, ngx_chain_t *in); static ngx_int_t ngx_output_chain_align_file_buf(ngx_output_chain_ctx_t *ctx, @@ -283,12 +279,6 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) buf->in_file = 0; } -#if (NGX_HAVE_AIO_SENDFILE) - if (ctx->aio_preload && buf->in_file) { - (void) ngx_output_chain_aio_setup(ctx, buf->file); - } -#endif - if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) { return 0; } @@ -301,28 +291,6 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) } -#if (NGX_HAVE_AIO_SENDFILE) - -static ngx_int_t -ngx_output_chain_aio_setup(ngx_output_chain_ctx_t *ctx, ngx_file_t *file) -{ - ngx_event_aio_t *aio; - - if (file->aio == NULL && ngx_file_aio_init(file, ctx->pool) != NGX_OK) { - return NGX_ERROR; - } - - aio = file->aio; - - aio->data = ctx->filter_ctx; - aio->preload_handler = ctx->aio_preload; - - return NGX_OK; -} - -#endif - - static ngx_int_t ngx_output_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, ngx_chain_t *in) diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h index 97f9673c90..51b8361321 100644 --- a/src/event/ngx_event.h +++ b/src/event/ngx_event.h @@ -147,10 +147,6 @@ struct ngx_event_aio_s { ngx_fd_t fd; -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) - ssize_t (*preload_handler)(ngx_buf_t *file); -#endif - #if (NGX_HAVE_EVENTFD) int64_t res; #endif diff --git a/src/http/ngx_http_copy_filter_module.c b/src/http/ngx_http_copy_filter_module.c index b47e4afa75..bd3028bc76 100644 --- a/src/http/ngx_http_copy_filter_module.c +++ b/src/http/ngx_http_copy_filter_module.c @@ -19,10 +19,6 @@ typedef struct { static void ngx_http_copy_aio_handler(ngx_output_chain_ctx_t *ctx, ngx_file_t *file); static void ngx_http_copy_aio_event_handler(ngx_event_t *ev); -#if (NGX_HAVE_AIO_SENDFILE) -static ssize_t ngx_http_copy_aio_sendfile_preload(ngx_buf_t *file); -static void ngx_http_copy_aio_sendfile_event_handler(ngx_event_t *ev); -#endif #endif #if (NGX_THREADS) static ngx_int_t ngx_http_copy_thread_handler(ngx_thread_task_t *task, @@ -128,9 +124,6 @@ ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in) #if (NGX_HAVE_FILE_AIO) if (ngx_file_aio && clcf->aio == NGX_HTTP_AIO_ON) { ctx->aio_handler = ngx_http_copy_aio_handler; -#if (NGX_HAVE_AIO_SENDFILE) - ctx->aio_preload = ngx_http_copy_aio_sendfile_preload; -#endif } #endif @@ -207,81 +200,6 @@ ngx_http_copy_aio_event_handler(ngx_event_t *ev) ngx_http_run_posted_requests(c); } - -#if (NGX_HAVE_AIO_SENDFILE) - -static ssize_t -ngx_http_copy_aio_sendfile_preload(ngx_buf_t *file) -{ - ssize_t n; - static u_char buf[1]; - ngx_event_aio_t *aio; - ngx_http_request_t *r; - ngx_output_chain_ctx_t *ctx; - - aio = file->file->aio; - r = aio->data; - - if (r->aio) { - /* - * tolerate sendfile() calls if another operation is already - * running; this can happen due to subrequests, multiple calls - * of the next body filter from a filter, or in HTTP/2 due to - * a write event on the main connection - */ - - return NGX_AGAIN; - } - - n = ngx_file_aio_read(file->file, buf, 1, file->file_pos, NULL); - - if (n == NGX_AGAIN) { - aio->handler = ngx_http_copy_aio_sendfile_event_handler; - - r->main->blocked++; - r->aio = 1; - - ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module); - ctx->aio = 1; - } - - return n; -} - - -static void -ngx_http_copy_aio_sendfile_event_handler(ngx_event_t *ev) -{ - ngx_event_aio_t *aio; - ngx_connection_t *c; - ngx_http_request_t *r; - - aio = ev->data; - r = aio->data; - c = r->connection; - - r->main->blocked--; - r->aio = 0; - ev->complete = 0; - -#if (NGX_HTTP_V2) - - if (r->stream) { - /* - * for HTTP/2, update write event to make sure processing will - * reach the main connection to handle sendfile() preload - */ - - c->write->ready = 1; - c->write->active = 0; - } - -#endif - - c->write->handler(c->write); -} - -#endif #endif diff --git a/src/os/unix/ngx_freebsd_sendfile_chain.c b/src/os/unix/ngx_freebsd_sendfile_chain.c index 750c12fc35..c939f6ad62 100644 --- a/src/os/unix/ngx_freebsd_sendfile_chain.c +++ b/src/os/unix/ngx_freebsd_sendfile_chain.c @@ -32,23 +32,22 @@ ngx_chain_t * ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - int rc, flags; - off_t send, prev_send, sent; - size_t file_size; - ssize_t n; - ngx_uint_t eintr, eagain; - ngx_err_t err; - ngx_buf_t *file; - ngx_event_t *wev; - ngx_chain_t *cl; - ngx_iovec_t header, trailer; - struct sf_hdtr hdtr; - struct iovec headers[NGX_IOVS_PREALLOCATE]; - struct iovec trailers[NGX_IOVS_PREALLOCATE]; -#if (NGX_HAVE_AIO_SENDFILE) - ngx_uint_t ebusy; - ngx_event_aio_t *aio; + int rc, flags; + off_t send, prev_send, sent; + size_t file_size; + ssize_t n; + ngx_err_t err; + ngx_buf_t *file; + ngx_uint_t eintr, eagain; +#if (NGX_HAVE_SENDFILE_NODISKIO) + ngx_uint_t ebusy; #endif + ngx_event_t *wev; + ngx_chain_t *cl; + ngx_iovec_t header, trailer; + struct sf_hdtr hdtr; + struct iovec headers[NGX_IOVS_PREALLOCATE]; + struct iovec trailers[NGX_IOVS_PREALLOCATE]; wev = c->write; @@ -77,11 +76,6 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) eagain = 0; flags = 0; -#if (NGX_HAVE_AIO_SENDFILE && NGX_SUPPRESS_WARN) - aio = NULL; - file = NULL; -#endif - header.iovs = headers; header.nalloc = NGX_IOVS_PREALLOCATE; @@ -90,7 +84,7 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) for ( ;; ) { eintr = 0; -#if (NGX_HAVE_AIO_SENDFILE) +#if (NGX_HAVE_SENDFILE_NODISKIO) ebusy = 0; #endif prev_send = send; @@ -179,9 +173,8 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) sent = 0; -#if (NGX_HAVE_AIO_SENDFILE) - aio = file->file->aio; - flags = (aio && aio->preload_handler) ? SF_NODISKIO : 0; +#if (NGX_HAVE_SENDFILE_NODISKIO) + flags = (c->busy_count <= 2) ? SF_NODISKIO : 0; #endif rc = sendfile(file->file->fd, c->fd, file->file_pos, @@ -199,7 +192,7 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) eintr = 1; break; -#if (NGX_HAVE_AIO_SENDFILE) +#if (NGX_HAVE_SENDFILE_NODISKIO) case NGX_EBUSY: ebusy = 1; break; @@ -252,41 +245,30 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) in = ngx_chain_update_sent(in, sent); -#if (NGX_HAVE_AIO_SENDFILE) +#if (NGX_HAVE_SENDFILE_NODISKIO) if (ebusy) { if (sent == 0) { c->busy_count++; - if (c->busy_count > 2) { - ngx_log_error(NGX_LOG_ALERT, c->log, 0, - "sendfile(%V) returned busy again", - &file->file->name); - - c->busy_count = 0; - aio->preload_handler = NULL; - - send = prev_send; - continue; - } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sendfile() busy, count:%d", c->busy_count); } else { c->busy_count = 0; } - n = aio->preload_handler(file); - - if (n > 0) { - send = prev_send + sent; - continue; + if (wev->posted) { + ngx_delete_posted_event(wev); } + ngx_post_event(wev, &ngx_posted_next_events); + + wev->ready = 0; return in; } - if (flags == SF_NODISKIO) { - c->busy_count = 0; - } + c->busy_count = 0; #endif From 2a00e6141f154d77a835e53b6527a1b3225f6f74 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Mon, 27 Dec 2021 19:48:42 +0300 Subject: [PATCH 09/13] SSL: SSL_sendfile(SF_NODISKIO) support. --- src/event/ngx_event_openssl.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index b03ca59ae4..57e2d44cab 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2942,7 +2942,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) { #ifdef BIO_get_ktls_send - int sslerr; + int sslerr, flags; ssize_t n; ngx_err_t err; @@ -2954,8 +2954,14 @@ ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) ngx_set_errno(0); +#if (NGX_HAVE_SENDFILE_NODISKIO) + flags = (c->busy_count <= 2) ? SF_NODISKIO : 0; +#else + flags = 0; +#endif + n = SSL_sendfile(c->ssl->connection, file->file->fd, file->file_pos, - size, 0); + size, flags); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %d", n); @@ -2974,6 +2980,10 @@ ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) ngx_post_event(c->read, &ngx_posted_events); } +#if (NGX_HAVE_SENDFILE_NODISKIO) + c->busy_count = 0; +#endif + c->sent += n; return n; @@ -3038,6 +3048,23 @@ ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) ngx_post_event(c->read, &ngx_posted_events); } +#if (NGX_HAVE_SENDFILE_NODISKIO) + + if (ngx_errno == EBUSY) { + c->busy_count++; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_sendfile() busy, count:%d", c->busy_count); + + if (c->write->posted) { + ngx_delete_posted_event(c->write); + } + + ngx_post_event(c->write, &ngx_posted_next_events); + } + +#endif + c->write->ready = 0; return NGX_AGAIN; } From 1f01183b9e6658749934313fd72f7f16c1918b54 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Mon, 27 Dec 2021 19:49:26 +0300 Subject: [PATCH 10/13] Support for sendfile(SF_NOCACHE). The SF_NOCACHE flag, introduced in FreeBSD 11 along with the new non-blocking sendfile() implementation by glebius@, makes it possible to use sendfile() along with the "directio" directive. --- src/core/ngx_output_chain.c | 17 +++++++++++++---- src/event/ngx_event_openssl.c | 6 ++++++ src/os/unix/ngx_freebsd_sendfile_chain.c | 6 ++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/core/ngx_output_chain.c b/src/core/ngx_output_chain.c index 41267eefff..8570742539 100644 --- a/src/core/ngx_output_chain.c +++ b/src/core/ngx_output_chain.c @@ -256,10 +256,6 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) } #endif - if (buf->in_file && buf->file->directio) { - return 0; - } - sendfile = ctx->sendfile; #if (NGX_SENDFILE_LIMIT) @@ -268,6 +264,19 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) sendfile = 0; } +#endif + +#if !(NGX_HAVE_SENDFILE_NODISKIO) + + /* + * With DIRECTIO, disable sendfile() unless sendfile(SF_NOCACHE) + * is available. + */ + + if (buf->in_file && buf->file->directio) { + sendfile = 0; + } + #endif if (!sendfile) { diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 57e2d44cab..33977af613 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2955,7 +2955,13 @@ ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) ngx_set_errno(0); #if (NGX_HAVE_SENDFILE_NODISKIO) + flags = (c->busy_count <= 2) ? SF_NODISKIO : 0; + + if (file->file->directio) { + flags |= SF_NOCACHE; + } + #else flags = 0; #endif diff --git a/src/os/unix/ngx_freebsd_sendfile_chain.c b/src/os/unix/ngx_freebsd_sendfile_chain.c index c939f6ad62..5c6a830d87 100644 --- a/src/os/unix/ngx_freebsd_sendfile_chain.c +++ b/src/os/unix/ngx_freebsd_sendfile_chain.c @@ -174,7 +174,13 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) sent = 0; #if (NGX_HAVE_SENDFILE_NODISKIO) + flags = (c->busy_count <= 2) ? SF_NODISKIO : 0; + + if (file->file->directio) { + flags |= SF_NOCACHE; + } + #endif rc = sendfile(file->file->fd, c->fd, file->file_pos, From d34c7b9fb0e0549f221abfb2cb1a2425f230e742 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Tue, 28 Dec 2021 17:56:16 +0300 Subject: [PATCH 11/13] Updated OpenSSL and PCRE used for win32 builds. --- misc/GNUmakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 36dd638361..58e9f7f00a 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -6,9 +6,9 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 -OPENSSL = openssl-1.1.1l +OPENSSL = openssl-1.1.1m ZLIB = zlib-1.2.11 -PCRE = pcre-8.44 +PCRE = pcre2-10.39 release: export From 3a7d332aef234d394e5fa2722ccdac437918fd36 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Tue, 28 Dec 2021 18:28:37 +0300 Subject: [PATCH 12/13] nginx-1.21.5-RELEASE --- docs/xml/nginx/changes.xml | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml index 0808b5c6ed..fd6da38ba7 100644 --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -5,6 +5,58 @@ + + + + +теперь nginx по умолчанию собирается с библиотекой PCRE2. + + +now nginx is built with the PCRE2 library by default. + + + + + +теперь nginx всегда использует sendfile(SF_NODISKIO) на FreeBSD. + + +now nginx always uses sendfile(SF_NODISKIO) on FreeBSD. + + + + + +поддержка sendfile(SF_NOCACHE) на FreeBSD. + + +support for sendfile(SF_NOCACHE) on FreeBSD. + + + + + +переменная $ssl_curve. + + +the $ssl_curve variable. + + + + + +при использовании HTTP/2 без SSL вместе с директивами sendfile и aio +соединения могли зависать. + + +connections might hang +when using HTTP/2 without SSL with the "sendfile" and "aio" directives. + + + + + + From 054944feb352ce16c5ef99e3fb58b8fd5796c0f1 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Tue, 28 Dec 2021 18:28:38 +0300 Subject: [PATCH 13/13] release-1.21.5 tag --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index ef68adcf45..a1e630018a 100644 --- a/.hgtags +++ b/.hgtags @@ -465,3 +465,4 @@ a68ac0677f8553b1f84d357bc9da114731ab5f47 release-1.21.1 bfbc52374adcbf2f9060afd62de940f6fab3bba5 release-1.21.2 2217a9c1d0b86026f22700b3c089545db1964f55 release-1.21.3 39be8a682c58308d9399cddd57e37f9fdb7bdf3e release-1.21.4 +d986378168fd4d70e0121cabac274c560cca9bdf release-1.21.5