Skip to content

[TRACKING] ext/http embedded server correctness and safety audit follow-ups #4287

Description

@thc1006

While fixing #4281 and #4282 I read through the rest of ext/http and found more than fits in one PR. This is a tracker rather than a single bug: each item below has its own root cause, its own platform surface and its own fix, and I am opening a child issue and a small PR per item as I get to them rather than filing everything at once.

Every item is a code-inspection finding against main at 9d38caf. The affected code path and the conditions that reach it are described per item. Runtime reproduction and regression-test status belong in the child issues, not here. Please push back on anything you consider a deliberate limitation of a test server rather than a defect, and I will drop it.

One further potentially security-sensitive finding was reported privately under the project security policy and is intentionally out of scope here.

Memory safety

Process safety

Nonblocking write handling

  • A partial write can stall a response on Windows. sendMore() performs a single send() per readiness event. Winsock permits a nonblocking send() to succeed having transferred only part of the buffer, and documents that after the first FD_WRITE an application should keep sending until a send fails with WSAEWOULDBLOCK, because that failure is what arms the next FD_WRITE. Re-registering the same interest does not help: socket_tools.h#L582 only calls WSAEventSelect when the flag set actually changes, so a repeat addSocket(Writable | Closed) is a no-op. A short write on a response larger than the send buffer can therefore park in SendingHeaders or SendingBody. Draining until the socket says stop also makes the would-block branch reachable by a test, which it is not today under level-triggered readiness. Credit for this one goes to a reviewer of [BUG] Fix transient would-block handling in the ext/http embedded server #4283.
  • A fatal send() error never closes the connection. http_server.h#L344-L347 returns true on any non would-block error without closing the socket, removing the reactor registration or changing state. On Linux EPOLLOUT is registered without EPOLLET, so the writable callback can be re-entered continuously; on Windows there may simply be no further event. [BUG] Fix transient would-block handling in the ext/http embedded server #4283 fixes the would-block cases and deliberately leaves this one.
  • EINTR is not classified as retryable. An interrupted recv is treated as a peer close and an interrupted send leaves the connection stalled. socket_tools.h#L770 already handles EINTR for the poll loop, so there is a precedent to follow.
  • Response length is narrowed to int. http_server.h#L342 casts conn.sendBuffer.size() to int before passing it to a wrapper that takes unsigned. The response body comes from the handler, so m_maxRequestContentSize does not bound it. A body around 4 GiB can narrow to a length of 0, which makes send() return 0 and the buffer stop advancing.

Reactor (all platforms)

macOS reactor

  • Interest flags are ignored and EV_EOF is never processed. socket_tools.h#L567-L570 registers both EVFILT_READ and EVFILT_WRITE regardless of the requested flags, and #L627 carries a TODO saying flag updates are unsupported, so a socket registered only for reading is woken continually by writability and then does nothing. Separately, in the event loop the EVFILT_READ and EVFILT_WRITE branches each end in continue while the EV_EOF / EV_ERROR handling sits after them. Since kqueue only reports EV_EOF as a flag on one of those two filters, that close path, and the LOG_ERROR("Reactor: unhandled kevent!") below it, are unreachable.

Request framing

  • Ambiguous framing is accepted. Duplicate headers are joined with a comma at http_server.h#L672-L680, and the Content-Length parse at #L440-L451 tolerates trailing characters, so Content-Length: 5 followed by Content-Length: 999 parses as 5. Transfer-Encoding does not appear anywhere in the file, so it is neither honored nor rejected. RFC 9112 requires framing to be unambiguous. The trailing-character tolerance is mine from [CODE HEALTH] Fix clang-tidy bugprone-unchecked-string-to-number-conversion warnings #4216, so tightening it means revisiting that decision, and this is the item most likely to be a deliberate test-server simplification rather than a defect.

Build

Minor

  • ::tolower and ::toupper receive a plain char at http_server.h#L708, #L724 and #L733, which is undefined for negative values.

Possible follow-up, not a defect

A compile-only target that includes every installed ext header on Linux, macOS and Windows would have caught the build item above, and other installed headers with no in-repo includer are likely in the same position. Happy to propose one if that sounds useful.


I do not expect all of these to be accepted, and I would rather you closed some as working-as-intended than have me open PRs for them. If you use formal sub-issues I am happy for these to be attached that way; I do not have the permission to do it myself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions