Skip to content

jhttpd: security audit of the server base classes, before anything faces a public port #240

Description

@xoloki

Part of #239. sys::server, sys::listener, net::http::server and
util::http were written for a loopback harness, and several of them say so.
jhttpd is a decision to face a port anyone can reach, and that is a different
threat model rather than a bigger one.

This is the audit issue. It is deliberately separate from #237
(authentication) and #238 (rate limiting), which are features; this is about
the code that already exists.

Starting from something, not nothing

Worth recording, so an audit does not re-derive it:

Two things a short look already found

Both are small; they are here because finding them took five minutes, which
suggests what an actual audit would turn up.

The number of header fields is unbounded. max_head caps the head at 8192
octets and nothing caps the field count, so a request can carry several
hundred fields within that budget. fields looks up linearly and the server
does several lookups per request, so cost is quadratic in the field count --
bounded by 8KB, so this is a sharp edge rather than a hole, but it is the shape
of one.

400 bodies reflect client input. decide_framing throws messages built
from the offending value:

throw error("Content-Length is not a number: \"" + v + "\"");

and serve_request_async puts e.what() straight into the response body. It
goes out as text/plain, so this is not XSS as it stands -- but it is
unsanitised client data echoed back, and the reasoning that makes it safe is
the Content-Type, which nothing enforces or documents.

The surface

  • util/http.cc -- the grammar-driven parsers, chunk decoding, the caps,
    and integer handling in Content-Length and range arithmetic.
  • net/http_server.cc -- routing, the conditional and range logic, error
    paths, and what those error paths disclose.
  • sys/server.cc, sys/listener.cc -- accept, admission control,
    descriptor ownership, the teardown paths that net: keep-alive on the async server, and the three latent sys defects only a reused connection could reach #217 already found a data race
    in.
  • sys/async_*.cc, sys/sslstream.hh -- buffer arithmetic, and the TLS
    configuration tls_context actually applies (protocol versions, ciphers,
    renegotiation) which has never been reviewed.
  • sys/reactor.cc -- table growth under many connections.

What "audit level" should mean here

  • Fuzzing the parsers. util::http's head and body readers, the ABNF
    grammar entry points, and the chunk decoder take attacker-controlled bytes
    and are the right shape for libFuzzer or afl. This is the highest-value item
    on the list and nothing here has ever been fuzzed.
  • ASan and UBSan across the suite, not only TSan -- which has been run and
    found the cancel_token race, so the precedent exists.
  • A pass for unbounded growth: buffered responses have no cap the way
    requests have max_body; jserve's relay queue grows if a client is slow but
    not gone.
  • Integer overflow in range and length arithmetic, which mixes
    long long, size_t and std::streamoff.
  • A written threat model, so the next person knows what is defended against
    and what is not -- which is what "not hardened for a public port" was doing
    informally.

What it should not become

A checklist ticked once. The value is in the fuzzing harness and the sanitiser
runs, because those keep working after the audit is over; a document that was
true in September is not a defence.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions