Opened 6 months ago

Last modified 6 months ago

#2664 new defect

broken header while reading PROXY protocol in nginx stream with pass module

Reported by: sad3rasd@… Owned by:
Priority: blocker Milestone:
Component: nginx-core Version: 1.25.x
Keywords: Cc: sad3rasd@…
uname -a: Linux debian 4.19.0-20-amd64 #1 SMP Debian 4.19.235-1 (2022-03-17) x86_64 GNU/Linux
nginx -V: nginx version: nginx/1.26.1
built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
built with OpenSSL 1.1.1w 11 Sep 2023
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/tmp/nginx-1.26.1=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -fPIC' --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --with-debug --with-pcre --with-pcre-jit --without-http --without-http-cache --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --without-stream_limit_conn_module --without-stream_access_module --without-stream_geo_module --without-stream_split_clients_module --without-stream_return_module --without-stream_upstream_hash_module --without-stream_upstream_least_conn_module --without-stream_upstream_random_module --without-stream_upstream_zone_module --with-threads --add-module=/tmp/njs-0.8.5/nginx

Description

the provided configuration should accept a proxy protocol header encapsulated in a ssl connection but it doesnt work
when the server block try to read and parse proxy protocol header it fail with the error:
broken header: "A%��м�P���S�V]Җƨ���Tp����@o$HB����~F��r�K3��8Q������W�M�" while reading PROXY protocol, client: ..., server: unix:/run/nginx/uds_pp.sock

i tried to preread the stream with njs and it start with the correct proxy protocol header

i also noticed that if no ssl is involved (server unix:/run/nginx/uds.sock without ssl) everything works as expected

relevant part of nginx.conf

stream {
    ...
    map $ssl_preread_server_name $us {
        "example.com" unix:/run/nginx/example.sock;
        default unix:/run/nginx/uds.sock;
    }
    server {
        listen 8443;
        ssl_preread on;
        pass $us;
    }
    server {
        listen unix:/run/nginx/uds.sock ssl;
        ssl_certificate /etc/nginx/self-signed.pem;
        ssl_certificate_key /etc/nginx/self-signed.key;
        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;
        ssl_protocols TLSv1.3;
        ssl_prefer_server_ciphers off;
        pass unix:/run/nginx/uds_pp.sock;
    }
    server {
        listen unix:/run/nginx/uds_pp.sock proxy_protocol;
        proxy_pass ...;
        ...
    }
}

Change History (4)

comment:1 by Roman Arutyunyan, 6 months ago

Thanks for reporting this. Indeed PROXY protocol incapsulated in SSL cannot be used with pass due to using recv(MSG_PEEK) on the socket. So far I don't see a simple fix for this. This can be avoided by using proxy_pass instead.

Meanwhile, PROXY protocol is not supposed to be incapsulated in another protocol in the first place.

in reply to:  1 comment:2 by sad3rasd@…, 6 months ago

Thank you for your reply.
I did two further tests after my ticket:

  • with proxy_pass that work like you said
  • move the server block inside an http block (and proxy_pass stuff inside a location block) and it does work with pass directive (see configuration below).

nginx.conf

stream {
    ...
    map $ssl_preread_server_name $us {
        "example.com" unix:/run/nginx/example.sock;
        default unix:/run/nginx/uds.sock;
    }
    server {
        listen 8443;
        ssl_preread on;
        pass $us;
    }
    server {
        listen unix:/run/nginx/uds.sock ssl;
        ssl_certificate /etc/nginx/self-signed.pem;
        ssl_certificate_key /etc/nginx/self-signed.key;
        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;
        ssl_protocols TLSv1.3;
        ssl_prefer_server_ciphers off;
        pass unix:/run/nginx/uds_pp.sock;
    }
}
http {
    ...
    server {
        listen unix:/run/nginx/uds_pp.sock proxy_protocol;
        location / {
            proxy_pass ...;
            ...
        }
    }
}

why this different behavior between stream to stream and stream to http? can be handled in the same way in order to fix the issue?

Regards

comment:3 by Roman Arutyunyan, 6 months ago

Indeed there's a slightly different behavior in http and stream regarding reading client PROXY protocol header. It originates from the fact that in plaintext HTTP nginx reads client request in a buffer and then parses PROXY protocol header at the start of the buffer. In stream client protocol is unknown and nginx avoids reading data beyond PROXY protocol header. And since there's no way to tell in advance how big the header will be, it first MSG_PEEKs the input, and only then the actual header is read out from the socket. The MSG_PEEK part is incompatible with an SSL socket being used.

comment:4 by sad3rasd@…, 6 months ago

Got it, I'll use proxy_pass for now, hoping for a fix in the future

Thank you again Roman

Note: See TracTickets for help on using tickets.