Project

General

Profile

Bug #3047

closed

mismatched nested config conditions in lighttpd 1.4.56

Added by altblue about 1 month ago. Updated about 1 month ago.

Status:
Fixed
Priority:
Normal
Category:
core
Target version:
ASK QUESTIONS IN Forums:
No

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!

#1

Updated by gstrauss about 1 month ago

  • Category set to core

Thank you for the details. I'll see if I can reproduce this.

#2

Updated by gstrauss about 1 month 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;
        }

#3

Updated by gstrauss about 1 month 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.

#4

Updated by gstrauss about 1 month ago

  • Status changed from Patch Pending to Fixed

Also available in: Atom