Skip to content

more_set_headers WWW-Authenticate can segfault #169

@richbern

Description

@richbern

I've got an authentication service set up that will return with a 401 response unless an appropriate Authorization header is provided.
If that authorization service does not provide a WWW-Authenticate header, the nginx worker process can segfault if more_set_headers is used to add the WWW-Authenticate header.

A lot of my config comes from using global-auth-url in the ingress-nginx controller, but this reproduces with just an OpenResty install.

Relevant nginx.conf

http {
 worker_processes 1;
  ...
  upstream upstream_balancer {
    server 0.0.0.1;
    keepalive 320;
    keepalive_time 1h;
    keepalive_timeout  60s;
    keepalive_requests 10000;
  }
  more_set_headers -s "401" 'WWW-Authenticate: Bearer realm="https://my.org/auth"';
  server {
    ...
    listen: 55555;
    server_name localhost;

    location /_external-auth-Lw-Prefix {
            internal;
            access_log off;
            set $proxy_upstream_name "default-auth";
            proxy_pass_request_body     off;
            proxy_set_header            Content-Length          "";
            proxy_set_header            X-Forwarded-Proto       "";
            proxy_method                GET;
            proxy_set_header            Host                    unix;
            proxy_set_header            X-Original-URL          $scheme://$http_host$request_uri;
            proxy_set_header            X-Original-Method       $request_method;
            proxy_set_header            X-Sent-From             "default-copy";
            proxy_set_header            X-Real-IP               $remote_addr;
            proxy_set_header            X-Forwarded-For        $remote_addr;
            proxy_set_header            X-Auth-Request-Redirect $request_uri;
            proxy_buffering             off;
            proxy_buffer_size           4k;
            proxy_buffers               4 4k;
            proxy_request_buffering     on;
            proxy_ssl_server_name       on;
            proxy_pass_request_headers  on;
            client_max_body_size        1m;
            proxy_http_version 1.1;
            set $target "http://127.0.0.1:8081/";
            proxy_pass $target;
    }
    location / {
        port_in_redirect off;
        set $balancer_ewma_score -1;
        set $proxy_upstream_name "default-auth";
        set $proxy_host          $proxy_upstream_name;
        set $pass_access_scheme  $scheme;
        set $pass_server_port    $server_port;
        set $best_http_host      $http_host;
        set $pass_port           $pass_server_port;
        set $proxy_alternative_upstream_name "";
        auth_request        /_external-auth-Lw-Prefix;
        auth_request_set    $auth_cookie $upstream_http_set_cookie;
        add_header          Set-Cookie $auth_cookie;
        auth_request_set $authHeader0 $upstream_http_;
        proxy_set_header '' $authHeader0;
        client_max_body_size                    1m;
        proxy_set_header Host                   $best_http_host;
        proxy_set_header                        Upgrade           $http_upgrade;
        proxy_set_header X-Real-IP              $remote_addr;
        proxy_set_header X-Forwarded-For        $remote_addr;
        proxy_set_header X-Forwarded-Host       $best_http_host;
        proxy_set_header X-Forwarded-Port       $pass_port;
        proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
        proxy_set_header X-Forwarded-Scheme     $pass_access_scheme;
        proxy_set_header X-Scheme               $pass_access_scheme;
        proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;
        proxy_set_header Proxy                  "";
        proxy_connect_timeout                   5s;
        proxy_send_timeout                      60s;
        proxy_read_timeout                      60s;
        proxy_buffering                         off;
        proxy_buffer_size                       4k;
        proxy_buffers                           4 4k;
        proxy_max_temp_file_size                1024m;
        proxy_request_buffering                 on;
        proxy_http_version                      1.1;
        proxy_cookie_domain                     off;
        proxy_cookie_path                       off;
        proxy_next_upstream                     error timeout;
        proxy_next_upstream_timeout             0;
        proxy_next_upstream_tries               3;
        proxy_pass http://upstream_balancer;
        proxy_redirect                          off;
    }

And a simple web site sitting on 8081 that responds with:

  • Status 401, no WWW-Authenticate header if X-Skip-Header: 1 header is provided on the request
  • Status 401, WWW-Authenticate header provided otherwise
$ curl -v http://localhost:55555/ --http1.1
* Host localhost:55555 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:55555...
* connect to ::1 port 55555 from ::1 port 40470 failed: Connection refused
*   Trying 127.0.0.1:55555...
* Connected to localhost (127.0.0.1) port 55555
> GET / HTTP/1.1
> Host: localhost:55555
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Server: openresty/1.27.1.2
< Date: Tue, 17 Jun 2025 18:57:45 GMT
< Content-Type: text/html
< Content-Length: 185
< Connection: keep-alive
< WWW-Authenticate: Bearer realm="https://my.org/auth"
<
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>openresty/1.27.1.2</center>
</body>
</html>
* Connection #0 to host localhost left intact

works successfully. However,

$ curl -v http://localhost:55555/ --http1.1 -H "X-Skip-Header: 1"
* Host localhost:55555 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:55555...
* connect to ::1 port 55555 from ::1 port 40116 failed: Connection refused
*   Trying 127.0.0.1:55555...
* Connected to localhost (127.0.0.1) port 55555
> GET / HTTP/1.1
> Host: localhost:55555
> User-Agent: curl/8.5.0
> Accept: */*
> X-Skip-Header: 1
>
* Empty reply from server
* Closing connection
curl: (52) Empty reply from server

can sometimes occur. This seems to happen more frequently if https is used, and for non-https requests I've had success throwing lots of traffic at the server with bombardier until the server starts having issues. When this happens, I see entries like the following in the nginx error log:

2025/06/17 14:05:26 [alert] 3423044#3423044: worker process 3426331 exited on signal 11 (core dumped)

Once in this state, any request with X-Skip-Headers: 1 will terminate abruptly; whereas other requests will succeed as expected.

I'm thinking this might be related to this nginx change: https://trac.nginx.org/nginx/ticket/485.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions