From 5fbb6420628944b4c860bd12710407d639082d56 Mon Sep 17 00:00:00 2001 From: Jun Ouyang Date: Fri, 13 Mar 2026 20:26:29 +0800 Subject: [PATCH 1/5] bugfix: prevent use-after-free crash in ngx_http_lua_pipe by ensuring connections are closed before pool destruction in quic connection close path. --- src/ngx_http_lua_pipe.c | 72 ++++- t/191-pipe-proc-quic-close-crash.t | 124 ++++++++ util/build-and-test.sh | 462 +++++++++++++++++++++++++++++ 3 files changed, 650 insertions(+), 8 deletions(-) create mode 100644 t/191-pipe-proc-quic-close-crash.t create mode 100644 util/build-and-test.sh diff --git a/src/ngx_http_lua_pipe.c b/src/ngx_http_lua_pipe.c index 8c0884bcc2..0d2ad08e09 100644 --- a/src/ngx_http_lua_pipe.c +++ b/src/ngx_http_lua_pipe.c @@ -1200,6 +1200,40 @@ ngx_http_lua_ffi_pipe_proc_destroy(ngx_http_lua_ffi_pipe_proc_t *proc) } ngx_http_lua_pipe_proc_finalize(proc); + + /* + * pipe_proc_finalize → ngx_http_lua_pipe_close_helper may leave pipe + * connections open with active timers/posted events when there are + * pending I/O operations (handler != dummy_handler). Close them now + * before destroying the pool. + * + * Without this, when pool cleanup LIFO ordering causes pipe_proc_destroy + * to run before request_cleanup_handler (e.g. QUIC connection close path: + * ngx_quic_close_streams → ngx_http_free_request → ngx_destroy_pool), + * request_cleanup sees proc->pipe == NULL and returns early, leaving the + * timer live. The timer then fires after the request pool is freed, + * accessing a dangling wait_co_ctx pointer → SIGSEGV. + * + * ngx_close_connection handles everything: timers (ngx_del_timer), + * posted events (ngx_delete_posted_event), epoll removal, fd close, + * and connection recycling. + */ + + if (pipe->stdout_ctx && pipe->stdout_ctx->c) { + ngx_close_connection(pipe->stdout_ctx->c); + pipe->stdout_ctx->c = NULL; + } + + if (pipe->stderr_ctx && pipe->stderr_ctx->c) { + ngx_close_connection(pipe->stderr_ctx->c); + pipe->stderr_ctx->c = NULL; + } + + if (pipe->stdin_ctx && pipe->stdin_ctx->c) { + ngx_close_connection(pipe->stdin_ctx->c); + pipe->stdin_ctx->c = NULL; + } + ngx_destroy_pool(pipe->pool); proc->pipe = NULL; } @@ -2495,13 +2529,20 @@ ngx_http_lua_pipe_proc_read_stdout_cleanup(void *data) "lua pipe proc read stdout cleanup"); proc = wait_co_ctx->data; + + wait_co_ctx->cleanup = NULL; + + if (proc->pipe == NULL) { + /* pipe_proc_destroy already ran (LIFO pool cleanup) and cancelled + * timers/connections; nothing left to clean up here. */ + return; + } + c = proc->pipe->stdout_ctx->c; if (c) { rev = c->read; ngx_http_lua_pipe_clear_event(rev); } - - wait_co_ctx->cleanup = NULL; } @@ -2517,13 +2558,18 @@ ngx_http_lua_pipe_proc_read_stderr_cleanup(void *data) "lua pipe proc read stderr cleanup"); proc = wait_co_ctx->data; + + wait_co_ctx->cleanup = NULL; + + if (proc->pipe == NULL) { + return; + } + c = proc->pipe->stderr_ctx->c; if (c) { rev = c->read; ngx_http_lua_pipe_clear_event(rev); } - - wait_co_ctx->cleanup = NULL; } @@ -2539,13 +2585,18 @@ ngx_http_lua_pipe_proc_write_cleanup(void *data) "lua pipe proc write cleanup"); proc = wait_co_ctx->data; + + wait_co_ctx->cleanup = NULL; + + if (proc->pipe == NULL) { + return; + } + c = proc->pipe->stdin_ctx->c; if (c) { wev = c->write; ngx_http_lua_pipe_clear_event(wev); } - - wait_co_ctx->cleanup = NULL; } @@ -2561,13 +2612,18 @@ ngx_http_lua_pipe_proc_wait_cleanup(void *data) "lua pipe proc wait cleanup"); proc = wait_co_ctx->data; + + wait_co_ctx->cleanup = NULL; + + if (proc->pipe == NULL) { + return; + } + node = proc->pipe->node; pipe_node = (ngx_http_lua_pipe_node_t *) &node->color; pipe_node->wait_co_ctx = NULL; ngx_http_lua_pipe_clear_event(&wait_co_ctx->sleep); - - wait_co_ctx->cleanup = NULL; } diff --git a/t/191-pipe-proc-quic-close-crash.t b/t/191-pipe-proc-quic-close-crash.t new file mode 100644 index 0000000000..9bd148302e --- /dev/null +++ b/t/191-pipe-proc-quic-close-crash.t @@ -0,0 +1,124 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +# +# Regression test for use-after-free crash in ngx_http_lua_pipe_resume_read_stdout_handler. +# +# Root cause: pool cleanup runs in LIFO order. pipe_proc_destroy (registered +# when the pipe is spawned, i.e. *later*) therefore runs *before* +# request_cleanup_handler (registered at Lua handler init time). +# pipe_proc_destroy calls pipe_proc_finalize → ngx_http_lua_pipe_close_helper, +# which, when a read is in progress, posts the event rather than calling +# ngx_close_connection, leaving the read-timeout timer live. It then sets +# proc->pipe = NULL. When request_cleanup_handler runs next it sees +# proc->pipe == NULL and returns early, so the timer is never cancelled. +# After ngx_destroy_pool(r->pool) frees wait_co_ctx, the timer fires and +# dereferences the dangling pointer → SIGSEGV. +# +# Trigger path (QUIC-specific): +# QUIC connection close +# → ngx_quic_close_streams → sc->read->handler (no-op without lua_check_client_abort) +# → ngx_http_free_request → ngx_destroy_pool(r->pool) +# → LIFO pool cleanups: pipe_proc_destroy first, request_cleanup_handler second +# → timer remains live; pool freed; timer fires → SIGSEGV +# +# Fix (ngx_http_lua_ffi_pipe_proc_destroy): after pipe_proc_finalize, call +# ngx_close_connection on any pipe ctx whose connection is still open. +# ngx_close_connection removes both the read-timeout timer (ngx_del_timer) +# and any already-posted event (ngx_delete_posted_event), preventing the UAF. +# +# Timing: +# pipe stdout read timeout : 1 s (timer armed 1 s after request lands) +# curl --max-time : 0.5 s (QUIC connection closed while timer live) +# --- wait : 2 s (covers remaining ~0.5 s + safety margin) +# +# NOTE on Test::Nginx::Util internals: +# ssl_certificate is injected into the server block ONLY when +# ($UseHttp3 && !defined $block->http3), i.e. only in global HTTP3 mode. +# Using a per-block "--- http3" directive without TEST_NGINX_USE_HTTP3=1 +# skips the ssl_certificate injection and nginx refuses to start. +# Therefore this test enables global HTTP3 mode in a BEGIN block so that +# the module reads the env vars at load time. + +BEGIN { + require File::Basename; + require Cwd; + + # Default cert paths (generated by `util/build-and-test.sh --http3`). + my $t_dir = File::Basename::dirname(Cwd::abs_path(__FILE__)); + $ENV{TEST_NGINX_HTTP3_CRT} = "$t_dir/cert/http3/http3.crt" + unless defined $ENV{TEST_NGINX_HTTP3_CRT}; + $ENV{TEST_NGINX_HTTP3_KEY} = "$t_dir/cert/http3/http3.key" + unless defined $ENV{TEST_NGINX_HTTP3_KEY}; + + my $nginx = $ENV{TEST_NGINX_BINARY} || 'nginx'; + my $v = eval { `$nginx -V 2>&1` } // ''; + + if ($v !~ /--with-http_v3_module/) { + $SkipReason = "requires nginx built with --with-http_v3_module"; + + } elsif (!-f $ENV{TEST_NGINX_HTTP3_CRT} || !-f $ENV{TEST_NGINX_HTTP3_KEY}) { + $SkipReason = + "requires TEST_NGINX_HTTP3_CRT / TEST_NGINX_HTTP3_KEY cert files " + . "(run util/build-and-test.sh --http3 once to generate them)"; + + } else { + # Enable global HTTP3 mode so Test::Nginx::Util injects ssl_certificate + # into the server block and makes curl use --http3-only for all tests. + $ENV{TEST_NGINX_USE_HTTP3} = 1 unless defined $ENV{TEST_NGINX_USE_HTTP3}; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); + +master_on(); # master process needed to detect/survive worker crash +workers(1); + +repeat_each(1); + +# 1 subtest per block: the --- no_error_log [alert] check. +# (--- ignore_response suppresses the default status-code subtest.) +plan tests => repeat_each() * blocks(); + +log_level('info'); +no_long_string(); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + $http_config .= <<'_EOC_'; + lua_package_path "../lua-resty-core/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + + init_by_lua_block { + require "resty.core" + } +_EOC_ + $block->set_value("http_config", $http_config); +}); + +run_tests(); + +__DATA__ + +=== TEST 1: pipe read timer must not fire after pool is freed on QUIC connection close +--- config + location = /t { + content_by_lua_block { + -- Spawn a long-lived child; stdout will never produce output. + -- set_timeouts(write_timeout, stdout_timeout, stderr_timeout, wait_timeout) + local proc = require("ngx.pipe").spawn({"sleep", "100"}) + proc:set_timeouts(nil, 1000) -- 1 s stdout read timeout + + -- This call yields and arms a 1 s timer. + -- The test client closes the QUIC connection before the timer fires, + -- triggering the LIFO pool-cleanup use-after-free (without the fix). + proc:stdout_read_line() + } + } +--- request +GET /t +--- timeout: 0.5 +--- wait: 2 +--- ignore_response +--- curl_error eval: qr/\(28\)/ +--- no_error_log +[alert] diff --git a/util/build-and-test.sh b/util/build-and-test.sh new file mode 100644 index 0000000000..2c378b81d7 --- /dev/null +++ b/util/build-and-test.sh @@ -0,0 +1,462 @@ +#!/usr/bin/env bash +# Build and test script for lua-nginx-module (based on .travis.yml) +# Usage: bash util/build-and-test.sh [nginx_version] [--skip-deps] [--skip-build] [--http2] [--http3] +# +# Options: +# nginx_version NGINX version to build (default: 1.29.4) +# --skip-deps Skip cloning/updating dependencies +# --skip-build Skip build steps, only run tests +# --http2 Enable HTTP/2 testing (TEST_NGINX_USE_HTTP2=1) +# --http3 Enable HTTP/3 testing (TEST_NGINX_USE_HTTP3=1) +# --boring Use BoringSSL instead of OpenSSL + +set -e + +# ── Parse arguments ──────────────────────────────────────────────────────────── +NGINX_VERSION=1.29.4 +SKIP_DEPS=0 +SKIP_BUILD=0 +USE_HTTP2=0 +USE_HTTP3=0 +USE_BORINGSSL=0 + +for arg in "$@"; do + case "$arg" in + --skip-deps) SKIP_DEPS=1 ;; + --skip-build) SKIP_BUILD=1 ;; + --http2) USE_HTTP2=1 ;; + --http3) USE_HTTP3=1 ;; + --boring) USE_BORINGSSL=1 ;; + [0-9]*) NGINX_VERSION="$arg" ;; + *) echo "Unknown option: $arg"; exit 1 ;; + esac +done + +# ── Global environment variables ─────────────────────────────────────────────── +export JOBS=${JOBS:-$(nproc)} +export NGX_BUILD_JOBS=$JOBS +export CC=${CC:-gcc} +export NGX_BUILD_CC=$CC + +export PCRE2_PREFIX=/usr/local/openresty/pcre2 +export PCRE2_LIB=$PCRE2_PREFIX/lib +export PCRE2_INC=$PCRE2_PREFIX/include + +export OPENSSL_PREFIX=/usr/local/openresty/openssl3 +export OPENSSL_LIB=$OPENSSL_PREFIX/lib +export OPENSSL_INC=$OPENSSL_PREFIX/include + +export DRIZZLE_VER=2011.07.21 +export BORINGSSL_RELEASE=v20230902 +export BORINGSSL_BUILD=20230902 + +ARCH_RAW="$(uname -m)" +case "$ARCH_RAW" in + x86_64|amd64) BORINGSSL_ARCH=x64 ;; + *) BORINGSSL_ARCH="$ARCH_RAW" ;; +esac + +if command -v lsb_release >/dev/null 2>&1; then + DIST_CODENAME="$(lsb_release -sc)" +elif [ -r /etc/os-release ]; then + DIST_CODENAME="$(. /etc/os-release && echo "${VERSION_CODENAME:-}")" +fi +DIST_CODENAME="${DIST_CODENAME:-focal}" + +export TEST_NGINX_SLEEP=0.006 +export TEST_NGINX_SKIP_COSOCKET_LOG_TEST=1 +export MALLOC_PERTURB_=9 +export TEST_NGINX_TIMEOUT=${TEST_NGINX_TIMEOUT:-5} +export TEST_NGINX_RESOLVER=8.8.4.4 + +if [ $USE_HTTP2 -eq 1 ]; then + export TEST_NGINX_USE_HTTP2=1 +fi + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +BUILDROOT="$ROOT/buildroot" +DOWNLOAD_ROOT="$BUILDROOT/downloads" +DEPS_ROOT="$BUILDROOT/deps" +DOWNLOAD_CACHE="$DOWNLOAD_ROOT/cache" +MODULES_ROOT="$DOWNLOAD_ROOT/modules" +TEST_NGINX_ROOT="$DOWNLOAD_ROOT/test-nginx" +OPENRESTY_UTILS_ROOT="$DOWNLOAD_ROOT/openresty-devel-utils" +MOCKEAGAIN_ROOT="$DOWNLOAD_ROOT/mockeagain" +LUA_CJSON_ROOT="$DOWNLOAD_ROOT/lua-cjson" +LUAJIT_SRC_ROOT="$DOWNLOAD_ROOT/luajit2" +DRIZZLE_SRC_ROOT="$DOWNLOAD_ROOT/drizzle-src" +BORINGSSL_TARBALL="" + +# LuaJIT and drizzle install into buildroot-local deps/ (no sudo needed) +export LUAJIT_PREFIX="$DEPS_ROOT/luajit21" +export LUAJIT_LIB=$LUAJIT_PREFIX/lib +export LUAJIT_INC="$LUAJIT_PREFIX/include/luajit-2.0" +export LUA_INCLUDE_DIR=$LUAJIT_INC + +export LIBDRIZZLE_PREFIX="$DEPS_ROOT/drizzle" +export LIBDRIZZLE_INC=$LIBDRIZZLE_PREFIX/include/libdrizzle-1.0 +export LIBDRIZZLE_LIB=$LIBDRIZZLE_PREFIX/lib + +export LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH + +mkdir -p \ + "$DOWNLOAD_CACHE" \ + "$MODULES_ROOT" \ + "$DEPS_ROOT" \ + "$DRIZZLE_SRC_ROOT" + +log() { echo "==> $*"; } +run_logged() { + "$@" > build.log 2>&1 || { echo "FAILED: $*"; cat build.log; exit 1; } +} + +set_luajit_include_dir() { + local candidates=( + "$LUAJIT_PREFIX/include/luajit-2.1" + "$LUAJIT_PREFIX/include/luajit-2.0" + "$LUAJIT_PREFIX/include" + ) + local candidate + for candidate in "${candidates[@]}"; do + if [ -f "$candidate/luajit.h" ]; then + export LUAJIT_INC="$candidate" + export LUA_INCLUDE_DIR="$candidate" + return + fi + done +} + +clean_ngx_buildroot() { + if [ -d "$BUILDROOT" ]; then + find "$BUILDROOT" -mindepth 1 -maxdepth 1 \ + ! -name downloads \ + ! -name deps \ + -exec rm -rf {} + + fi +} + +# ── Step 1: System package dependencies ─────────────────────────────────────── +install_sys_deps() { + log "Installing system packages..." + sudo apt-get update -q + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ack axel cpanminus \ + libtest-base-perl libtext-diff-perl liburi-perl libwww-perl \ + libtest-longstring-perl liblist-moreutils-perl \ + libgd-dev time cmake libunwind-dev wget \ + libbrotli1 lsb-release gnupg ca-certificates \ + mysql-server redis-server memcached gcc make + + # Install OpenResty apt repo for PCRE2 and OpenSSL3 + if ! dpkg -l openresty-pcre2 &>/dev/null; then + wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - + echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" \ + | sudo tee /etc/apt/sources.list.d/openresty.list + sudo apt-get update -q + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + openresty-pcre2 openresty-openssl3 \ + openresty-pcre2-dev openresty-openssl3-dev + fi + + log "Installing Perl test modules..." + /usr/bin/env perl "$(command -v cpanm)" --sudo --notest Test::Nginx IPC::Run +} + +# ── Step 2: Clone / update dependency repos ──────────────────────────────────── +clone_or_update() { + local url="$1" dest="$2" + local branch="${3:-}" + if [ -d "$dest/.git" ]; then + log "Updating $(basename "$dest")..." + git -C "$dest" pull --quiet + else + log "Cloning $(basename "$dest")..." + if [ -n "$branch" ]; then + git clone -b "$branch" --depth=1 "$url" "$dest" + else + git clone --depth=1 "$url" "$dest" + fi + fi +} + +install_deps() { + log "Cloning dependency repositories..." + cd "$ROOT" + + clone_or_update https://github.com/openresty/test-nginx.git "$TEST_NGINX_ROOT" + clone_or_update https://github.com/openresty/openresty.git "$MODULES_ROOT/openresty" + clone_or_update https://github.com/openresty/no-pool-nginx.git "$MODULES_ROOT/no-pool-nginx" + clone_or_update https://github.com/openresty/openresty-devel-utils.git "$OPENRESTY_UTILS_ROOT" + clone_or_update https://github.com/openresty/mockeagain.git "$MOCKEAGAIN_ROOT" + clone_or_update https://github.com/openresty/lua-cjson.git "$LUA_CJSON_ROOT" + clone_or_update https://github.com/openresty/lua-upstream-nginx-module.git "$MODULES_ROOT/lua-upstream-nginx-module" + clone_or_update https://github.com/openresty/echo-nginx-module.git "$MODULES_ROOT/echo-nginx-module" + clone_or_update https://github.com/openresty/nginx-eval-module.git "$MODULES_ROOT/nginx-eval-module" + clone_or_update https://github.com/simpl/ngx_devel_kit.git "$MODULES_ROOT/ndk-nginx-module" + clone_or_update https://github.com/FRiCKLE/ngx_coolkit.git "$MODULES_ROOT/coolkit-nginx-module" + clone_or_update https://github.com/openresty/headers-more-nginx-module.git "$MODULES_ROOT/headers-more-nginx-module" + clone_or_update https://github.com/openresty/drizzle-nginx-module.git "$MODULES_ROOT/drizzle-nginx-module" + clone_or_update https://github.com/openresty/set-misc-nginx-module.git "$MODULES_ROOT/set-misc-nginx-module" + clone_or_update https://github.com/openresty/memc-nginx-module.git "$MODULES_ROOT/memc-nginx-module" + clone_or_update https://github.com/openresty/rds-json-nginx-module.git "$MODULES_ROOT/rds-json-nginx-module" + clone_or_update https://github.com/openresty/srcache-nginx-module.git "$MODULES_ROOT/srcache-nginx-module" + clone_or_update https://github.com/openresty/redis2-nginx-module.git "$MODULES_ROOT/redis2-nginx-module" + clone_or_update https://github.com/openresty/lua-resty-core.git "$MODULES_ROOT/lua-resty-core" + clone_or_update https://github.com/openresty/lua-resty-lrucache.git "$MODULES_ROOT/lua-resty-lrucache" + clone_or_update https://github.com/openresty/lua-resty-mysql.git "$MODULES_ROOT/lua-resty-mysql" + clone_or_update https://github.com/spacewander/lua-resty-rsa.git "$MODULES_ROOT/lua-resty-rsa" + clone_or_update https://github.com/openresty/lua-resty-string.git "$MODULES_ROOT/lua-resty-string" + clone_or_update https://github.com/openresty/stream-lua-nginx-module.git "$MODULES_ROOT/stream-lua-nginx-module" + clone_or_update https://github.com/openresty/luajit2.git "$LUAJIT_SRC_ROOT" "v2.1-agentzh" + + # Download drizzle prebuilt tarball + if [ ! -f "$DOWNLOAD_CACHE/drizzle7-$DRIZZLE_VER.tar.gz" ]; then + log "Downloading drizzle7 $DRIZZLE_VER..." + wget -q -P "$DOWNLOAD_CACHE" \ + "https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/drizzle7-$DRIZZLE_VER.tar.gz" + fi + + if [ $USE_BORINGSSL -eq 1 ]; then + local candidates=( + "boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-${DIST_CODENAME}.tar.gz" + "boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-focal.tar.gz" + ) + local pkg="" + for candidate in "${candidates[@]}"; do + if [ -f "$DOWNLOAD_CACHE/$candidate" ]; then + pkg="$candidate" + break + fi + done + + if [ -z "$pkg" ]; then + log "Downloading BoringSSL prebuilt (arch=$BORINGSSL_ARCH, distro=$DIST_CODENAME)..." + for candidate in "${candidates[@]}"; do + if wget -q -P "$DOWNLOAD_CACHE" \ + "https://github.com/openresty/openresty-deps-prebuild/releases/download/$BORINGSSL_RELEASE/$candidate" + then + pkg="$candidate" + break + fi + done + fi + + if [ -n "$pkg" ]; then + BORINGSSL_TARBALL="$DOWNLOAD_CACHE/$pkg" + log "Using BoringSSL package: $pkg" + fi + fi +} + +# ── Step 3: Build LuaJIT ────────────────────────────────────────────────────── +build_luajit() { + log "Building LuaJIT..." + cd "$LUAJIT_SRC_ROOT" + run_logged make -j"$JOBS" CCDEBUG=-g Q= PREFIX="$LUAJIT_PREFIX" CC="$CC" \ + XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' + run_logged make install PREFIX="$LUAJIT_PREFIX" + set_luajit_include_dir + cd "$ROOT" +} + +# ── Step 4: Build drizzle ───────────────────────────────────────────────────── +build_drizzle() { + log "Building drizzle7..." + cd "$DRIZZLE_SRC_ROOT" + rm -rf "drizzle7-$DRIZZLE_VER" + tar xzf "$DOWNLOAD_CACHE/drizzle7-$DRIZZLE_VER.tar.gz" + cd "$DRIZZLE_SRC_ROOT/drizzle7-$DRIZZLE_VER" + run_logged ./configure --prefix="$LIBDRIZZLE_PREFIX" --without-server + run_logged make libdrizzle-1.0 -j"$JOBS" + run_logged make install-libdrizzle-1.0 + cd "$ROOT" + rm -rf "$DRIZZLE_SRC_ROOT/drizzle7-$DRIZZLE_VER" +} + +# ── Step 5: Build mockeagain ────────────────────────────────────────────────── +build_mockeagain() { + log "Building mockeagain..." + cd "$MOCKEAGAIN_ROOT" + run_logged make CC="$CC" -j"$JOBS" + cd "$ROOT" +} + +# ── Step 6: Build lua-cjson ─────────────────────────────────────────────────── +build_lua_cjson() { + log "Building lua-cjson..." + cd "$LUA_CJSON_ROOT" + # Install cjson.so alongside LuaJIT's own Lua C modules (no sudo needed) + local cmod_dir="$LUAJIT_PREFIX/lib/lua/5.1" + mkdir -p "$cmod_dir" + run_logged make -j"$JOBS" LUA_CMODULE_DIR="$cmod_dir" LUA_MODULE_DIR="$cmod_dir" + run_logged make install LUA_CMODULE_DIR="$cmod_dir" LUA_MODULE_DIR="$cmod_dir" + cd "$ROOT" +} + +# ── Step 7: Set up BoringSSL (optional) ─────────────────────────────────────── +setup_boringssl() { + if [ $USE_BORINGSSL -eq 1 ]; then + log "Setting up BoringSSL..." + # Override OPENSSL_PREFIX to a buildroot-local path (no sudo needed) + export OPENSSL_PREFIX="$DEPS_ROOT/boringssl" + export OPENSSL_LIB=$OPENSSL_PREFIX/lib + export OPENSSL_INC=$OPENSSL_PREFIX/include + if [ -z "$BORINGSSL_TARBALL" ]; then + local candidates=( + "$DOWNLOAD_CACHE/boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-${DIST_CODENAME}.tar.gz" + "$DOWNLOAD_CACHE/boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-focal.tar.gz" + ) + local candidate + for candidate in "${candidates[@]}"; do + if [ -f "$candidate" ]; then + BORINGSSL_TARBALL="$candidate" + break + fi + done + fi + + if [ -z "$BORINGSSL_TARBALL" ] && \ + [ -f "$OPENSSL_INC/openssl/base.h" ] && \ + [ -f "$OPENSSL_LIB/libssl.so" ] && \ + [ -f "$OPENSSL_LIB/libcrypto.so" ] + then + log "Reusing existing BoringSSL in $OPENSSL_PREFIX" + return + fi + + if [ -z "$BORINGSSL_TARBALL" ] || [ ! -f "$BORINGSSL_TARBALL" ]; then + echo "ERROR: BoringSSL package not found for arch=$BORINGSSL_ARCH distro=$DIST_CODENAME." >&2 + echo "Tried download-cache and fallback focal package from $BORINGSSL_RELEASE." >&2 + exit 1 + fi + + rm -fr "$OPENSSL_PREFIX" + mkdir -p "$OPENSSL_PREFIX" + tar -C "$OPENSSL_PREFIX" -xf "$BORINGSSL_TARBALL" --strip-components=1 + fi +} + +# ── Step 8: Build nginx ─────────────────────────────────────────────────────── +build_nginx() { + log "Building nginx $NGINX_VERSION..." + export MODULES_ROOT + export PATH="$ROOT/work/nginx/sbin:$OPENRESTY_UTILS_ROOT:$PATH" + + # Run all three build variants (mirrors CI) + run_logged sh "$ROOT/util/build-without-ssl.sh" "$NGINX_VERSION" + run_logged sh "$ROOT/util/build-with-dd.sh" "$NGINX_VERSION" + clean_ngx_buildroot + run_logged sh "$ROOT/util/build.sh" "$NGINX_VERSION" + + log "nginx version:" + nginx -V + + log "nginx dynamic library links:" + ldd "$(which nginx)" | grep -E 'luajit|ssl|pcre' || true +} + +# ── Step 9: Set up iptables / sysctl (mirrors CI network rules) ────────────── +setup_network() { + log "Configuring network rules..." + sudo iptables -I OUTPUT 1 -p udp --dport 10086 -j REJECT 2>/dev/null || true + sudo iptables -I OUTPUT -p tcp --dst 127.0.0.2 --dport 12345 -j DROP 2>/dev/null || true + sudo iptables -I OUTPUT -p udp --dst 127.0.0.2 --dport 12345 -j DROP 2>/dev/null || true + sudo ip route add prohibit 0.0.0.1/32 2>/dev/null || true + sudo sysctl -w kernel.pid_max=10000 2>/dev/null || true +} + +# ── Step 10: Set up MySQL ───────────────────────────────────────────────────── +setup_mysql() { + log "Setting up MySQL test database..." + mysql -uroot -e " + CREATE DATABASE IF NOT EXISTS ngx_test; + CREATE USER IF NOT EXISTS 'ngx_test'@'%' IDENTIFIED WITH mysql_native_password BY 'ngx_test'; + GRANT ALL ON ngx_test.* TO 'ngx_test'@'%'; + FLUSH PRIVILEGES; + " 2>/dev/null || \ + mysql -uroot --password='' -e " + CREATE DATABASE IF NOT EXISTS ngx_test; + CREATE USER IF NOT EXISTS 'ngx_test'@'%' IDENTIFIED BY 'ngx_test'; + GRANT ALL ON ngx_test.* TO 'ngx_test'@'%'; + FLUSH PRIVILEGES; + " 2>/dev/null || log "WARNING: MySQL setup failed (tests requiring MySQL may fail)" +} + +# ── Step 11: Run tests ──────────────────────────────────────────────────────── +run_tests() { + log "Running tests..." + cd "$ROOT" + + export PATH="$ROOT/work/nginx/sbin:$OPENRESTY_UTILS_ROOT:$PATH" + export LD_PRELOAD="$MOCKEAGAIN_ROOT/mockeagain.so" + export LD_LIBRARY_PATH="$MOCKEAGAIN_ROOT:$LD_LIBRARY_PATH" + export TEST_NGINX_HTTP3_CRT="$ROOT/t/cert/http3/http3.crt" + export TEST_NGINX_HTTP3_KEY="$ROOT/t/cert/http3/http3.key" + + if [ $USE_HTTP3 -eq 1 ]; then + export TEST_NGINX_USE_HTTP3=1 + export TEST_NGINX_QUIC_IDLE_TIMEOUT=3 + fi + + # Clean up stale nc_server from previous aborted runs. + pkill -f "$ROOT/util/nc_server.py" 2>/dev/null || true + + # Start nc_server in background (used by some tests) + python3 "$ROOT/util/nc_server.py" & + NC_PID=$! + trap 'kill "$NC_PID" 2>/dev/null || true' EXIT INT TERM + + # Allow passing specific test files via TEST_NGINX_TARGETS env var + TARGETS="${TEST_NGINX_TARGETS:-t/}" + CORE_PERL_LIB="$(perl -MConfig -e 'print $Config{installprivlib}')" + CORE_PERL_ARCH="$(perl -MConfig -e 'print $Config{archlib}')" + + /usr/bin/env perl "$(command -v prove)" \ + -I"$CORE_PERL_LIB" \ + -I"$CORE_PERL_ARCH" \ + -I. \ + -I"$TEST_NGINX_ROOT/inc" \ + -I"$TEST_NGINX_ROOT/lib" \ + -r $TARGETS + + kill "$NC_PID" 2>/dev/null || true + trap - EXIT INT TERM +} + +# ── Source code style check (mirrors CI before_install) ─────────────────────── +check_style() { + log "Checking source code style..." + cd "$ROOT" + if grep -n -P '(?<=.{80}).+' $(find src -name '*.c') $(find . -name '*.h') 2>/dev/null; then + echo "ERROR: Found C source lines exceeding 80 columns." >&2 + exit 1 + fi + if grep -n -P '\t+' $(find src -name '*.c') $(find . -name '*.h') 2>/dev/null; then + echo "ERROR: Cannot use tabs." >&2 + exit 1 + fi + log "Style check passed." +} + +# ── Main ─────────────────────────────────────────────────────────────────────── +cd "$ROOT" + +if [ $SKIP_BUILD -eq 0 ]; then + # check_style + + if [ $SKIP_DEPS -eq 0 ]; then + # install_sys_deps + install_deps + fi + + build_luajit + build_drizzle + build_mockeagain + build_lua_cjson + setup_boringssl + # setup_network + # setup_mysql + build_nginx +fi + +run_tests From b64d1fb0c82f4aa3f85267d3f58e33d2fcb736f3 Mon Sep 17 00:00:00 2001 From: Jun Ouyang Date: Fri, 13 Mar 2026 20:27:37 +0800 Subject: [PATCH 2/5] fix --- util/build-and-test.sh | 462 ----------------------------------------- 1 file changed, 462 deletions(-) delete mode 100644 util/build-and-test.sh diff --git a/util/build-and-test.sh b/util/build-and-test.sh deleted file mode 100644 index 2c378b81d7..0000000000 --- a/util/build-and-test.sh +++ /dev/null @@ -1,462 +0,0 @@ -#!/usr/bin/env bash -# Build and test script for lua-nginx-module (based on .travis.yml) -# Usage: bash util/build-and-test.sh [nginx_version] [--skip-deps] [--skip-build] [--http2] [--http3] -# -# Options: -# nginx_version NGINX version to build (default: 1.29.4) -# --skip-deps Skip cloning/updating dependencies -# --skip-build Skip build steps, only run tests -# --http2 Enable HTTP/2 testing (TEST_NGINX_USE_HTTP2=1) -# --http3 Enable HTTP/3 testing (TEST_NGINX_USE_HTTP3=1) -# --boring Use BoringSSL instead of OpenSSL - -set -e - -# ── Parse arguments ──────────────────────────────────────────────────────────── -NGINX_VERSION=1.29.4 -SKIP_DEPS=0 -SKIP_BUILD=0 -USE_HTTP2=0 -USE_HTTP3=0 -USE_BORINGSSL=0 - -for arg in "$@"; do - case "$arg" in - --skip-deps) SKIP_DEPS=1 ;; - --skip-build) SKIP_BUILD=1 ;; - --http2) USE_HTTP2=1 ;; - --http3) USE_HTTP3=1 ;; - --boring) USE_BORINGSSL=1 ;; - [0-9]*) NGINX_VERSION="$arg" ;; - *) echo "Unknown option: $arg"; exit 1 ;; - esac -done - -# ── Global environment variables ─────────────────────────────────────────────── -export JOBS=${JOBS:-$(nproc)} -export NGX_BUILD_JOBS=$JOBS -export CC=${CC:-gcc} -export NGX_BUILD_CC=$CC - -export PCRE2_PREFIX=/usr/local/openresty/pcre2 -export PCRE2_LIB=$PCRE2_PREFIX/lib -export PCRE2_INC=$PCRE2_PREFIX/include - -export OPENSSL_PREFIX=/usr/local/openresty/openssl3 -export OPENSSL_LIB=$OPENSSL_PREFIX/lib -export OPENSSL_INC=$OPENSSL_PREFIX/include - -export DRIZZLE_VER=2011.07.21 -export BORINGSSL_RELEASE=v20230902 -export BORINGSSL_BUILD=20230902 - -ARCH_RAW="$(uname -m)" -case "$ARCH_RAW" in - x86_64|amd64) BORINGSSL_ARCH=x64 ;; - *) BORINGSSL_ARCH="$ARCH_RAW" ;; -esac - -if command -v lsb_release >/dev/null 2>&1; then - DIST_CODENAME="$(lsb_release -sc)" -elif [ -r /etc/os-release ]; then - DIST_CODENAME="$(. /etc/os-release && echo "${VERSION_CODENAME:-}")" -fi -DIST_CODENAME="${DIST_CODENAME:-focal}" - -export TEST_NGINX_SLEEP=0.006 -export TEST_NGINX_SKIP_COSOCKET_LOG_TEST=1 -export MALLOC_PERTURB_=9 -export TEST_NGINX_TIMEOUT=${TEST_NGINX_TIMEOUT:-5} -export TEST_NGINX_RESOLVER=8.8.4.4 - -if [ $USE_HTTP2 -eq 1 ]; then - export TEST_NGINX_USE_HTTP2=1 -fi - -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -BUILDROOT="$ROOT/buildroot" -DOWNLOAD_ROOT="$BUILDROOT/downloads" -DEPS_ROOT="$BUILDROOT/deps" -DOWNLOAD_CACHE="$DOWNLOAD_ROOT/cache" -MODULES_ROOT="$DOWNLOAD_ROOT/modules" -TEST_NGINX_ROOT="$DOWNLOAD_ROOT/test-nginx" -OPENRESTY_UTILS_ROOT="$DOWNLOAD_ROOT/openresty-devel-utils" -MOCKEAGAIN_ROOT="$DOWNLOAD_ROOT/mockeagain" -LUA_CJSON_ROOT="$DOWNLOAD_ROOT/lua-cjson" -LUAJIT_SRC_ROOT="$DOWNLOAD_ROOT/luajit2" -DRIZZLE_SRC_ROOT="$DOWNLOAD_ROOT/drizzle-src" -BORINGSSL_TARBALL="" - -# LuaJIT and drizzle install into buildroot-local deps/ (no sudo needed) -export LUAJIT_PREFIX="$DEPS_ROOT/luajit21" -export LUAJIT_LIB=$LUAJIT_PREFIX/lib -export LUAJIT_INC="$LUAJIT_PREFIX/include/luajit-2.0" -export LUA_INCLUDE_DIR=$LUAJIT_INC - -export LIBDRIZZLE_PREFIX="$DEPS_ROOT/drizzle" -export LIBDRIZZLE_INC=$LIBDRIZZLE_PREFIX/include/libdrizzle-1.0 -export LIBDRIZZLE_LIB=$LIBDRIZZLE_PREFIX/lib - -export LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH - -mkdir -p \ - "$DOWNLOAD_CACHE" \ - "$MODULES_ROOT" \ - "$DEPS_ROOT" \ - "$DRIZZLE_SRC_ROOT" - -log() { echo "==> $*"; } -run_logged() { - "$@" > build.log 2>&1 || { echo "FAILED: $*"; cat build.log; exit 1; } -} - -set_luajit_include_dir() { - local candidates=( - "$LUAJIT_PREFIX/include/luajit-2.1" - "$LUAJIT_PREFIX/include/luajit-2.0" - "$LUAJIT_PREFIX/include" - ) - local candidate - for candidate in "${candidates[@]}"; do - if [ -f "$candidate/luajit.h" ]; then - export LUAJIT_INC="$candidate" - export LUA_INCLUDE_DIR="$candidate" - return - fi - done -} - -clean_ngx_buildroot() { - if [ -d "$BUILDROOT" ]; then - find "$BUILDROOT" -mindepth 1 -maxdepth 1 \ - ! -name downloads \ - ! -name deps \ - -exec rm -rf {} + - fi -} - -# ── Step 1: System package dependencies ─────────────────────────────────────── -install_sys_deps() { - log "Installing system packages..." - sudo apt-get update -q - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - ack axel cpanminus \ - libtest-base-perl libtext-diff-perl liburi-perl libwww-perl \ - libtest-longstring-perl liblist-moreutils-perl \ - libgd-dev time cmake libunwind-dev wget \ - libbrotli1 lsb-release gnupg ca-certificates \ - mysql-server redis-server memcached gcc make - - # Install OpenResty apt repo for PCRE2 and OpenSSL3 - if ! dpkg -l openresty-pcre2 &>/dev/null; then - wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - - echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" \ - | sudo tee /etc/apt/sources.list.d/openresty.list - sudo apt-get update -q - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - openresty-pcre2 openresty-openssl3 \ - openresty-pcre2-dev openresty-openssl3-dev - fi - - log "Installing Perl test modules..." - /usr/bin/env perl "$(command -v cpanm)" --sudo --notest Test::Nginx IPC::Run -} - -# ── Step 2: Clone / update dependency repos ──────────────────────────────────── -clone_or_update() { - local url="$1" dest="$2" - local branch="${3:-}" - if [ -d "$dest/.git" ]; then - log "Updating $(basename "$dest")..." - git -C "$dest" pull --quiet - else - log "Cloning $(basename "$dest")..." - if [ -n "$branch" ]; then - git clone -b "$branch" --depth=1 "$url" "$dest" - else - git clone --depth=1 "$url" "$dest" - fi - fi -} - -install_deps() { - log "Cloning dependency repositories..." - cd "$ROOT" - - clone_or_update https://github.com/openresty/test-nginx.git "$TEST_NGINX_ROOT" - clone_or_update https://github.com/openresty/openresty.git "$MODULES_ROOT/openresty" - clone_or_update https://github.com/openresty/no-pool-nginx.git "$MODULES_ROOT/no-pool-nginx" - clone_or_update https://github.com/openresty/openresty-devel-utils.git "$OPENRESTY_UTILS_ROOT" - clone_or_update https://github.com/openresty/mockeagain.git "$MOCKEAGAIN_ROOT" - clone_or_update https://github.com/openresty/lua-cjson.git "$LUA_CJSON_ROOT" - clone_or_update https://github.com/openresty/lua-upstream-nginx-module.git "$MODULES_ROOT/lua-upstream-nginx-module" - clone_or_update https://github.com/openresty/echo-nginx-module.git "$MODULES_ROOT/echo-nginx-module" - clone_or_update https://github.com/openresty/nginx-eval-module.git "$MODULES_ROOT/nginx-eval-module" - clone_or_update https://github.com/simpl/ngx_devel_kit.git "$MODULES_ROOT/ndk-nginx-module" - clone_or_update https://github.com/FRiCKLE/ngx_coolkit.git "$MODULES_ROOT/coolkit-nginx-module" - clone_or_update https://github.com/openresty/headers-more-nginx-module.git "$MODULES_ROOT/headers-more-nginx-module" - clone_or_update https://github.com/openresty/drizzle-nginx-module.git "$MODULES_ROOT/drizzle-nginx-module" - clone_or_update https://github.com/openresty/set-misc-nginx-module.git "$MODULES_ROOT/set-misc-nginx-module" - clone_or_update https://github.com/openresty/memc-nginx-module.git "$MODULES_ROOT/memc-nginx-module" - clone_or_update https://github.com/openresty/rds-json-nginx-module.git "$MODULES_ROOT/rds-json-nginx-module" - clone_or_update https://github.com/openresty/srcache-nginx-module.git "$MODULES_ROOT/srcache-nginx-module" - clone_or_update https://github.com/openresty/redis2-nginx-module.git "$MODULES_ROOT/redis2-nginx-module" - clone_or_update https://github.com/openresty/lua-resty-core.git "$MODULES_ROOT/lua-resty-core" - clone_or_update https://github.com/openresty/lua-resty-lrucache.git "$MODULES_ROOT/lua-resty-lrucache" - clone_or_update https://github.com/openresty/lua-resty-mysql.git "$MODULES_ROOT/lua-resty-mysql" - clone_or_update https://github.com/spacewander/lua-resty-rsa.git "$MODULES_ROOT/lua-resty-rsa" - clone_or_update https://github.com/openresty/lua-resty-string.git "$MODULES_ROOT/lua-resty-string" - clone_or_update https://github.com/openresty/stream-lua-nginx-module.git "$MODULES_ROOT/stream-lua-nginx-module" - clone_or_update https://github.com/openresty/luajit2.git "$LUAJIT_SRC_ROOT" "v2.1-agentzh" - - # Download drizzle prebuilt tarball - if [ ! -f "$DOWNLOAD_CACHE/drizzle7-$DRIZZLE_VER.tar.gz" ]; then - log "Downloading drizzle7 $DRIZZLE_VER..." - wget -q -P "$DOWNLOAD_CACHE" \ - "https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/drizzle7-$DRIZZLE_VER.tar.gz" - fi - - if [ $USE_BORINGSSL -eq 1 ]; then - local candidates=( - "boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-${DIST_CODENAME}.tar.gz" - "boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-focal.tar.gz" - ) - local pkg="" - for candidate in "${candidates[@]}"; do - if [ -f "$DOWNLOAD_CACHE/$candidate" ]; then - pkg="$candidate" - break - fi - done - - if [ -z "$pkg" ]; then - log "Downloading BoringSSL prebuilt (arch=$BORINGSSL_ARCH, distro=$DIST_CODENAME)..." - for candidate in "${candidates[@]}"; do - if wget -q -P "$DOWNLOAD_CACHE" \ - "https://github.com/openresty/openresty-deps-prebuild/releases/download/$BORINGSSL_RELEASE/$candidate" - then - pkg="$candidate" - break - fi - done - fi - - if [ -n "$pkg" ]; then - BORINGSSL_TARBALL="$DOWNLOAD_CACHE/$pkg" - log "Using BoringSSL package: $pkg" - fi - fi -} - -# ── Step 3: Build LuaJIT ────────────────────────────────────────────────────── -build_luajit() { - log "Building LuaJIT..." - cd "$LUAJIT_SRC_ROOT" - run_logged make -j"$JOBS" CCDEBUG=-g Q= PREFIX="$LUAJIT_PREFIX" CC="$CC" \ - XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' - run_logged make install PREFIX="$LUAJIT_PREFIX" - set_luajit_include_dir - cd "$ROOT" -} - -# ── Step 4: Build drizzle ───────────────────────────────────────────────────── -build_drizzle() { - log "Building drizzle7..." - cd "$DRIZZLE_SRC_ROOT" - rm -rf "drizzle7-$DRIZZLE_VER" - tar xzf "$DOWNLOAD_CACHE/drizzle7-$DRIZZLE_VER.tar.gz" - cd "$DRIZZLE_SRC_ROOT/drizzle7-$DRIZZLE_VER" - run_logged ./configure --prefix="$LIBDRIZZLE_PREFIX" --without-server - run_logged make libdrizzle-1.0 -j"$JOBS" - run_logged make install-libdrizzle-1.0 - cd "$ROOT" - rm -rf "$DRIZZLE_SRC_ROOT/drizzle7-$DRIZZLE_VER" -} - -# ── Step 5: Build mockeagain ────────────────────────────────────────────────── -build_mockeagain() { - log "Building mockeagain..." - cd "$MOCKEAGAIN_ROOT" - run_logged make CC="$CC" -j"$JOBS" - cd "$ROOT" -} - -# ── Step 6: Build lua-cjson ─────────────────────────────────────────────────── -build_lua_cjson() { - log "Building lua-cjson..." - cd "$LUA_CJSON_ROOT" - # Install cjson.so alongside LuaJIT's own Lua C modules (no sudo needed) - local cmod_dir="$LUAJIT_PREFIX/lib/lua/5.1" - mkdir -p "$cmod_dir" - run_logged make -j"$JOBS" LUA_CMODULE_DIR="$cmod_dir" LUA_MODULE_DIR="$cmod_dir" - run_logged make install LUA_CMODULE_DIR="$cmod_dir" LUA_MODULE_DIR="$cmod_dir" - cd "$ROOT" -} - -# ── Step 7: Set up BoringSSL (optional) ─────────────────────────────────────── -setup_boringssl() { - if [ $USE_BORINGSSL -eq 1 ]; then - log "Setting up BoringSSL..." - # Override OPENSSL_PREFIX to a buildroot-local path (no sudo needed) - export OPENSSL_PREFIX="$DEPS_ROOT/boringssl" - export OPENSSL_LIB=$OPENSSL_PREFIX/lib - export OPENSSL_INC=$OPENSSL_PREFIX/include - if [ -z "$BORINGSSL_TARBALL" ]; then - local candidates=( - "$DOWNLOAD_CACHE/boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-${DIST_CODENAME}.tar.gz" - "$DOWNLOAD_CACHE/boringssl-${BORINGSSL_BUILD}-${BORINGSSL_ARCH}-focal.tar.gz" - ) - local candidate - for candidate in "${candidates[@]}"; do - if [ -f "$candidate" ]; then - BORINGSSL_TARBALL="$candidate" - break - fi - done - fi - - if [ -z "$BORINGSSL_TARBALL" ] && \ - [ -f "$OPENSSL_INC/openssl/base.h" ] && \ - [ -f "$OPENSSL_LIB/libssl.so" ] && \ - [ -f "$OPENSSL_LIB/libcrypto.so" ] - then - log "Reusing existing BoringSSL in $OPENSSL_PREFIX" - return - fi - - if [ -z "$BORINGSSL_TARBALL" ] || [ ! -f "$BORINGSSL_TARBALL" ]; then - echo "ERROR: BoringSSL package not found for arch=$BORINGSSL_ARCH distro=$DIST_CODENAME." >&2 - echo "Tried download-cache and fallback focal package from $BORINGSSL_RELEASE." >&2 - exit 1 - fi - - rm -fr "$OPENSSL_PREFIX" - mkdir -p "$OPENSSL_PREFIX" - tar -C "$OPENSSL_PREFIX" -xf "$BORINGSSL_TARBALL" --strip-components=1 - fi -} - -# ── Step 8: Build nginx ─────────────────────────────────────────────────────── -build_nginx() { - log "Building nginx $NGINX_VERSION..." - export MODULES_ROOT - export PATH="$ROOT/work/nginx/sbin:$OPENRESTY_UTILS_ROOT:$PATH" - - # Run all three build variants (mirrors CI) - run_logged sh "$ROOT/util/build-without-ssl.sh" "$NGINX_VERSION" - run_logged sh "$ROOT/util/build-with-dd.sh" "$NGINX_VERSION" - clean_ngx_buildroot - run_logged sh "$ROOT/util/build.sh" "$NGINX_VERSION" - - log "nginx version:" - nginx -V - - log "nginx dynamic library links:" - ldd "$(which nginx)" | grep -E 'luajit|ssl|pcre' || true -} - -# ── Step 9: Set up iptables / sysctl (mirrors CI network rules) ────────────── -setup_network() { - log "Configuring network rules..." - sudo iptables -I OUTPUT 1 -p udp --dport 10086 -j REJECT 2>/dev/null || true - sudo iptables -I OUTPUT -p tcp --dst 127.0.0.2 --dport 12345 -j DROP 2>/dev/null || true - sudo iptables -I OUTPUT -p udp --dst 127.0.0.2 --dport 12345 -j DROP 2>/dev/null || true - sudo ip route add prohibit 0.0.0.1/32 2>/dev/null || true - sudo sysctl -w kernel.pid_max=10000 2>/dev/null || true -} - -# ── Step 10: Set up MySQL ───────────────────────────────────────────────────── -setup_mysql() { - log "Setting up MySQL test database..." - mysql -uroot -e " - CREATE DATABASE IF NOT EXISTS ngx_test; - CREATE USER IF NOT EXISTS 'ngx_test'@'%' IDENTIFIED WITH mysql_native_password BY 'ngx_test'; - GRANT ALL ON ngx_test.* TO 'ngx_test'@'%'; - FLUSH PRIVILEGES; - " 2>/dev/null || \ - mysql -uroot --password='' -e " - CREATE DATABASE IF NOT EXISTS ngx_test; - CREATE USER IF NOT EXISTS 'ngx_test'@'%' IDENTIFIED BY 'ngx_test'; - GRANT ALL ON ngx_test.* TO 'ngx_test'@'%'; - FLUSH PRIVILEGES; - " 2>/dev/null || log "WARNING: MySQL setup failed (tests requiring MySQL may fail)" -} - -# ── Step 11: Run tests ──────────────────────────────────────────────────────── -run_tests() { - log "Running tests..." - cd "$ROOT" - - export PATH="$ROOT/work/nginx/sbin:$OPENRESTY_UTILS_ROOT:$PATH" - export LD_PRELOAD="$MOCKEAGAIN_ROOT/mockeagain.so" - export LD_LIBRARY_PATH="$MOCKEAGAIN_ROOT:$LD_LIBRARY_PATH" - export TEST_NGINX_HTTP3_CRT="$ROOT/t/cert/http3/http3.crt" - export TEST_NGINX_HTTP3_KEY="$ROOT/t/cert/http3/http3.key" - - if [ $USE_HTTP3 -eq 1 ]; then - export TEST_NGINX_USE_HTTP3=1 - export TEST_NGINX_QUIC_IDLE_TIMEOUT=3 - fi - - # Clean up stale nc_server from previous aborted runs. - pkill -f "$ROOT/util/nc_server.py" 2>/dev/null || true - - # Start nc_server in background (used by some tests) - python3 "$ROOT/util/nc_server.py" & - NC_PID=$! - trap 'kill "$NC_PID" 2>/dev/null || true' EXIT INT TERM - - # Allow passing specific test files via TEST_NGINX_TARGETS env var - TARGETS="${TEST_NGINX_TARGETS:-t/}" - CORE_PERL_LIB="$(perl -MConfig -e 'print $Config{installprivlib}')" - CORE_PERL_ARCH="$(perl -MConfig -e 'print $Config{archlib}')" - - /usr/bin/env perl "$(command -v prove)" \ - -I"$CORE_PERL_LIB" \ - -I"$CORE_PERL_ARCH" \ - -I. \ - -I"$TEST_NGINX_ROOT/inc" \ - -I"$TEST_NGINX_ROOT/lib" \ - -r $TARGETS - - kill "$NC_PID" 2>/dev/null || true - trap - EXIT INT TERM -} - -# ── Source code style check (mirrors CI before_install) ─────────────────────── -check_style() { - log "Checking source code style..." - cd "$ROOT" - if grep -n -P '(?<=.{80}).+' $(find src -name '*.c') $(find . -name '*.h') 2>/dev/null; then - echo "ERROR: Found C source lines exceeding 80 columns." >&2 - exit 1 - fi - if grep -n -P '\t+' $(find src -name '*.c') $(find . -name '*.h') 2>/dev/null; then - echo "ERROR: Cannot use tabs." >&2 - exit 1 - fi - log "Style check passed." -} - -# ── Main ─────────────────────────────────────────────────────────────────────── -cd "$ROOT" - -if [ $SKIP_BUILD -eq 0 ]; then - # check_style - - if [ $SKIP_DEPS -eq 0 ]; then - # install_sys_deps - install_deps - fi - - build_luajit - build_drizzle - build_mockeagain - build_lua_cjson - setup_boringssl - # setup_network - # setup_mysql - build_nginx -fi - -run_tests From 28aa24c13a9e2a70ed81cb15deba7ab65e259724 Mon Sep 17 00:00:00 2001 From: Jun Ouyang Date: Fri, 13 Mar 2026 20:36:04 +0800 Subject: [PATCH 3/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ngx_http_lua_pipe.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ngx_http_lua_pipe.c b/src/ngx_http_lua_pipe.c index 0d2ad08e09..27e1c3b233 100644 --- a/src/ngx_http_lua_pipe.c +++ b/src/ngx_http_lua_pipe.c @@ -1202,17 +1202,17 @@ ngx_http_lua_ffi_pipe_proc_destroy(ngx_http_lua_ffi_pipe_proc_t *proc) ngx_http_lua_pipe_proc_finalize(proc); /* - * pipe_proc_finalize → ngx_http_lua_pipe_close_helper may leave pipe + * pipe_proc_finalize -> ngx_http_lua_pipe_close_helper may leave pipe * connections open with active timers/posted events when there are * pending I/O operations (handler != dummy_handler). Close them now * before destroying the pool. * * Without this, when pool cleanup LIFO ordering causes pipe_proc_destroy * to run before request_cleanup_handler (e.g. QUIC connection close path: - * ngx_quic_close_streams → ngx_http_free_request → ngx_destroy_pool), + * ngx_quic_close_streams -> ngx_http_free_request -> ngx_destroy_pool), * request_cleanup sees proc->pipe == NULL and returns early, leaving the * timer live. The timer then fires after the request pool is freed, - * accessing a dangling wait_co_ctx pointer → SIGSEGV. + * accessing a dangling wait_co_ctx pointer -> SIGSEGV. * * ngx_close_connection handles everything: timers (ngx_del_timer), * posted events (ngx_delete_posted_event), epoll removal, fd close, From 8265ae253184ed3c5121c0b3039f71ada74cbc94 Mon Sep 17 00:00:00 2001 From: Jun Ouyang Date: Fri, 13 Mar 2026 21:48:16 +0800 Subject: [PATCH 4/5] fix --- t/191-pipe-proc-quic-close-crash.t | 6 ------ 1 file changed, 6 deletions(-) diff --git a/t/191-pipe-proc-quic-close-crash.t b/t/191-pipe-proc-quic-close-crash.t index 9bd148302e..49cfd30105 100644 --- a/t/191-pipe-proc-quic-close-crash.t +++ b/t/191-pipe-proc-quic-close-crash.t @@ -54,12 +54,6 @@ BEGIN { if ($v !~ /--with-http_v3_module/) { $SkipReason = "requires nginx built with --with-http_v3_module"; - - } elsif (!-f $ENV{TEST_NGINX_HTTP3_CRT} || !-f $ENV{TEST_NGINX_HTTP3_KEY}) { - $SkipReason = - "requires TEST_NGINX_HTTP3_CRT / TEST_NGINX_HTTP3_KEY cert files " - . "(run util/build-and-test.sh --http3 once to generate them)"; - } else { # Enable global HTTP3 mode so Test::Nginx::Util injects ssl_certificate # into the server block and makes curl use --http3-only for all tests. From 05860c8f48cec3c2dcfd9024225d36d9a844830d Mon Sep 17 00:00:00 2001 From: Jun Ouyang Date: Fri, 13 Mar 2026 21:51:32 +0800 Subject: [PATCH 5/5] fix --- t/191-pipe-proc-quic-close-crash.t | 35 ++++++++---------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/t/191-pipe-proc-quic-close-crash.t b/t/191-pipe-proc-quic-close-crash.t index 49cfd30105..cb2f0da7f4 100644 --- a/t/191-pipe-proc-quic-close-crash.t +++ b/t/191-pipe-proc-quic-close-crash.t @@ -29,35 +29,18 @@ # pipe stdout read timeout : 1 s (timer armed 1 s after request lands) # curl --max-time : 0.5 s (QUIC connection closed while timer live) # --- wait : 2 s (covers remaining ~0.5 s + safety margin) -# -# NOTE on Test::Nginx::Util internals: -# ssl_certificate is injected into the server block ONLY when -# ($UseHttp3 && !defined $block->http3), i.e. only in global HTTP3 mode. -# Using a per-block "--- http3" directive without TEST_NGINX_USE_HTTP3=1 -# skips the ssl_certificate injection and nginx refuses to start. -# Therefore this test enables global HTTP3 mode in a BEGIN block so that -# the module reads the env vars at load time. - -BEGIN { - require File::Basename; - require Cwd; - - # Default cert paths (generated by `util/build-and-test.sh --http3`). - my $t_dir = File::Basename::dirname(Cwd::abs_path(__FILE__)); - $ENV{TEST_NGINX_HTTP3_CRT} = "$t_dir/cert/http3/http3.crt" - unless defined $ENV{TEST_NGINX_HTTP3_CRT}; - $ENV{TEST_NGINX_HTTP3_KEY} = "$t_dir/cert/http3/http3.key" - unless defined $ENV{TEST_NGINX_HTTP3_KEY}; - my $nginx = $ENV{TEST_NGINX_BINARY} || 'nginx'; - my $v = eval { `$nginx -V 2>&1` } // ''; - if ($v !~ /--with-http_v3_module/) { - $SkipReason = "requires nginx built with --with-http_v3_module"; +BEGIN { + if (!$ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "TEST_NGINX_USE_HTTP3 is not set"; } else { - # Enable global HTTP3 mode so Test::Nginx::Util injects ssl_certificate - # into the server block and makes curl use --http3-only for all tests. - $ENV{TEST_NGINX_USE_HTTP3} = 1 unless defined $ENV{TEST_NGINX_USE_HTTP3}; + my $nginx = $ENV{TEST_NGINX_BINARY} || 'nginx'; + my $v = eval { `$nginx -V 2>&1` } // ''; + + if ($v !~ /--with-http_v3_module/) { + $SkipReason = "requires nginx built with --with-http_v3_module"; + } } }