Bug #3047
closedmismatched nested config conditions in lighttpd 1.4.56
Description
Stumbled upon a weird branching issue, seems to be changing its outcome from first run to the other: what follows is the minimized example, apologies for length :-)
Briefly, if(condition) else if(condition)
- on first branch condition
returns false
then on second returns true
. condition
is $HTTP["scheme"] != "https"
.
Operating System: Fedora Rawhide
$ rpm -qi lighttpd … Version : 1.4.56 Release : 2.fc34 Architecture: x86_64 Signature : RSA/SHA256, Wed Dec 2 21:23:49 2020, Key ID 1161ae6945719a39 Source RPM : lighttpd-1.4.56-2.fc34.src.rpm Build Date : Wed Dec 2 21:06:33 2020 Build Host : buildhw-x86-01.iad2.fedoraproject.org … $ lighttpd -V lighttpd/1.4.56 (ssl) - a light and fast webserver Event Handlers: + select (generic) + poll (Unix) + epoll (Linux) - /dev/poll (Solaris) - eventports (Solaris) - kqueue (FreeBSD) - libev (generic) Network handler: + linux-sendfile - freebsd-sendfile - darwin-sendfile - solaris-sendfilev + writev + write - mmap support Features: + IPv6 support + zlib support - bzip2 support - brotli support + crypt support + OpenSSL support - mbedTLS support - NSS crypto support - GnuTLS support - WolfSSL support + Nettle support + PCRE support + MySQL support - PgSQL support - DBI support + Kerberos support + LDAP support + PAM support - memcached support - FAM support + LUA support - xml support - SQLite support + GDBM support
Configuration:
$ cat huh.conf server.modules = ( "mod_redirect", "mod_openssl" ) server.document-root = "/tmp" debug.log-condition-handling = "enable" debug.log-file-not-found = "enable" debug.log-request-handling = "enable" debug.log-request-header = "enable" debug.log-response-header = "enable" debug.log-state-handling = "enable" debug.log-ssl-noise = "enable" $SERVER["socket"] == ":443" { ssl.engine = "enable" ssl.pemfile = "/etc/pki/tls/certs/dummy.pem" } $HTTP["scheme"] != "https" { url.redirect = ("" => "OFC-NOT!${url.scheme}!${url.authority}!${url.port}!${url.path}!${qsa}!") } else { $HTTP["scheme"] != "https" { url.redirect = ("" => "THEN-WHY?!${url.scheme}!${url.authority}!${url.port}!${url.path}!${qsa}!") } } $ lighttpd -tt -f huh.conf $ lighttpd -p -f huh.conf config { var.PID = 448883 var.CWD = "/tmp" server.modules = ("mod_redirect", "mod_openssl") server.document-root = "/tmp" debug.log-condition-handling = "enable" debug.log-file-not-found = "enable" debug.log-request-handling = "enable" debug.log-request-header = "enable" debug.log-response-header = "enable" debug.log-state-handling = "enable" debug.log-ssl-noise = "enable" $SERVER["socket"] == ":443" { # block 1 ssl.engine = "enable" ssl.pemfile = "/etc/pki/tls/certs/dummy.pem" } # end of $SERVER["socket"] == ":443" $HTTP["scheme"] != "https" { # block 2 url.redirect = ( "" => "OFC-NOT!${url.scheme}!${url.authority}!${url.port}!${url.path}!${qsa}!", ) } # end of $HTTP["scheme"] != "https" else { # block 3 $HTTP["scheme"] != "https" { # block 4 url.redirect = ( "" => "THEN-WHY?!${url.scheme}!${url.authority}!${url.port}!${url.path}!${qsa}!", ) } # end of $HTTP["scheme"] != "https" } # end of else }
Run server with:
$ lighttpd -D -f huh.conf |& tee huh.log
Test:
curl -k -v --compressed --resolve '*:80:192.168.122.66' --resolve '*:443:192.168.122.66' https://foo/bar * Added *:80:192.168.122.66 to DNS cache * RESOLVE *:80 is wildcard, enabling wildcard checks * Added *:443:192.168.122.66 to DNS cache * RESOLVE *:443 is wildcard, enabling wildcard checks * Hostname foo was found in DNS cache * Trying 192.168.122.66:443... * Connected to foo (192.168.122.66) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/pki/tls/certs/ca-bundle.crt * CApath: none * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * ALPN, server accepted to use http/1.1 * Server certificate: * subject: C=--; ST=SomeState; L=SomeLocality; O=SomeOrganization; OU=SomeUnit; CN=localhost.localdomain; emailAddress=root@localhost.localdomain * start date: May 14 22:22:07 2017 GMT * expire date: May 29 22:22:07 2037 GMT * issuer: C=--; ST=SomeState; L=SomeLocality; O=SomeOrganization; OU=SomeUnit; CN=localhost.localdomain; emailAddress=root@localhost.localdomain * SSL certificate verify result: self signed certificate (18), continuing anyway. > GET /bar HTTP/1.1 > Host: foo > User-Agent: curl/7.74.0 > Accept: */* > Accept-Encoding: deflate, gzip, br > * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * old SSL session ID is stale, removing * Mark bundle as not supporting multiuse < HTTP/1.1 301 Moved Permanently < Location: THEN-WHY?!https!foo!443!/bar!! < Content-Length: 0 < Date: Mon, 14 Dec 2020 23:46:07 GMT < Server: lighttpd/1.4.56 < * Connection #0 to host foo left intact
Server log:
2020-12-15 00:56:50: (server.c.1499) server started (lighttpd/1.4.56) 2020-12-15 00:57:01: (configfile-glue.c.647) === start of condition block === 2020-12-15 00:57:01: (configfile-glue.c.612) SERVER["socket"] (0.0.0.0:443) compare to 0.0.0.0:443 2020-12-15 00:57:01: (configfile-glue.c.352) 1 (uncached) result: true 2020-12-15 00:57:01: (connections.c.1412) state at enter 6 req-start 2020-12-15 00:57:01: (connections.c.1060) state for fd:6 id:0 req-start 2020-12-15 00:57:01: (connections.c.1060) state for fd:6 id:0 read 2020-12-15 00:57:01: (connections.c.1419) state at exit: 6 read 2020-12-15 00:57:02: (connections.c.1412) state at enter 6 read 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 read 2020-12-15 00:57:02: (connections.c.1419) state at exit: 6 read 2020-12-15 00:57:02: (connections.c.1412) state at enter 6 read 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 read 2020-12-15 00:57:02: (connections.c.800) fd: 6 request-len: 106\nGET /bar HTTP/1.1\r\nHost: foo\r\nUser-Agent: curl/7.69.1\r\nAccept: */*\r\nAccept-Encoding: deflate, gzip, br\r\n\r\n 2020-12-15 00:57:02: (response.c.342) run condition 2020-12-15 00:57:02: (response.c.401) -- parsed Request-URI 2020-12-15 00:57:02: (response.c.403) Request-URI : /bar 2020-12-15 00:57:02: (response.c.405) URI-scheme : https 2020-12-15 00:57:02: (response.c.407) URI-authority : foo 2020-12-15 00:57:02: (response.c.409) URI-path (clean): /bar 2020-12-15 00:57:02: (response.c.411) URI-query : 2020-12-15 00:57:02: (configfile-glue.c.647) === start of condition block === 2020-12-15 00:57:02: (configfile-glue.c.612) SERVER["socket"] (0.0.0.0:443) compare to 0.0.0.0:443 2020-12-15 00:57:02: (configfile-glue.c.352) 1 (uncached) result: true 2020-12-15 00:57:02: (configfile-glue.c.647) === start of condition block === 2020-12-15 00:57:02: (configfile-glue.c.612) HTTP["scheme"] (https) compare to https 2020-12-15 00:57:02: (configfile-glue.c.352) 2 (uncached) result: false 2020-12-15 00:57:02: (configfile-glue.c.647) === start of condition block === 2020-12-15 00:57:02: (configfile-glue.c.450) go parent global/HTTPscheme==https 2020-12-15 00:57:02: (configfile-glue.c.473) go prev global/HTTPscheme!=https 2020-12-15 00:57:02: (configfile-glue.c.352) 2 (cached) result: false 2020-12-15 00:57:02: (configfile-glue.c.352) 3 (uncached) result: true 2020-12-15 00:57:02: (configfile-glue.c.352) 4 (uncached) result: unset 2020-12-15 00:57:02: (response.c.161) Response-Header:\nHTTP/1.1 301 Moved Permanently\r\nLocation: THEN-WHY?!https!foo!443!/bar!!\r\nContent-Length: 0\r\nDate: Mon, 14 Dec 2020 22:57:02 GMT\r\nServer: lighttpd/1.4.56\r\n\r\n 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 req-start 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 read 2020-12-15 00:57:02: (connections.c.1419) state at exit: 6 read 2020-12-15 00:57:02: (connections.c.1412) state at enter 6 read 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 read 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 error 2020-12-15 00:57:02: (connections.c.191) shutdown for fd 6 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 close 2020-12-15 00:57:02: (connections.c.1419) state at exit: 6 close 2020-12-15 00:57:02: (connections.c.1412) state at enter 6 close 2020-12-15 00:57:02: (connections.c.1060) state for fd:6 id:0 close 2020-12-15 00:57:02: (connections.c.126) connection closed for fd 6 2020-12-15 00:57:02: (connections.c.1060) state for fd:-1 id:0 connect 2020-12-15 00:57:02: (connections.c.1419) state at exit: -1 connect
Thanks!
Updated by gstrauss about 4 years ago
- Category set to core
Thank you for the details. I'll see if I can reproduce this.
Updated by gstrauss about 4 years ago
- Subject changed from Flaky $HTTP["scheme"] != "https" to mismatched nested config conditions in lighttpd 1.4.56
- Status changed from New to Patch Pending
- Target version changed from 1.4.x to 1.4.57
This was a wild one to track down. The (historical) config parsing code was sometimes re-ordering items in an array after I had saved the index of its position.
--- a/src/configfile.c +++ b/src/configfile.c @@ -2190,6 +2190,25 @@ int config_read(server *srv, const char *fn) { return ret; } + /* reorder dc->context_ndx to match srv->config_context->data[] index. + * srv->config_context->data[] may have been re-ordered in configparser.y. + * Since the dc->context_ndx (id) is reused by config_insert*() and by + * plugins to index into srv->config_context->data[], reorder into the + * order encountered during config file parsing for least surprise to + * end-users writing config files. */ + for (uint32_t i = 0; i < srv->config_context->used; ++i) { + dc = (data_config *)srv->config_context->data[i]; + if (dc->context_ndx == (int)i) continue; + for (uint32_t j = i; j < srv->config_context->used; ++j) { + dc = (data_config *)srv->config_context->data[j]; + if (dc->context_ndx == (int)i) { + srv->config_context->data[j] = srv->config_context->data[i]; + srv->config_context->data[i] = (data_unset *)dc; + break; + } + } + } + if (0 != config_insert_srvconf(srv)) { return -1; }
Updated by gstrauss about 4 years ago
In earlier versions of lighttpd, the (historical) config parsing code was ALSO sometimes re-ordering items in an array.
However, it was not until lighttpd 1.4.56 that the context_ndx was reused as an index into the array.
Updated by gstrauss about 4 years ago
- Status changed from Patch Pending to Fixed
Applied in changeset bbd958382e596a86b07eb7bfa1c675007a31bb49.
Also available in: Atom