H1: valid request rejected with 414 when its data end exactly at the end of a full input buffer · Issue #3540 · haproxy/haproxy · GitHub
Skip to content

H1: valid request rejected with 414 when its data end exactly at the end of a full input buffer #3540

Open

Description

@manu-ns

Detailed Description of the Problem

A valid request on a keep-alive connection is sometimes rejected with 414 URI Too Long. show errors shows the complete, well-formed request head at position 0, with len equal to wraps at and 56 bytes free:

  buffer starts at 0 (including 0 out), 56 free,
  len 33736, wraps at 33736, error at position 0
  H1 connection flags 0x00000020, H1 stream flags 0x00000890
  H1 msg state MSG_RQBEFORE(0), H1 msg flags 0x00001400
  00000  PUT /x HTTP/1.1\r\n

We see this in production on large PUTs from clients with sub-ms RTT.
From 1 ms of RTT upward it no longer reproduces.

Expected Behavior

The request is parsed and forwarded.

Steps to Reproduce the Behavior

Config:

global
    tune.bufsize 33792

defaults
    mode http
    timeout client 10s
    timeout server 10s
    timeout connect 5s

frontend fe
    bind 127.0.0.1:8000
    default_backend be

backend be
    server s1 127.0.0.1:8001

Script: it starts a backend on :8001, sends PUT #1 except its last 16
body bytes, waits, then sends those 16 bytes and PUT ##2 in one write:

import http.server, socket, threading, time

class H(http.server.BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def do_PUT(self):
        self.rfile.read(int(self.headers["Content-Length"]))
        self.send_response(200)
        self.send_header("Content-Length", "0")
        self.end_headers()
    def log_message(self, *a): pass

srv = http.server.ThreadingHTTPServer(("127.0.0.1", 8001), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()

def put(n, extra=""):
    return (f"PUT /x HTTP/1.1\r\nHost: a\r\nContent-Length: {n}\r\n"
            f"{extra}\r\n").encode() + b"x" * n

r1 = put(1000)
r2 = put(450000, "Connection: close\r\n")
s = socket.create_connection(("127.0.0.1", 8000))
s.sendall(r1[:-16])
time.sleep(0.2)
try:
    s.sendall(r1[-16:] + r2)
except OSError:
    pass  # HAProxy may close after its 414 while we are still sending
resp = b""
while d := s.recv(65536):
    resp += d
print(*[l.split(b"\r\n")[0].decode() for l in resp.split(b"HTTP/1.1 ")[1:]])

Output: 200 OK 414 URI Too Long on every run. With a 15- or 17-byte
tail it prints 200 OK 200 OK.

Do you have any idea what may have caused this?

h1_parse_msg_hdrs() realigns the input buffer only when head + data > size. Here the H1 mux reads into an empty ibuf, so head = sizeof(struct htx) = 40. It consumes the 16 tail bytes, so head = 56. Then it tops the buffer up to size - HTX_BUF_OVERHEAD while request #1 waits for its response. That leaves head + data == size exactly, so there is no realign. b_tail() wraps to the buffer origin, h1_headers_to_hdr_list() gets stop < start and returns 0, and since the buffer is full this is reported as headers-too-large (414 in RQBEFORE).

Do you have an idea how to solve the issue?

The data are contiguous after the realign check, so use the non-wrapping accessors:

--- a/src/h1_htx.c
+++ b/src/h1_htx.c
@@ int h1_parse_msg_hdrs(...)
-	ret = h1_headers_to_hdr_list(b_peek(srcbuf, ofs), b_tail(srcbuf),
+	ret = h1_headers_to_hdr_list(__b_peek(srcbuf, ofs), __b_tail(srcbuf),
 				     hdrs, sizeof(hdrs)/sizeof(hdrs[0]), h1m, h1sl);

With this change the reproducer gives 200 OK 200 OK on 3.4.6 and master. I'll send a patch to the ML.

What is your configuration?

See above (minimal reproducer).

Output of haproxy -vv

HAProxy version 3.4.6-0+ha34+ubuntu26.04u1 2026/09/28 - https://haproxy.org/
Running on: Linux 7.0.0-31-generic x86_64


Also reproduced on master (3.5-dev9, 069081792). The code involved is the same since at least 2.2.

Last Outputs and Backtraces


Additional Information

No response

Activity

  1. capflam commented on Oct 15, 2026

    @capflam
    Member

    Thanks for the report. I'm on a series of patches about H1 that includes this one I guess:

    0001-BUG-MEDIUM-h1-htx-don-t-stall-on-messages-ending-exa.patch.txt

    In my case, I was able to hit a timeout but it seems to be the same issue. It will be merged soon.

  2. added
    status: feedback requiredThe developers are waiting for a reply from the reporter.
    status: reviewedThis issue was reviewed. A fix is required.
    and removed on Oct 15, 2026
  3. manu-ns commented on Oct 15, 2026

    @manu-ns
    ContributorAuthor

    @capflam indeed, I can confirm this fixes our case! With the repro used in the issue body, it works with your patch. So you may want to add the 414 failure case in the commit message 🙂

    Thanks for the speedy response!

  4. capflam commented on Oct 15, 2026

    @capflam
    Member

    Cool, thanks. I will keep the issue open till the fix is merged. I will reference it in the commit message. Thanks !

  5. added
    status: fixedThis issue is a now-fixed bug.
    2.6This issue affects the HAProxy 2.6 stable branch.
    2.8This issue affects the HAProxy 2.8 stable branch.
    3.0This issue affects the HAProxy 3.0 stable branch.
    3.2The issue affects the HAProxy 3.2 stable branch.
    3.3The issue affects the HAProxy 3.3 stable branch.
    3.4This issue affects the HAProxy 3.4 stable branch.
    and removed
    status: reviewedThis issue was reviewed. A fix is required.
    on Oct 16, 2026
  6. capflam commented on Oct 16, 2026

    @capflam
    Member

    The fix was merged in DEV. Thanks !

  7. removed
    3.4This issue affects the HAProxy 3.4 stable branch.
    on Oct 20, 2026
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

    2.6This issue affects the HAProxy 2.6 stable branch.2.8This issue affects the HAProxy 2.8 stable branch.3.0This issue affects the HAProxy 3.0 stable branch.3.2The issue affects the HAProxy 3.2 stable branch.3.3The issue affects the HAProxy 3.3 stable branch.status: fixedThis issue is a now-fixed bug.type: bugThis issue describes a bug.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions