Summary
All NDK set_var-based directives (e.g. set_md5, set_sha1, set_encode_base64 from openresty/set-misc-nginx-module) crash the nginx worker with SIGSEGV on nginx 1.30.4 (stable) and 1.31.3 (mainline). The same configuration works fine on nginx 1.30.3 and earlier.
Root cause
The fix for CVE-2026-42533 (shipped in nginx 1.30.4 / 1.31.3) changed the script engine contract: the complex-value stack push was moved out of ngx_http_script_complex_value_code into a new instruction ngx_http_script_complex_value_end_code, which is emitted only by the stock rewrite module (ngx_http_rewrite_value in src/http/modules/ngx_http_rewrite_module.c).
NDK's ndk_http_rewrite_value (src/ndk_rewrite.c) is a stale copy of the pre-1.30.4 rewrite module code (the file header even says it was "taken from the rewrite module and http_script file because those functions are defined as being static"). It emits ngx_http_script_complex_value_code + ngx_http_script_compile() but never emits the new ngx_http_script_complex_value_end_code.
As a result, the evaluated value is never pushed onto e->sp. The consumer ndk_set_var_value_code (src/ndk_set_var.c) then reads e->sp - 1, which contains garbage, and passes it to the set-misc handler. ngx_http_set_misc_set_md5 hashes a bogus pointer/length and the worker segfaults.
Backtrace (gdb, nginx 1.30.4 built from source + NDK v0.3.3 + set-misc v0.34)
#0 ngx_md5_body (ctx=0x7fff3faaad30, data=0x79d23813d2b0 "HTTP", size=268435392) at src/core/ngx_md5.c:198
#1 ngx_md5_update (ctx=0x7fff3faaad30, data=0x79d23813d2b0, size=268435451) at src/core/ngx_md5.c:53
#2 ngx_http_set_misc_set_md5 (r=..., res=..., v=0x79d23813e030) at set-misc/src/ngx_http_set_hash.c:74
#3 ndk_set_var_value_code (e=0x79d23813dfe0) at ndk/src/ndk_set_var.c:144
#4 ngx_http_rewrite_handler (r=0x79d23813d2b0) at src/http/modules/ngx_http_rewrite_module.c:180
#5 ngx_http_core_rewrite_phase ...
Note data == the request pointer and size = 268435451 — garbage read from the stale stack slot.
Affected versions
| nginx |
NDK |
set-misc |
result |
| 1.30.3 (source build) |
simpl v0.3.3 |
v0.34 |
works |
| 1.30.4 (source build) |
simpl v0.3.3 |
v0.34 |
SIGSEGV |
| 1.30.4 (source build) |
openresty master (57270ba... per set-misc CI) |
v0.34 |
SIGSEGV |
All set_var directives are affected, not only set_md5: verified crashes with set_sha1, set_encode_base64, set_quote_sql_str. Module load order does not matter. This is reproducible with pure upstream sources — it is not a distro packaging issue (verified against Alpine's nginx-mod-http-set-misc=1.30.4-r0 as well).
Minimal reproduction
Dockerfile (Alpine 3.24, pure source build):
FROM alpine:3.24.1
RUN apk add --no-cache build-base pcre-dev zlib-dev openssl-dev curl git
WORKDIR /build
RUN curl -fSL https://nginx.org/download/nginx-1.30.4.tar.gz | tar xz
RUN git clone --depth 1 --branch v0.3.3 https://github.com/simpl/ngx_devel_kit ndk
RUN git clone --depth 1 --branch v0.34 https://github.com/openresty/set-misc-nginx-module set-misc
WORKDIR /build/nginx-1.30.4
RUN ./configure --prefix=/opt/nginx --with-compat \
--add-dynamic-module=/build/ndk \
--add-dynamic-module=/build/set-misc \
&& make -j$(nproc) && make install
nginx.conf:
load_module /opt/nginx/modules/ndk_http_module.so;
load_module /opt/nginx/modules/ngx_http_set_misc_module.so;
worker_processes 1;
events { worker_connections 128; }
http {
server {
listen 8080;
location /t { set $x "hello"; set_md5 $y $x; return 200 "$y\n"; }
}
}
$ curl http://127.0.0.1:8080/t
curl: (52) Empty reply from server
# error log: [alert] worker process N exited on signal 11 (core dumped)
Changing only nginx-1.30.4.tar.gz to nginx-1.30.3.tar.gz makes the same build return 5d41402abc4b2a76b9719d911017c592.
Proposed fix
Mirror the upstream change in ndk_http_rewrite_value (src/ndk_rewrite.c): after ngx_http_script_compile(), append ngx_http_script_complex_value_end_code when building against nginx versions that have it (1.30.4+ stable, 1.31.3+ mainline):
#include <ngx_http_script.h>
/* after ngx_http_script_compile(&sc) succeeds */
#if defined(nginx_version) && \
((nginx_version >= 1030004 && nginx_version < 1031000) || nginx_version >= 1031003)
{
ngx_http_script_complex_value_end_code_t *complex_end;
complex_end = ngx_http_script_add_code(lcf->codes,
sizeof(ngx_http_script_complex_value_end_code_t),
&complex);
if (complex_end == NULL) {
return NGX_CONF_ERROR;
}
complex_end->code = ngx_http_script_complex_value_end_code;
}
#endif
This matches what nginx itself does in ngx_http_rewrite_value (src/http/modules/ngx_http_rewrite_module.c, ~line 1021 in 1.30.4).
It may also be worth flagging to the nginx team that the 1.30.4/1.31.3 change silently broke the de-facto script-engine contract for every third-party module that builds its own instruction stream via ngx_http_script_compile() (NDK is the most widespread one — this affects at least all set-misc set_* users).
References
Summary
All NDK
set_var-based directives (e.g.set_md5,set_sha1,set_encode_base64from openresty/set-misc-nginx-module) crash the nginx worker with SIGSEGV on nginx 1.30.4 (stable) and 1.31.3 (mainline). The same configuration works fine on nginx 1.30.3 and earlier.Root cause
The fix for CVE-2026-42533 (shipped in nginx 1.30.4 / 1.31.3) changed the script engine contract: the complex-value stack push was moved out of
ngx_http_script_complex_value_codeinto a new instructionngx_http_script_complex_value_end_code, which is emitted only by the stock rewrite module (ngx_http_rewrite_valueinsrc/http/modules/ngx_http_rewrite_module.c).NDK's
ndk_http_rewrite_value(src/ndk_rewrite.c) is a stale copy of the pre-1.30.4 rewrite module code (the file header even says it was "taken from the rewrite module and http_script file because those functions are defined as being static"). It emitsngx_http_script_complex_value_code+ngx_http_script_compile()but never emits the newngx_http_script_complex_value_end_code.As a result, the evaluated value is never pushed onto
e->sp. The consumerndk_set_var_value_code(src/ndk_set_var.c) then readse->sp - 1, which contains garbage, and passes it to the set-misc handler.ngx_http_set_misc_set_md5hashes a bogus pointer/length and the worker segfaults.Backtrace (gdb, nginx 1.30.4 built from source + NDK v0.3.3 + set-misc v0.34)
Note
data== the request pointer andsize= 268435451 — garbage read from the stale stack slot.Affected versions
All
set_vardirectives are affected, not onlyset_md5: verified crashes withset_sha1,set_encode_base64,set_quote_sql_str. Module load order does not matter. This is reproducible with pure upstream sources — it is not a distro packaging issue (verified against Alpine'snginx-mod-http-set-misc=1.30.4-r0as well).Minimal reproduction
Dockerfile (Alpine 3.24, pure source build):
nginx.conf:Changing only
nginx-1.30.4.tar.gztonginx-1.30.3.tar.gzmakes the same build return5d41402abc4b2a76b9719d911017c592.Proposed fix
Mirror the upstream change in
ndk_http_rewrite_value(src/ndk_rewrite.c): afterngx_http_script_compile(), appendngx_http_script_complex_value_end_codewhen building against nginx versions that have it (1.30.4+ stable, 1.31.3+ mainline):This matches what nginx itself does in
ngx_http_rewrite_value(src/http/modules/ngx_http_rewrite_module.c, ~line 1021 in 1.30.4).It may also be worth flagging to the nginx team that the 1.30.4/1.31.3 change silently broke the de-facto script-engine contract for every third-party module that builds its own instruction stream via
ngx_http_script_compile()(NDK is the most widespread one — this affects at least all set-miscset_*users).References
nginx-mod-http-set-misc=1.30.4-r0segfaults under any config usingset_md5