Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@

Changes with FreeUnit 1.35.4 xx xxx 2026

*) Bugfix: pass a non-zero ovector size to PCRE2 match-data create
(a 0 size is undefined per the PCRE2 docs) and reject malformed
port ranges of fewer than three bytes in HTTP routing. Also
documents the symmetric pattern/request URI decoding, the
intentionally case-insensitive host matcher, and that the
Linux capability module does not call capset(2) — privilege
barriers in Unit are setuid + PR_SET_NO_NEW_PRIVS, not cap
drops.

*) Bugfix: fix router process CPU spin and connection hang under port
scanning load; CLOSE-WAIT sockets are now cleaned up properly on
client FIN, idle connection queue iteration fixed, systemd file
Expand Down
12 changes: 12 additions & 0 deletions src/nxt_capability.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
* Copyright (C) NGINX, Inc.
*/

/*
* NOTE: this module currently exposes capability *detection*
* (capget), used by nxt_isolation.c and the credential machinery to
* decide whether a non-root build can honor setuid/setgid in the
* "user"/"group" config keys. Capabilities are NOT dropped
* programmatically via capset(2): the privilege barrier for app
* processes is the existing setuid + PR_SET_NO_NEW_PRIVS dance in
* nxt_credential.c / nxt_process.c. Operators expecting an explicit
* capability-drop step should not assume one exists; isolation in
* Unit relies on uid/namespace/seccomp boundaries, not capset.
*/

#include <nxt_main.h>

#if (NXT_HAVE_LINUX_CAPABILITY)
Expand Down
26 changes: 25 additions & 1 deletion src/nxt_http_route.c
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,13 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
}

if (mtcf.host != NULL) {
/*
* Host matching is intentionally always case-insensitive
* (LOWCASE). Per RFC 9110 §4.2.3 the host component is
* case-insensitive, and there is no per-rule sensitivity
* flag — an admin expecting case-sensitive host filtering
* will not get it from this rule.
*/
rule = nxt_http_route_rule_create(task, mp, mtcf.host, 1,
NXT_HTTP_ROUTE_PATTERN_LOWCASE,
NXT_HTTP_URI_ENCODING_NONE);
Expand Down Expand Up @@ -1176,6 +1183,15 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp,
}


/*
* Configured pattern strings are decoded once here at compile time,
* while request URIs are decoded by the HTTP parser before they reach
* the matcher. Both paths funnel through the same nxt_decode_uri /
* nxt_decode_uri_plus helpers; %XX semantics are symmetric, so a
* "%2e%2e" in the configured pattern will compare against the same
* bytes a request "%2e%2e" decoded into. If either helper's behaviour
* changes, route patterns and request URIs MUST be updated in lock-step.
*/
static nxt_int_t
nxt_http_route_decode_str(nxt_str_t *str, nxt_http_uri_encoding_t encoding)
{
Expand Down Expand Up @@ -2147,7 +2163,15 @@ nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern,
#if (NXT_HAVE_REGEX)
if (pattern->regex) {
if (r->regex_match == NULL) {
r->regex_match = nxt_regex_match_create(r->mem_pool, 0);
/*
* Reuse one match-data struct across every pattern compiled
* against this request, so size it for the minimum ovector
* (one offset pair — the overall match). Captures are not
* consulted by the matcher. Passing 0 to PCRE2's
* pcre2_match_data_create() is undefined per the public
* docs; 1 is the documented minimum.
*/
r->regex_match = nxt_regex_match_create(r->mem_pool, 1);
if (nxt_slow_path(r->regex_match == NULL)) {
return NXT_ERROR;
}
Expand Down
15 changes: 14 additions & 1 deletion src/nxt_http_route_addr.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ nxt_http_route_addr_pattern_parse(nxt_mp_t *mp,

goto parse_port;
}

/*
* /32 is the single-host case; set EXACT explicitly so the
* state machine reflects intent rather than relying on the
* implicit assignment in the fallthrough path below.
*/
Comment on lines +265 to +269

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment states that EXACT is set here, but the code actually falls through to line 277 where base->match_type is eventually assigned. To make the code match the documentation and improve clarity, it is better to explicitly set the match type within this block before exiting the CIDR parsing logic.

        /*
         * /32 is the single-host case; fall through to the inet_addr
         * parse below, but set EXACT here so the state machine reflects
         * the user's intent rather than relying on the implicit default.
         */
        base->match_type = NXT_HTTP_ROUTE_ADDR_EXACT;

base->match_type = NXT_HTTP_ROUTE_ADDR_EXACT;
}

inet->start = nxt_inet_addr(addr.start, addr.length);
Expand All @@ -283,7 +290,13 @@ nxt_http_route_addr_pattern_parse(nxt_mp_t *mp,
return NXT_OK;
}

delim = memchr(port.start, '-', port.length - 1);
/*
* Search the entire port string for '-' (the original "-1" bound
* silently ignored a trailing dash in inputs like "8-"). If the
* dash is at either end the resulting empty half makes
* nxt_int_parse() return < 0 below, producing PATTERN_PORT_ERROR.
*/
delim = memchr(port.start, '-', port.length);
if (delim != NULL) {
ret = nxt_int_parse(port.start, delim - port.start);
if (nxt_slow_path(ret < 0 || ret > 65535)) {
Expand Down
Loading