Patch-Source: https://github.com/nginx/nginx/compare/b81dcfb0b0437190cba5413751c01d7d23d91bfa..97e40e59b7b4ad6aa36f54ce4f1e64d1e98f24cb -- From 15a0ac548f5d042e98e3390a0018fec95e6d98c4 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Fri, 19 Jun 2026 22:54:19 +0300 Subject: [PATCH 1/5] Script: simplified copy capture codes. Signed-off-by: Roman Arutyunyan Origin: --- src/http/ngx_http_script.c | 22 ++++++++++++---------- src/stream/ngx_stream_script.c | 10 +++++++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/http/ngx_http_script.c b/src/http/ngx_http_script.c index 4f71ea75f..3f41a4254 100644 --- a/src/http/ngx_http_script.c +++ b/src/http/ngx_http_script.c @@ -1342,6 +1342,7 @@ ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e) { int *cap; u_char *p; + size_t len; ngx_uint_t n; ngx_http_request_t *r; ngx_http_script_copy_capture_code_t *code; @@ -1357,17 +1358,17 @@ ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e) if (n < r->ncaptures) { cap = r->captures; + len = cap[n + 1] - cap[n]; if ((e->is_args || e->quote) && (e->request->quoted_uri || e->request->plus_in_uri)) { - p = r->captures_data; + p = r->captures_data + cap[n]; + + return len + 2 * ngx_escape_uri(NULL, p, len, NGX_ESCAPE_ARGS); - return cap[n + 1] - cap[n] - + 2 * ngx_escape_uri(NULL, &p[cap[n]], cap[n + 1] - cap[n], - NGX_ESCAPE_ARGS); } else { - return cap[n + 1] - cap[n]; + return len; } } @@ -1380,6 +1381,7 @@ ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e) { int *cap; u_char *p, *pos; + size_t len; ngx_uint_t n; ngx_http_request_t *r; ngx_http_script_copy_capture_code_t *code; @@ -1397,16 +1399,16 @@ ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e) if (n < r->ncaptures) { cap = r->captures; - p = r->captures_data; + len = cap[n + 1] - cap[n]; + p = r->captures_data + cap[n]; if ((e->is_args || e->quote) && (e->request->quoted_uri || e->request->plus_in_uri)) { - e->pos = (u_char *) ngx_escape_uri(pos, &p[cap[n]], - cap[n + 1] - cap[n], - NGX_ESCAPE_ARGS); + e->pos = (u_char *) ngx_escape_uri(pos, p, len, NGX_ESCAPE_ARGS); + } else { - e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]); + e->pos = ngx_copy(pos, p, len); } } diff --git a/src/stream/ngx_stream_script.c b/src/stream/ngx_stream_script.c index c447e152f..2cd8b08c7 100644 --- a/src/stream/ngx_stream_script.c +++ b/src/stream/ngx_stream_script.c @@ -894,6 +894,7 @@ size_t ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e) { int *cap; + size_t len; ngx_uint_t n; ngx_stream_session_t *s; ngx_stream_script_copy_capture_code_t *code; @@ -908,7 +909,8 @@ ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e) if (n < s->ncaptures) { cap = s->captures; - return cap[n + 1] - cap[n]; + len = cap[n + 1] - cap[n]; + return len; } return 0; @@ -920,6 +922,7 @@ ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e) { int *cap; u_char *p, *pos; + size_t len; ngx_uint_t n; ngx_stream_session_t *s; ngx_stream_script_copy_capture_code_t *code; @@ -936,8 +939,9 @@ ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e) if (n < s->ncaptures) { cap = s->captures; - p = s->captures_data; - e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]); + len = cap[n + 1] - cap[n]; + p = s->captures_data + cap[n]; + e->pos = ngx_copy(pos, p, len); } ngx_log_debug2(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0, -- 2.55.0 From 5db55f1fd87d972c41cd6178343846e3103c7c97 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Fri, 19 Jun 2026 22:54:27 +0300 Subject: [PATCH 2/5] Script: buffer overrun protection. With this change, all script copy operations now check if there is enough room in the buffer. To do so, the script engine now provides the e->end pointer, which specifies expected buffer end, and each copy operation is checked against it with the ngx_http_script_check_length() function. The e->end pointer is optional and only checked when set, thus introducing no incompatible API changes. All standard functions were updated to use it, notably ngx_http_complex_value(), ngx_http_script_run(), ngx_http_script_regex_start_code(), ngx_http_script_complex_value_code(). Direct script evaluation in the proxy, fastcgi, scgi, uwsgi, grpc proxy, index, and try_files modules will be updated by a separate patch. In particular, this catches issues as observed when evaluating variables with side effects, such as in the following configuration: map $uri $map { ~(?.*) $capture; } set $capture ""; set $temp "$capture $map"; As well as when evaluating non-cacheable variables, where length of a variable might change between length and copy codes, such as in the following configuration: map prefix:$capture $map_volatile { volatile; ~(?.*) $capture; } set $capture ""; set $temp "$map_volatile"; Similar changes were made in the stream module. Signed-off-by: Roman Arutyunyan Origin: --- src/http/ngx_http_script.c | 63 ++++++++++++++++++++++++++++++++++ src/http/ngx_http_script.h | 4 +++ src/stream/ngx_stream_script.c | 45 ++++++++++++++++++++++++ src/stream/ngx_stream_script.h | 5 +++ 4 files changed, 117 insertions(+) diff --git a/src/http/ngx_http_script.c b/src/http/ngx_http_script.c index 3f41a4254..c0cd44a6b 100644 --- a/src/http/ngx_http_script.c +++ b/src/http/ngx_http_script.c @@ -92,12 +92,17 @@ ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val, e.ip = val->values; e.pos = value->data; e.buf = *value; + e.end = value->data + len; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } + if (e.status) { + return NGX_ERROR; + } + *value = e.buf; return NGX_OK; @@ -650,12 +655,17 @@ ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value, e.ip = code_values; e.pos = value->data; + e.end = value->data + len; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } + if (e.status) { + return NULL; + } + return e.pos; } @@ -805,6 +815,25 @@ ngx_http_script_add_code(ngx_array_t *codes, size_t size, void *code) } +ngx_int_t +ngx_http_script_check_length(ngx_http_script_engine_t *e, size_t len) +{ + if (e->end == NULL) { + return NGX_OK; + } + + if (e->end - e->pos < (ssize_t) len) { + ngx_log_error(NGX_LOG_ALERT, e->request->connection->log, 0, + "no buffer space in script copy"); + e->ip = ngx_http_script_exit; + e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; + return NGX_ERROR; + } + + return NGX_OK; +} + + static ngx_int_t ngx_http_script_add_copy_code(ngx_http_script_compile_t *sc, ngx_str_t *value, ngx_uint_t last) @@ -873,6 +902,11 @@ ngx_http_script_copy_code(ngx_http_script_engine_t *e) p = e->pos; if (!e->skip) { + + if (ngx_http_script_check_length(e, code->len) != NGX_OK) { + return; + } + e->pos = ngx_copy(p, e->ip + sizeof(ngx_http_script_copy_code_t), code->len); } @@ -976,6 +1010,11 @@ ngx_http_script_copy_var_code(ngx_http_script_engine_t *e) } if (value && !value->not_found) { + + if (ngx_http_script_check_length(e, value->len) != NGX_OK) { + return; + } + p = e->pos; e->pos = ngx_copy(p, value->data, value->len); @@ -1192,6 +1231,7 @@ ngx_http_script_regex_start_code(ngx_http_script_engine_t *e) e->quote = code->redirect; e->pos = e->buf.data; + e->end = e->buf.data + e->buf.len; e->ip += sizeof(ngx_http_script_regex_code_t); } @@ -1228,6 +1268,11 @@ ngx_http_script_regex_end_code(ngx_http_script_engine_t *e) e->pos = dst; if (code->add_args && r->args.len) { + + if (ngx_http_script_check_length(e, r->args.len + 1) != NGX_OK) { + return; + } + *e->pos++ = (u_char) (code->args ? '&' : '?'); e->pos = ngx_copy(e->pos, r->args.data, r->args.len); } @@ -1261,6 +1306,11 @@ ngx_http_script_regex_end_code(ngx_http_script_engine_t *e) e->buf.len = e->args - e->buf.data; if (code->add_args && r->args.len) { + + if (ngx_http_script_check_length(e, r->args.len + 1) != NGX_OK) { + return; + } + *e->pos++ = '&'; e->pos = ngx_copy(e->pos, r->args.data, r->args.len); } @@ -1382,6 +1432,7 @@ ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e) int *cap; u_char *p, *pos; size_t len; + uintptr_t escape; ngx_uint_t n; ngx_http_request_t *r; ngx_http_script_copy_capture_code_t *code; @@ -1405,9 +1456,20 @@ ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e) if ((e->is_args || e->quote) && (e->request->quoted_uri || e->request->plus_in_uri)) { + escape = 2 * ngx_escape_uri(NULL, p, len, NGX_ESCAPE_ARGS); + + if (ngx_http_script_check_length(e, len + escape) != NGX_OK) { + return; + } + e->pos = (u_char *) ngx_escape_uri(pos, p, len, NGX_ESCAPE_ARGS); } else { + + if (ngx_http_script_check_length(e, len) != NGX_OK) { + return; + } + e->pos = ngx_copy(pos, p, len); } } @@ -1791,6 +1853,7 @@ ngx_http_script_complex_value_code(ngx_http_script_engine_t *e) } e->pos = e->buf.data; + e->end = e->buf.data + len; e->sp->len = e->buf.len; e->sp->data = e->buf.data; diff --git a/src/http/ngx_http_script.h b/src/http/ngx_http_script.h index 43600383c..b086b266f 100644 --- a/src/http/ngx_http_script.h +++ b/src/http/ngx_http_script.h @@ -17,6 +17,7 @@ typedef struct { u_char *ip; u_char *pos; + u_char *end; ngx_http_variable_value_t *sp; ngx_str_t buf; @@ -240,6 +241,9 @@ void *ngx_http_script_start_code(ngx_pool_t *pool, ngx_array_t **codes, size_t size); void *ngx_http_script_add_code(ngx_array_t *codes, size_t size, void *code); +ngx_int_t ngx_http_script_check_length(ngx_http_script_engine_t *e, + size_t len); + size_t ngx_http_script_copy_len_code(ngx_http_script_engine_t *e); void ngx_http_script_copy_code(ngx_http_script_engine_t *e); size_t ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e); diff --git a/src/stream/ngx_stream_script.c b/src/stream/ngx_stream_script.c index 2cd8b08c7..2e31102df 100644 --- a/src/stream/ngx_stream_script.c +++ b/src/stream/ngx_stream_script.c @@ -92,6 +92,7 @@ ngx_stream_complex_value(ngx_stream_session_t *s, e.ip = val->values; e.pos = value->data; + e.end = value->data + len; e.buf = *value; while (*(uintptr_t *) e.ip) { @@ -99,6 +100,10 @@ ngx_stream_complex_value(ngx_stream_session_t *s, code((ngx_stream_script_engine_t *) &e); } + if (e.status) { + return NGX_ERROR; + } + *value = e.buf; return NGX_OK; @@ -526,12 +531,17 @@ ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value, e.ip = code_values; e.pos = value->data; + e.end = value->data + len; while (*(uintptr_t *) e.ip) { code = *(ngx_stream_script_code_pt *) e.ip; code((ngx_stream_script_engine_t *) &e); } + if (e.status) { + return NULL; + } + return e.pos; } @@ -668,6 +678,25 @@ ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code) } +ngx_int_t +ngx_stream_script_check_length(ngx_stream_script_engine_t *e, size_t len) +{ + if (e->end == NULL) { + return NGX_OK; + } + + if (e->end - e->pos < (ssize_t) len) { + ngx_log_error(NGX_LOG_ALERT, e->session->connection->log, 0, + "no buffer space in script copy"); + e->ip = ngx_stream_script_exit; + e->status = NGX_STREAM_INTERNAL_SERVER_ERROR; + return NGX_ERROR; + } + + return NGX_OK; +} + + static ngx_int_t ngx_stream_script_add_copy_code(ngx_stream_script_compile_t *sc, ngx_str_t *value, ngx_uint_t last) @@ -737,6 +766,11 @@ ngx_stream_script_copy_code(ngx_stream_script_engine_t *e) p = e->pos; if (!e->skip) { + + if (ngx_stream_script_check_length(e, code->len) != NGX_OK) { + return; + } + e->pos = ngx_copy(p, e->ip + sizeof(ngx_stream_script_copy_code_t), code->len); } @@ -841,6 +875,11 @@ ngx_stream_script_copy_var_code(ngx_stream_script_engine_t *e) } if (value && !value->not_found) { + + if (ngx_stream_script_check_length(e, value->len) != NGX_OK) { + return; + } + p = e->pos; e->pos = ngx_copy(p, value->data, value->len); @@ -941,6 +980,11 @@ ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e) cap = s->captures; len = cap[n + 1] - cap[n]; p = s->captures_data + cap[n]; + + if (ngx_stream_script_check_length(e, len) != NGX_OK) { + return; + } + e->pos = ngx_copy(pos, p, len); } @@ -1013,6 +1057,7 @@ ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e) != NGX_OK) { e->ip = ngx_stream_script_exit; + e->status = NGX_STREAM_INTERNAL_SERVER_ERROR; return; } diff --git a/src/stream/ngx_stream_script.h b/src/stream/ngx_stream_script.h index d8f374047..dc6de9c72 100644 --- a/src/stream/ngx_stream_script.h +++ b/src/stream/ngx_stream_script.h @@ -17,6 +17,7 @@ typedef struct { u_char *ip; u_char *pos; + u_char *end; ngx_stream_variable_value_t *sp; ngx_str_t buf; @@ -25,6 +26,7 @@ typedef struct { unsigned flushed:1; unsigned skip:1; + ngx_int_t status; ngx_stream_session_t *session; } ngx_stream_script_engine_t; @@ -127,6 +129,9 @@ void ngx_stream_script_flush_no_cacheable_variables(ngx_stream_session_t *s, void *ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code); +ngx_int_t ngx_stream_script_check_length(ngx_stream_script_engine_t *e, + size_t len); + size_t ngx_stream_script_copy_len_code(ngx_stream_script_engine_t *e); void ngx_stream_script_copy_code(ngx_stream_script_engine_t *e); size_t ngx_stream_script_copy_var_len_code(ngx_stream_script_engine_t *e); -- 2.55.0 From 7a322df68c8ca35b6976a76190169c340bba7053 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Fri, 19 Jun 2026 22:55:38 +0300 Subject: [PATCH 3/5] Access log: buffer overrun protection. Similarly to generic script operations, access log script copy operations now check if there is enough room in the buffer. Signed-off-by: Roman Arutyunyan Origin: --- src/http/modules/ngx_http_log_module.c | 185 ++++++++++++++++++++----- src/stream/ngx_stream_log_module.c | 101 +++++++++++--- 2 files changed, 235 insertions(+), 51 deletions(-) diff --git a/src/http/modules/ngx_http_log_module.c b/src/http/modules/ngx_http_log_module.c index f7c4bd2f5..d282362ea 100644 --- a/src/http/modules/ngx_http_log_module.c +++ b/src/http/modules/ngx_http_log_module.c @@ -17,7 +17,7 @@ typedef struct ngx_http_log_op_s ngx_http_log_op_t; typedef u_char *(*ngx_http_log_op_run_pt) (ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); typedef size_t (*ngx_http_log_op_getlen_pt) (ngx_http_request_t *r, uintptr_t data); @@ -112,39 +112,42 @@ static void ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log); static void ngx_http_log_flush_handler(ngx_event_t *ev); static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static u_char *ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static u_char *ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static u_char *ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); -static u_char *ngx_http_log_body_bytes_sent(ngx_http_request_t *r, - u_char *buf, ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); +static u_char *ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf, + u_char *end, ngx_http_log_op_t *op); static u_char *ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op, ngx_str_t *value, ngx_uint_t escape); static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static uintptr_t ngx_http_log_escape(u_char *dst, u_char *src, size_t size); static size_t ngx_http_log_json_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op); + u_char *end, ngx_http_log_op_t *op); static size_t ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_unescaped_variable(ngx_http_request_t *r, - u_char *buf, ngx_http_log_op_t *op); + u_char *buf, u_char *end, ngx_http_log_op_t *op); + +static ngx_int_t ngx_http_log_check_length(ngx_http_request_t *r, + u_char *buf, u_char *end, size_t len); static void *ngx_http_log_create_main_conf(ngx_conf_t *cf); @@ -253,7 +256,7 @@ static ngx_http_log_var_t ngx_http_log_vars[] = { static ngx_int_t ngx_http_log_handler(ngx_http_request_t *r) { - u_char *line, *p; + u_char *line, *p, *end; size_t len, size; ssize_t n; ngx_str_t val; @@ -309,6 +312,8 @@ ngx_http_log_handler(ngx_http_request_t *r) } } + len += NGX_LINEFEED_SIZE; + if (log[l].syslog_peer) { /* length of syslog's PRI and HEADER message parts */ @@ -319,8 +324,6 @@ ngx_http_log_handler(ngx_http_request_t *r) goto alloc_line; } - len += NGX_LINEFEED_SIZE; - buffer = log[l].file ? log[l].file->data : NULL; if (buffer) { @@ -336,13 +339,18 @@ ngx_http_log_handler(ngx_http_request_t *r) if (len <= (size_t) (buffer->last - buffer->pos)) { p = buffer->pos; + end = p + len - NGX_LINEFEED_SIZE; if (buffer->event && p == buffer->start) { ngx_add_timer(buffer->event, buffer->flush); } - for (i = 0; i < log[l].format->ops->nelts; i++) { - p = op[i].run(r, p, &op[i]); + for (i = 0; i < log[l].format->ops->nelts && p; i++) { + p = op[i].run(r, p, end, &op[i]); + } + + if (p == NULL) { + return NGX_ERROR; } ngx_linefeed(p); @@ -365,13 +373,18 @@ ngx_http_log_handler(ngx_http_request_t *r) } p = line; + end = line + len - NGX_LINEFEED_SIZE; if (log[l].syslog_peer) { p = ngx_syslog_add_header(log[l].syslog_peer, line); } - for (i = 0; i < log[l].format->ops->nelts; i++) { - p = op[i].run(r, p, &op[i]); + for (i = 0; i < log[l].format->ops->nelts && p; i++) { + p = op[i].run(r, p, end, &op[i]); + } + + if (p == NULL) { + return NGX_ERROR; } if (log[l].syslog_peer) { @@ -770,7 +783,7 @@ ngx_http_log_flush_handler(ngx_event_t *ev) static u_char * -ngx_http_log_copy_short(ngx_http_request_t *r, u_char *buf, +ngx_http_log_copy_short(ngx_http_request_t *r, u_char *buf, u_char *end, ngx_http_log_op_t *op) { size_t len; @@ -779,6 +792,10 @@ ngx_http_log_copy_short(ngx_http_request_t *r, u_char *buf, len = op->len; data = op->data; + if (ngx_http_log_check_length(r, buf, end, len) != NGX_OK) { + return NULL; + } + while (len--) { *buf++ = (u_char) (data & 0xff); data >>= 8; @@ -789,16 +806,25 @@ ngx_http_log_copy_short(ngx_http_request_t *r, u_char *buf, static u_char * -ngx_http_log_copy_long(ngx_http_request_t *r, u_char *buf, +ngx_http_log_copy_long(ngx_http_request_t *r, u_char *buf, u_char *end, ngx_http_log_op_t *op) { + if (ngx_http_log_check_length(r, buf, end, op->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, (u_char *) op->data, op->len); } static u_char * -ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) +ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, u_char *end, + ngx_http_log_op_t *op) { + if (ngx_http_log_check_length(r, buf, end, 1) != NGX_OK) { + return NULL; + } + if (r->pipeline) { *buf = 'p'; } else { @@ -810,24 +836,43 @@ ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) static u_char * -ngx_http_log_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) +ngx_http_log_time(ngx_http_request_t *r, u_char *buf, u_char *end, + ngx_http_log_op_t *op) { + if (ngx_http_log_check_length(r, buf, end, ngx_cached_http_log_time.len) + != NGX_OK) + { + return NULL; + } + return ngx_cpymem(buf, ngx_cached_http_log_time.data, ngx_cached_http_log_time.len); } static u_char * -ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) +ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, u_char *end, + ngx_http_log_op_t *op) { + if (ngx_http_log_check_length(r, buf, end, ngx_cached_http_log_iso8601.len) + != NGX_OK) + { + return NULL; + } + return ngx_cpymem(buf, ngx_cached_http_log_iso8601.data, ngx_cached_http_log_iso8601.len); } static u_char * -ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) +ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, u_char *end, + ngx_http_log_op_t *op) { ngx_time_t *tp; + if (ngx_http_log_check_length(r, buf, end, NGX_TIME_T_LEN + 4) != NGX_OK) { + return NULL; + } + tp = ngx_timeofday(); return ngx_sprintf(buf, "%T.%03M", tp->sec, tp->msec); @@ -835,12 +880,16 @@ ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) static u_char * -ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, +ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, u_char *end, ngx_http_log_op_t *op) { ngx_time_t *tp; ngx_msec_int_t ms; + if (ngx_http_log_check_length(r, buf, end, NGX_TIME_T_LEN + 4) != NGX_OK) { + return NULL; + } + tp = ngx_timeofday(); ms = (ngx_msec_int_t) @@ -852,10 +901,15 @@ ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, static u_char * -ngx_http_log_status(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) +ngx_http_log_status(ngx_http_request_t *r, u_char *buf, u_char *end, + ngx_http_log_op_t *op) { ngx_uint_t status; + if (ngx_http_log_check_length(r, buf, end, NGX_INT_T_LEN) != NGX_OK) { + return NULL; + } + if (r->err_status) { status = r->err_status; @@ -874,9 +928,13 @@ ngx_http_log_status(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) static u_char * -ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf, +ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf, u_char *end, ngx_http_log_op_t *op) { + if (ngx_http_log_check_length(r, buf, end, NGX_OFF_T_LEN) != NGX_OK) { + return NULL; + } + return ngx_sprintf(buf, "%O", r->connection->sent); } @@ -887,11 +945,15 @@ ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf, */ static u_char * -ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf, +ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf, u_char *end, ngx_http_log_op_t *op) { off_t length; + if (ngx_http_log_check_length(r, buf, end, NGX_OFF_T_LEN) != NGX_OK) { + return NULL; + } + length = r->connection->sent - r->header_size; if (length > 0) { @@ -905,9 +967,13 @@ ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf, static u_char * -ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf, +ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf, u_char *end, ngx_http_log_op_t *op) { + if (ngx_http_log_check_length(r, buf, end, NGX_OFF_T_LEN) != NGX_OK) { + return NULL; + } + return ngx_sprintf(buf, "%O", r->request_length); } @@ -968,21 +1034,39 @@ ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data) static u_char * -ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) +ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, u_char *end, + ngx_http_log_op_t *op) { + uintptr_t len; ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, op->data); if (value == NULL || value->not_found) { + if (ngx_http_log_check_length(r, buf, end, 1) != NGX_OK) { + return NULL; + } + *buf = '-'; return buf + 1; } if (value->escape == 0) { + if (ngx_http_log_check_length(r, buf, end, value->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, value->data, value->len); } else { + len = ngx_http_log_escape(NULL, value->data, value->len); + + if (ngx_http_log_check_length(r, buf, end, value->len + len * 3) + != NGX_OK) + { + return NULL; + } + return (u_char *) ngx_http_log_escape(buf, value->data, value->len); } } @@ -1069,9 +1153,10 @@ ngx_http_log_json_variable_getlen(ngx_http_request_t *r, uintptr_t data) static u_char * -ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf, +ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf, u_char *end, ngx_http_log_op_t *op) { + uintptr_t len; ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, op->data); @@ -1081,9 +1166,21 @@ ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf, } if (value->escape == 0) { + if (ngx_http_log_check_length(r, buf, end, value->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, value->data, value->len); } else { + len = ngx_escape_json(NULL, value->data, value->len); + + if (ngx_http_log_check_length(r, buf, end, value->len + len) + != NGX_OK) + { + return NULL; + } + return (u_char *) ngx_escape_json(buf, value->data, value->len); } } @@ -1108,7 +1205,7 @@ ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r, uintptr_t data) static u_char * ngx_http_log_unescaped_variable(ngx_http_request_t *r, u_char *buf, - ngx_http_log_op_t *op) + u_char *end, ngx_http_log_op_t *op) { ngx_http_variable_value_t *value; @@ -1118,10 +1215,28 @@ ngx_http_log_unescaped_variable(ngx_http_request_t *r, u_char *buf, return buf; } + if (ngx_http_log_check_length(r, buf, end, value->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, value->data, value->len); } +static ngx_int_t +ngx_http_log_check_length(ngx_http_request_t *r, u_char *buf, u_char *end, + size_t len) +{ + if (end - buf < (ssize_t) len) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "no buffer space in log script copy"); + return NGX_ERROR; + } + + return NGX_OK; +} + + static void * ngx_http_log_create_main_conf(ngx_conf_t *cf) { diff --git a/src/stream/ngx_stream_log_module.c b/src/stream/ngx_stream_log_module.c index 0ff7f4244..774bc7e93 100644 --- a/src/stream/ngx_stream_log_module.c +++ b/src/stream/ngx_stream_log_module.c @@ -17,7 +17,7 @@ typedef struct ngx_stream_log_op_s ngx_stream_log_op_t; typedef u_char *(*ngx_stream_log_op_run_pt) (ngx_stream_session_t *s, - u_char *buf, ngx_stream_log_op_t *op); + u_char *buf, u_char *end, ngx_stream_log_op_t *op); typedef size_t (*ngx_stream_log_op_getlen_pt) (ngx_stream_session_t *s, uintptr_t data); @@ -115,16 +115,19 @@ static ngx_int_t ngx_stream_log_variable_compile(ngx_conf_t *cf, static size_t ngx_stream_log_variable_getlen(ngx_stream_session_t *s, uintptr_t data); static u_char *ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf, - ngx_stream_log_op_t *op); + u_char *end, ngx_stream_log_op_t *op); static uintptr_t ngx_stream_log_escape(u_char *dst, u_char *src, size_t size); static size_t ngx_stream_log_json_variable_getlen(ngx_stream_session_t *s, uintptr_t data); static u_char *ngx_stream_log_json_variable(ngx_stream_session_t *s, - u_char *buf, ngx_stream_log_op_t *op); + u_char *buf, u_char *end, ngx_stream_log_op_t *op); static size_t ngx_stream_log_unescaped_variable_getlen(ngx_stream_session_t *s, uintptr_t data); static u_char *ngx_stream_log_unescaped_variable(ngx_stream_session_t *s, - u_char *buf, ngx_stream_log_op_t *op); + u_char *buf, u_char *end, ngx_stream_log_op_t *op); + +static ngx_int_t ngx_stream_log_check_length(ngx_stream_session_t *s, + u_char *buf, u_char *end, size_t len); static void *ngx_stream_log_create_main_conf(ngx_conf_t *cf); @@ -200,7 +203,7 @@ ngx_module_t ngx_stream_log_module = { static ngx_int_t ngx_stream_log_handler(ngx_stream_session_t *s) { - u_char *line, *p; + u_char *line, *p, *end; size_t len, size; ssize_t n; ngx_str_t val; @@ -257,6 +260,8 @@ ngx_stream_log_handler(ngx_stream_session_t *s) } } + len += NGX_LINEFEED_SIZE; + if (log[l].syslog_peer) { /* length of syslog's PRI and HEADER message parts */ @@ -267,8 +272,6 @@ ngx_stream_log_handler(ngx_stream_session_t *s) goto alloc_line; } - len += NGX_LINEFEED_SIZE; - buffer = log[l].file ? log[l].file->data : NULL; if (buffer) { @@ -284,13 +287,18 @@ ngx_stream_log_handler(ngx_stream_session_t *s) if (len <= (size_t) (buffer->last - buffer->pos)) { p = buffer->pos; + end = p + len - NGX_LINEFEED_SIZE; if (buffer->event && p == buffer->start) { ngx_add_timer(buffer->event, buffer->flush); } - for (i = 0; i < log[l].format->ops->nelts; i++) { - p = op[i].run(s, p, &op[i]); + for (i = 0; i < log[l].format->ops->nelts && p; i++) { + p = op[i].run(s, p, end, &op[i]); + } + + if (p == NULL) { + return NGX_ERROR; } ngx_linefeed(p); @@ -313,13 +321,18 @@ ngx_stream_log_handler(ngx_stream_session_t *s) } p = line; + end = p + len - NGX_LINEFEED_SIZE; if (log[l].syslog_peer) { p = ngx_syslog_add_header(log[l].syslog_peer, line); } - for (i = 0; i < log[l].format->ops->nelts; i++) { - p = op[i].run(s, p, &op[i]); + for (i = 0; i < log[l].format->ops->nelts && p; i++) { + p = op[i].run(s, p, end, &op[i]); + } + + if (p == NULL) { + return NGX_ERROR; } if (log[l].syslog_peer) { @@ -663,7 +676,7 @@ ngx_stream_log_flush_handler(ngx_event_t *ev) static u_char * -ngx_stream_log_copy_short(ngx_stream_session_t *s, u_char *buf, +ngx_stream_log_copy_short(ngx_stream_session_t *s, u_char *buf, u_char *end, ngx_stream_log_op_t *op) { size_t len; @@ -672,6 +685,10 @@ ngx_stream_log_copy_short(ngx_stream_session_t *s, u_char *buf, len = op->len; data = op->data; + if (ngx_stream_log_check_length(s, buf, end, len) != NGX_OK) { + return NULL; + } + while (len--) { *buf++ = (u_char) (data & 0xff); data >>= 8; @@ -682,9 +699,13 @@ ngx_stream_log_copy_short(ngx_stream_session_t *s, u_char *buf, static u_char * -ngx_stream_log_copy_long(ngx_stream_session_t *s, u_char *buf, +ngx_stream_log_copy_long(ngx_stream_session_t *s, u_char *buf, u_char *end, ngx_stream_log_op_t *op) { + if (ngx_stream_log_check_length(s, buf, end, op->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, (u_char *) op->data, op->len); } @@ -745,22 +766,39 @@ ngx_stream_log_variable_getlen(ngx_stream_session_t *s, uintptr_t data) static u_char * -ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf, +ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf, u_char *end, ngx_stream_log_op_t *op) { + uintptr_t len; ngx_stream_variable_value_t *value; value = ngx_stream_get_indexed_variable(s, op->data); if (value == NULL || value->not_found) { + if (ngx_stream_log_check_length(s, buf, end, 1) != NGX_OK) { + return NULL; + } + *buf = '-'; return buf + 1; } if (value->escape == 0) { + if (ngx_stream_log_check_length(s, buf, end, value->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, value->data, value->len); } else { + len = ngx_stream_log_escape(NULL, value->data, value->len); + + if (ngx_stream_log_check_length(s, buf, end, value->len + len * 3) + != NGX_OK) + { + return NULL; + } + return (u_char *) ngx_stream_log_escape(buf, value->data, value->len); } } @@ -847,9 +885,10 @@ ngx_stream_log_json_variable_getlen(ngx_stream_session_t *s, uintptr_t data) static u_char * -ngx_stream_log_json_variable(ngx_stream_session_t *s, u_char *buf, +ngx_stream_log_json_variable(ngx_stream_session_t *s, u_char *buf, u_char *end, ngx_stream_log_op_t *op) { + uintptr_t len; ngx_stream_variable_value_t *value; value = ngx_stream_get_indexed_variable(s, op->data); @@ -859,9 +898,21 @@ ngx_stream_log_json_variable(ngx_stream_session_t *s, u_char *buf, } if (value->escape == 0) { + if (ngx_stream_log_check_length(s, buf, end, value->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, value->data, value->len); } else { + len = ngx_escape_json(NULL, value->data, value->len); + + if (ngx_stream_log_check_length(s, buf, end, value->len + len) + != NGX_OK) + { + return NULL; + } + return (u_char *) ngx_escape_json(buf, value->data, value->len); } } @@ -887,7 +938,7 @@ ngx_stream_log_unescaped_variable_getlen(ngx_stream_session_t *s, static u_char * ngx_stream_log_unescaped_variable(ngx_stream_session_t *s, u_char *buf, - ngx_stream_log_op_t *op) + u_char *end, ngx_stream_log_op_t *op) { ngx_stream_variable_value_t *value; @@ -897,10 +948,28 @@ ngx_stream_log_unescaped_variable(ngx_stream_session_t *s, u_char *buf, return buf; } + if (ngx_stream_log_check_length(s, buf, end, value->len) != NGX_OK) { + return NULL; + } + return ngx_cpymem(buf, value->data, value->len); } +static ngx_int_t +ngx_stream_log_check_length(ngx_stream_session_t *s, u_char *buf, u_char *end, + size_t len) +{ + if (end - buf < (ssize_t) len) { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0, + "no buffer space in log script copy"); + return NGX_ERROR; + } + + return NGX_OK; +} + + static void * ngx_stream_log_create_main_conf(ngx_conf_t *cf) { -- 2.55.0 From 62ae743762e68786f51e714b3563fba11c703c9d Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Fri, 19 Jun 2026 22:54:46 +0300 Subject: [PATCH 4/5] Script: buffer overrun protection in direct script usage. Following the previous change, this change adds script overrun protection to direct script evaluation in the proxy, fastcgi, scgi, uwsgi, grpc proxy, index, and try_files modules. This change is a modified version of a patch by Maxim Dounin. The modifications include ngx_http_proxy_v2_module and hardened size checks. Signed-off-by: Roman Arutyunyan Origin: --- src/http/modules/ngx_http_fastcgi_module.c | 26 ++++++++-- src/http/modules/ngx_http_grpc_module.c | 52 ++++++++++++++++++-- src/http/modules/ngx_http_index_module.c | 13 ++++- src/http/modules/ngx_http_proxy_module.c | 31 ++++++++++-- src/http/modules/ngx_http_scgi_module.c | 27 +++++++++- src/http/modules/ngx_http_try_files_module.c | 17 ++++++- src/http/modules/ngx_http_uwsgi_module.c | 30 ++++++++++- 7 files changed, 180 insertions(+), 16 deletions(-) diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c index b9890833d..2b681988b 100644 --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -836,8 +836,8 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) { off_t file_pos; u_char ch, sep, *pos, *lowcase_key; - size_t size, len, key_len, val_len, padding, - allocated; + size_t size, len, params_len, + key_len, val_len, padding, allocated; ngx_uint_t i, n, next, hash, skip_empty, header_params; ngx_buf_t *b; ngx_chain_t *cl, *body; @@ -852,6 +852,7 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) ngx_http_script_len_code_pt lcode; len = 0; + params_len = 0; header_params = 0; ignored = NULL; @@ -891,8 +892,10 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) continue; } - len += 1 + key_len + ((val_len > 127) ? 4 : 1) + val_len; + params_len += 1 + key_len + ((val_len > 127) ? 4 : 1) + val_len; } + + len += params_len; } if (flcf->upstream.pass_request_headers) { @@ -1048,6 +1051,7 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) e.ip = params->values->elts; e.pos = b->last; + e.end = b->last + params_len; e.request = r; e.flushed = 1; @@ -1080,6 +1084,12 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) continue; } + if (ngx_http_script_check_length(&e, 1 + ((val_len > 127) ? 4 : 1)) + != NGX_OK) + { + return NGX_ERROR; + } + *e.pos++ = (u_char) key_len; if (val_len > 127) { @@ -1098,12 +1108,22 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) } e.ip += sizeof(uintptr_t); + if (e.status) { + return NGX_ERROR; + } + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi param: \"%*s: %*s\"", key_len, e.pos - (key_len + val_len), val_len, e.pos - val_len); } + if (e.pos != e.end) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "fastcgi request length mismatch"); + return NGX_ERROR; + } + b->last = e.pos; } diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 98ba8a98f..12d54b923 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -708,8 +708,10 @@ ngx_http_grpc_eval(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, static ngx_int_t ngx_http_grpc_create_request(ngx_http_request_t *r) { - u_char *p, *tmp, *key_tmp, *val_tmp, *headers_frame; - size_t len, tmp_len, key_len, val_len, uri_len; + u_char *p, *tmp, *key_tmp, *val_tmp, *headers_frame, + *headers_end; + size_t len, headers_len, tmp_len, + key_len, val_len, uri_len; uintptr_t escape; ngx_buf_t *b; ngx_uint_t i, next; @@ -733,6 +735,8 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) len = sizeof(ngx_http_grpc_connection_start) - 1 + sizeof(ngx_http_grpc_frame_t); /* headers frame */ + headers_len = 0; + /* :method header */ if (r->method == NGX_HTTP_GET || r->method == NGX_HTTP_POST) { @@ -829,8 +833,8 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) return NGX_ERROR; } - len += 1 + NGX_HTTP_V2_INT_OCTETS + key_len - + NGX_HTTP_V2_INT_OCTETS + val_len; + headers_len += 1 + NGX_HTTP_V2_INT_OCTETS + key_len + + NGX_HTTP_V2_INT_OCTETS + val_len; if (tmp_len < key_len) { tmp_len = key_len; @@ -841,6 +845,8 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) } } + len += headers_len; + if (glcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; @@ -1037,6 +1043,8 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) le.ip = glcf->headers.lengths->elts; + headers_end = b->last + headers_len; + while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; @@ -1061,16 +1069,38 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) continue; } + if (headers_end - b->last < 1) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "no buffer space in grpc create request"); + return NGX_ERROR; + } + *b->last++ = 0; e.pos = key_tmp; + e.end = key_tmp + tmp_len; code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); + if (e.status) { + return NGX_ERROR; + } + + key_len = e.pos - key_tmp; + + if (headers_end - b->last + < (ssize_t) (NGX_HTTP_V2_INT_OCTETS + key_len)) + { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "no buffer space in grpc create request"); + return NGX_ERROR; + } + b->last = ngx_http_v2_write_name(b->last, key_tmp, key_len, tmp); e.pos = val_tmp; + e.end = val_tmp + tmp_len; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; @@ -1078,6 +1108,20 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) } e.ip += sizeof(uintptr_t); + if (e.status) { + return NGX_ERROR; + } + + val_len = e.pos - val_tmp; + + if (headers_end - b->last + < (ssize_t) (NGX_HTTP_V2_INT_OCTETS + val_len)) + { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "no buffer space in grpc create request"); + return NGX_ERROR; + } + b->last = ngx_http_v2_write_value(b->last, val_tmp, val_len, tmp); #if (NGX_DEBUG) diff --git a/src/http/modules/ngx_http_index_module.c b/src/http/modules/ngx_http_index_module.c index 2ee1dd5a4..8f92ebe19 100644 --- a/src/http/modules/ngx_http_index_module.c +++ b/src/http/modules/ngx_http_index_module.c @@ -126,6 +126,7 @@ ngx_http_index_handler(ngx_http_request_t *r) name = NULL; /* suppress MSVC warning */ path.data = NULL; + e.status = 0; index = ilcf->indices->elts; for (i = 0; i < ilcf->indices->nelts; i++) { @@ -180,18 +181,28 @@ ngx_http_index_handler(ngx_http_request_t *r) } else { e.ip = index[i].values->elts; e.pos = name; + e.end = name + allocated; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } + if (e.status) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_http_script_check_length(&e, 1) != NGX_OK) { + return NGX_ERROR; + } + if (*name == '/') { - uri.len = len - 1; + uri.len = e.pos - name; uri.data = name; return ngx_http_internal_redirect(r, &uri, &r->args); } + len = e.pos - name + 1; path.len = e.pos - path.data; *e.pos = '\0'; diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 9cc202c9d..ed9ab956e 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -1235,7 +1235,7 @@ ngx_http_proxy_create_key(ngx_http_request_t *r) static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r) { - size_t len, uri_len, loc_len, body_len, + size_t len, uri_len, loc_len, body_len, headers_len, key_len, val_len; uintptr_t escape; ngx_buf_t *b; @@ -1289,6 +1289,8 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) escape = 0; loc_len = 0; unparsed_uri = 0; + body_len = 0; + headers_len = 0; if (plcf->proxy_lengths && ctx->vars.uri.len) { uri_len = ctx->vars.uri.len; @@ -1327,7 +1329,6 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) le.ip = plcf->body_lengths->elts; le.request = r; le.flushed = 1; - body_len = 0; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; @@ -1363,9 +1364,11 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) continue; } - len += key_len + sizeof(": ") - 1 + val_len + sizeof(CRLF) - 1; + headers_len += key_len + sizeof(": ") - 1 + val_len + sizeof(CRLF) - 1; } + len += headers_len; + if (plcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; @@ -1457,6 +1460,7 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) e.ip = headers->values->elts; e.pos = b->last; + e.end = b->last + headers_len; e.request = r; e.flushed = 1; @@ -1489,6 +1493,14 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); + if (e.status) { + return NGX_ERROR; + } + + if (ngx_http_script_check_length(&e, 2) != NGX_OK) { + return NGX_ERROR; + } + *e.pos++ = ':'; *e.pos++ = ' '; while (*(uintptr_t *) e.ip) { @@ -1497,6 +1509,14 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) } e.ip += sizeof(uintptr_t); + if (e.status) { + return NGX_ERROR; + } + + if (ngx_http_script_check_length(&e, 2) != NGX_OK) { + return NGX_ERROR; + } + *e.pos++ = CR; *e.pos++ = LF; } @@ -1547,6 +1567,7 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) if (plcf->body_values) { e.ip = plcf->body_values->elts; e.pos = b->last; + e.end = b->last + body_len; e.skip = 0; while (*(uintptr_t *) e.ip) { @@ -1554,6 +1575,10 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) code((ngx_http_script_engine_t *) &e); } + if (e.status) { + return NGX_ERROR; + } + b->last = e.pos; } diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c index 3acea87b7..71a817f52 100644 --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -634,7 +634,7 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) { off_t content_length_n; u_char ch, sep, *key, *val, *lowcase_key; - size_t len, key_len, val_len, allocated; + size_t len, params_len, key_len, val_len, allocated; ngx_buf_t *b; ngx_str_t content_length; ngx_uint_t i, n, hash, skip_empty, header_params; @@ -661,6 +661,7 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) len = sizeof("CONTENT_LENGTH") + content_length.len + 1; + params_len = 0; header_params = 0; ignored = NULL; @@ -698,8 +699,10 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) continue; } - len += key_len + val_len + 1; + params_len += key_len + val_len + 1; } + + len += params_len; } if (scf->upstream.pass_request_headers) { @@ -814,6 +817,7 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) e.ip = params->values->elts; e.pos = b->last; + e.end = b->last + params_len; e.request = r; e.flushed = 1; @@ -852,6 +856,10 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); + if (e.status) { + return NGX_ERROR; + } + #if (NGX_DEBUG) val = e.pos; #endif @@ -859,6 +867,15 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } + + if (e.status) { + return NGX_ERROR; + } + + if (ngx_http_script_check_length(&e, 1) != NGX_OK) { + return NGX_ERROR; + } + *e.pos++ = '\0'; e.ip += sizeof(uintptr_t); @@ -866,6 +883,12 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) "scgi param: \"%s: %s\"", key, val); } + if (e.pos != e.end) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "scgi request length mismatch"); + return NGX_ERROR; + } + b->last = e.pos; } diff --git a/src/http/modules/ngx_http_try_files_module.c b/src/http/modules/ngx_http_try_files_module.c index ce783a24d..bbdab8598 100644 --- a/src/http/modules/ngx_http_try_files_module.c +++ b/src/http/modules/ngx_http_try_files_module.c @@ -78,7 +78,7 @@ ngx_module_t ngx_http_try_files_module = { static ngx_int_t ngx_http_try_files_handler(ngx_http_request_t *r) { - size_t len, root, alias, reserve, allocated; + size_t len, root, alias, reserve, allocated, n; u_char *p, *name; ngx_str_t path, args; ngx_uint_t test_dir; @@ -162,8 +162,15 @@ ngx_http_try_files_handler(ngx_http_request_t *r) path.len = (name + tf->name.len - 1) - path.data; } else { + n = allocated; + + if (alias != NGX_MAX_SIZE_T_VALUE) { + n += (r->uri.len - alias); + } + e.ip = tf->values->elts; e.pos = name; + e.end = name + n; e.flushed = 1; while (*(uintptr_t *) e.ip) { @@ -171,6 +178,14 @@ ngx_http_try_files_handler(ngx_http_request_t *r) code((ngx_http_script_engine_t *) &e); } + if (e.status) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_http_script_check_length(&e, 1) != NGX_OK) { + return NGX_ERROR; + } + path.len = e.pos - path.data; *e.pos = '\0'; diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index c1731ff48..024c6c311 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -848,7 +848,7 @@ static ngx_int_t ngx_http_uwsgi_create_request(ngx_http_request_t *r) { u_char ch, sep, *lowcase_key; - size_t key_len, val_len, len, allocated; + size_t key_len, val_len, len, params_len, allocated; ngx_uint_t i, n, hash, skip_empty, header_params; ngx_buf_t *b; ngx_chain_t *cl, *body; @@ -861,6 +861,7 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) ngx_http_script_len_code_pt lcode; len = 0; + params_len = 0; header_params = 0; ignored = NULL; @@ -898,8 +899,10 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) continue; } - len += 2 + key_len + 2 + val_len; + params_len += 2 + key_len + 2 + val_len; } + + len += params_len; } if (uwcf->upstream.pass_request_headers) { @@ -1031,6 +1034,7 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) e.ip = params->values->elts; e.pos = b->last; + e.end = b->last + params_len; e.request = r; e.flushed = 1; @@ -1063,12 +1067,24 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) continue; } + if (ngx_http_script_check_length(&e, 2) != NGX_OK) { + return NGX_ERROR; + } + *e.pos++ = (u_char) (key_len & 0xff); *e.pos++ = (u_char) ((key_len >> 8) & 0xff); code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); + if (e.status) { + return NGX_ERROR; + } + + if (ngx_http_script_check_length(&e, 2) != NGX_OK) { + return NGX_ERROR; + } + *e.pos++ = (u_char) (val_len & 0xff); *e.pos++ = (u_char) ((val_len >> 8) & 0xff); @@ -1077,6 +1093,10 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) code((ngx_http_script_engine_t *) &e); } + if (e.status) { + return NGX_ERROR; + } + e.ip += sizeof(uintptr_t); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1085,6 +1105,12 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) val_len, e.pos - val_len); } + if (e.pos != e.end) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "uwsgi request length mismatch"); + return NGX_ERROR; + } + b->last = e.pos; } -- 2.55.0 From b68fcaf8fb28973eb780973999d7db273ebb2aca Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 3 Jul 2026 15:11:09 +0400 Subject: [PATCH 5/5] Script: avoid garbage at the end of the result string If the script result turned out to be shorter than its predicted length, the result string contained uninitialized bytes at the end. The fix is to cut the result string by its actual size. The following locations returned trailing garbage to the client with URI "/1234abcd". map $uri $foo { ~^/(?[0-9]).*$ $bar; } location ~(?[0-9]*)[a-z]*$ { return 200 $1:$foo; } location ~(?[0-9]*)[a-z]*$ { set $qux $1:$foo; return 200 $qux; } --- src/http/modules/ngx_http_rewrite_module.c | 18 ++++++++++--- src/http/ngx_http_script.c | 30 +++++++++++++++++----- src/http/ngx_http_script.h | 6 +++++ src/stream/ngx_stream_script.c | 18 +++++++++---- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/http/modules/ngx_http_rewrite_module.c b/src/http/modules/ngx_http_rewrite_module.c index ff3b68716..eefe4c8c8 100644 --- a/src/http/modules/ngx_http_rewrite_module.c +++ b/src/http/modules/ngx_http_rewrite_module.c @@ -966,10 +966,11 @@ static char * ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value) { - ngx_int_t n; - ngx_http_script_compile_t sc; - ngx_http_script_value_code_t *val; - ngx_http_script_complex_value_code_t *complex; + ngx_int_t n; + ngx_http_script_compile_t sc; + ngx_http_script_value_code_t *val; + ngx_http_script_complex_value_code_t *complex; + ngx_http_script_complex_value_end_code_t *complex_end; n = ngx_http_script_variables_count(value); @@ -1016,5 +1017,14 @@ ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf, return NGX_CONF_ERROR; } + 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; + return NGX_CONF_OK; } diff --git a/src/http/ngx_http_script.c b/src/http/ngx_http_script.c index c0cd44a6b..43ca64f33 100644 --- a/src/http/ngx_http_script.c +++ b/src/http/ngx_http_script.c @@ -103,7 +103,8 @@ ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val, return NGX_ERROR; } - *value = e.buf; + value->data = e.buf.data; + value->len = e.pos - e.buf.data; return NGX_OK; } @@ -620,6 +621,7 @@ u_char * ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value, void *code_lengths, size_t len, void *code_values) { + size_t n; ngx_uint_t i; ngx_http_script_code_pt code; ngx_http_script_len_code_pt lcode; @@ -641,21 +643,23 @@ ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value, e.request = r; e.flushed = 1; + n = len; + while (*(uintptr_t *) e.ip) { lcode = *(ngx_http_script_len_code_pt *) e.ip; - len += lcode(&e); + n += lcode(&e); } - value->len = len; - value->data = ngx_pnalloc(r->pool, len); + value->len = n; + value->data = ngx_pnalloc(r->pool, n); if (value->data == NULL) { return NULL; } e.ip = code_values; e.pos = value->data; - e.end = value->data + len; + e.end = value->data + n; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; @@ -666,6 +670,8 @@ ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value, return NULL; } + value->len = e.pos + len - value->data; + return e.pos; } @@ -1547,6 +1553,8 @@ ngx_http_script_full_name_code(ngx_http_script_engine_t *e) } e->buf = value; + e->pos = value.data + value.len; + e->end = e->pos; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script fullname: \"%V\"", &value); @@ -1854,8 +1862,18 @@ ngx_http_script_complex_value_code(ngx_http_script_engine_t *e) e->pos = e->buf.data; e->end = e->buf.data + len; +} + + +void +ngx_http_script_complex_value_end_code(ngx_http_script_engine_t *e) +{ + e->ip += sizeof(ngx_http_script_complex_value_end_code_t); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, + "http script complex value end"); - e->sp->len = e->buf.len; + e->sp->len = e->pos - e->buf.data; e->sp->data = e->buf.data; e->sp++; } diff --git a/src/http/ngx_http_script.h b/src/http/ngx_http_script.h index b086b266f..4f2fbd16e 100644 --- a/src/http/ngx_http_script.h +++ b/src/http/ngx_http_script.h @@ -200,6 +200,11 @@ typedef struct { } ngx_http_script_complex_value_code_t; +typedef struct { + ngx_http_script_code_pt code; +} ngx_http_script_complex_value_end_code_t; + + typedef struct { ngx_http_script_code_pt code; uintptr_t value; @@ -263,6 +268,7 @@ void ngx_http_script_equal_code(ngx_http_script_engine_t *e); void ngx_http_script_not_equal_code(ngx_http_script_engine_t *e); void ngx_http_script_file_code(ngx_http_script_engine_t *e); void ngx_http_script_complex_value_code(ngx_http_script_engine_t *e); +void ngx_http_script_complex_value_end_code(ngx_http_script_engine_t *e); void ngx_http_script_value_code(ngx_http_script_engine_t *e); void ngx_http_script_set_var_code(ngx_http_script_engine_t *e); void ngx_http_script_var_set_handler_code(ngx_http_script_engine_t *e); diff --git a/src/stream/ngx_stream_script.c b/src/stream/ngx_stream_script.c index 2e31102df..559b8b9fb 100644 --- a/src/stream/ngx_stream_script.c +++ b/src/stream/ngx_stream_script.c @@ -104,7 +104,8 @@ ngx_stream_complex_value(ngx_stream_session_t *s, return NGX_ERROR; } - *value = e.buf; + value->data = e.buf.data; + value->len = e.pos - e.buf.data; return NGX_OK; } @@ -496,6 +497,7 @@ u_char * ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value, void *code_lengths, size_t len, void *code_values) { + size_t n; ngx_uint_t i; ngx_stream_script_code_pt code; ngx_stream_script_engine_t e; @@ -517,21 +519,23 @@ ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value, e.session = s; e.flushed = 1; + n = len; + while (*(uintptr_t *) e.ip) { lcode = *(ngx_stream_script_len_code_pt *) e.ip; - len += lcode(&e); + n += lcode(&e); } - value->len = len; - value->data = ngx_pnalloc(s->connection->pool, len); + value->len = n; + value->data = ngx_pnalloc(s->connection->pool, n); if (value->data == NULL) { return NULL; } e.ip = code_values; e.pos = value->data; - e.end = value->data + len; + e.end = value->data + n; while (*(uintptr_t *) e.ip) { code = *(ngx_stream_script_code_pt *) e.ip; @@ -542,6 +546,8 @@ ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value, return NULL; } + value->len = e.pos + len - value->data; + return e.pos; } @@ -1062,6 +1068,8 @@ ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e) } e->buf = value; + e->pos = value.data + value.len; + e->end = e->pos; ngx_log_debug1(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0, "stream script fullname: \"%V\"", &value); -- 2.55.0