Bug 296316 - [libfetch] Fix POST upload truncation with fetchReqHTTP()
Summary: [libfetch] Fix POST upload truncation with fetchReqHTTP()
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: bin (show other bugs)
Version: 15.1-RELEASE
Hardware: Any Any
: --- Affects Only Me
Assignee: Dag-Erling Smørgrav
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2026-06-27 10:56 UTC by Bernhard Froehlich
Modified: 2026-07-21 19:33 UTC (History)
0 users

See Also:
des: mfc-stable15+
des: mfc-stable14+


Attachments
Fix for the described problem (364 bytes, patch)
2026-06-27 10:56 UTC, Bernhard Froehlich
no flags Details | Diff
Fix for the described problem (346 bytes, patch)
2026-06-27 11:57 UTC, Mark Linimon
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Bernhard Froehlich freebsd_committer freebsd_triage 2026-06-27 10:56:27 UTC
Created attachment 272175 [details]
Fix for the described problem

I noticed a regression in 15.1 that causes fetchReqHTTP() to get stuck when uploading a file via HTTPS POST request. It worked fine with FreeBSD 15.0. A small utility that I use was able to trigger that very reliable:

https://codeberg.org/decke/mrpt-upload/src/branch/main/mrpt-upload.c

I used Claude to analyze the truss output and generate a patch that works for me:


fetch_writev() in lib/libfetch/common.c uses a non-blocking socket with
a poll-and-write loop. The inner poll loop is gated on pfd.revents == 0,
but pfd.revents is never reset after a successful write. On subsequent
iterations the poll is skipped and SSL_write() is called immediately
without checking socket readiness. When the send buffer is full and
SSL_write() returns -1 with errno == EAGAIN, the error is not retried
— the function returns -1, silently truncating the request body.

For unencrypted connections this is less likely to trigger because
writev() operates on all iovec entries at once. For SSL, SSL_write()
operates on a single iovec per call, making short writes and a
subsequent EAGAIN much more likely with large request bodies.

The result observed with fetchReqHTTP() over HTTPS is that the server
receives a truncated POST body, returns an error response, closes its
end of the connection, and fetch_writev() then hangs polling for a
response that never arrives — until fetchTimeout fires.

Fix: In the wlen < 0 branch of fetch_writev(), handle EAGAIN by
resetting pfd.revents = 0 and continuing the loop, re-engaging the poll
before retrying SSL_write().
Comment 1 Mark Linimon freebsd_committer freebsd_triage 2026-06-27 11:57:36 UTC
Created attachment 272181 [details]
Fix for the described problem

^Triage: rebase.
Comment 2 Dag-Erling Smørgrav freebsd_committer freebsd_triage 2026-06-27 14:48:57 UTC
https://reviews.freebsd.org/D57906
Comment 3 commit-hook freebsd_committer freebsd_triage 2026-06-29 15:53:30 UTC
A commit in branch main references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=298f37a71ae6d9f2cb0c2abd4ff2887e81e0942c

commit 298f37a71ae6d9f2cb0c2abd4ff2887e81e0942c
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-06-27 12:16:31 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-06-29 13:49:19 +0000

    libfetch: Overhaul socket read / write

    * Make fetch_ssl_read() and fetch_ssl_write() behave more like read(2)
      and write(2), and drop fetch_socket_read() in favor of read(2).

    * Don't request POLLERR, it's implied.

    * Don't needlessly set errno, it's relatively costly.

    * Always check for EAGAIN from writev(2), otherwise we will abort on a
      short write instead of proceeding to poll(2).

    * Always check for EAGAIN from poll(2) even though it can't happen on
      FreeBSD; POSIX says it can, and it might in the future.

    * Rewrite fetch_read() and fetch_writev() to be more similar to each
      other.  The main difference is that a partial read is treated as
      success while a partial write is treated as failure.

    PR:             296316
    MFC after:      1 week

 lib/libfetch/common.c | 206 ++++++++++++++++++++++++++++++--------------------
 lib/libfetch/common.h |   4 +-
 2 files changed, 127 insertions(+), 83 deletions(-)
Comment 4 Dag-Erling Smørgrav freebsd_committer freebsd_triage 2026-06-29 16:20:32 UTC
Sorry, the patch was committed prematurely by accident and has been reverted.
Comment 5 commit-hook freebsd_committer freebsd_triage 2026-07-13 06:46:43 UTC
A commit in branch main references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=32c341bd1c8b1154128f62fafa6988ed29db564a

commit 32c341bd1c8b1154128f62fafa6988ed29db564a
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-07-13 06:43:28 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-07-13 06:43:28 +0000

    libfetch: Overhaul socket read / write

    * Make fetch_ssl_read() and fetch_ssl_write() behave more like read(2)
      and write(2), and drop fetch_socket_read() in favor of read(2).

    * Don't request POLLERR, it's implied.

    * Don't needlessly set errno, it's relatively costly.

    * Always check for EAGAIN from writev(2), otherwise we will abort on a
      short write instead of proceeding to poll(2).

    * Always check for EAGAIN from poll(2) even though it can't happen on
      FreeBSD; POSIX says it can, and it might in the future.

    * Rewrite fetch_read() and fetch_writev() to be more similar to each
      other.  The main difference is that a partial read is treated as
      success while a partial write is treated as failure.

    PR:             296316
    MFC after:      1 week
    Reviewed by:    op
    Differential Revision:  https://reviews.freebsd.org/D57906

 lib/libfetch/common.c | 206 ++++++++++++++++++++++++++++++--------------------
 lib/libfetch/common.h |   4 +-
 2 files changed, 127 insertions(+), 83 deletions(-)
Comment 6 commit-hook freebsd_committer freebsd_triage 2026-07-21 17:05:08 UTC
A commit in branch stable/15 references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=b56f3ea3b3f3a12d308b8ae22d4be1d9862929a7

commit b56f3ea3b3f3a12d308b8ae22d4be1d9862929a7
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-07-13 06:43:28 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-07-21 17:03:37 +0000

    libfetch: Overhaul socket read / write

    * Make fetch_ssl_read() and fetch_ssl_write() behave more like read(2)
      and write(2), and drop fetch_socket_read() in favor of read(2).

    * Don't request POLLERR, it's implied.

    * Don't needlessly set errno, it's relatively costly.

    * Always check for EAGAIN from writev(2), otherwise we will abort on a
      short write instead of proceeding to poll(2).

    * Always check for EAGAIN from poll(2) even though it can't happen on
      FreeBSD; POSIX says it can, and it might in the future.

    * Rewrite fetch_read() and fetch_writev() to be more similar to each
      other.  The main difference is that a partial read is treated as
      success while a partial write is treated as failure.

    PR:             296316
    MFC after:      1 week
    Reviewed by:    op
    Differential Revision:  https://reviews.freebsd.org/D57906

    (cherry picked from commit 32c341bd1c8b1154128f62fafa6988ed29db564a)

 lib/libfetch/common.c | 206 ++++++++++++++++++++++++++++++--------------------
 lib/libfetch/common.h |   4 +-
 2 files changed, 127 insertions(+), 83 deletions(-)
Comment 7 commit-hook freebsd_committer freebsd_triage 2026-07-21 17:05:08 UTC
A commit in branch stable/14 references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=edf0ba570acab945bdda0ea8291c0631ca427a21

commit edf0ba570acab945bdda0ea8291c0631ca427a21
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-07-13 06:43:28 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-07-21 17:04:27 +0000

    libfetch: Overhaul socket read / write

    * Make fetch_ssl_read() and fetch_ssl_write() behave more like read(2)
      and write(2), and drop fetch_socket_read() in favor of read(2).

    * Don't request POLLERR, it's implied.

    * Don't needlessly set errno, it's relatively costly.

    * Always check for EAGAIN from writev(2), otherwise we will abort on a
      short write instead of proceeding to poll(2).

    * Always check for EAGAIN from poll(2) even though it can't happen on
      FreeBSD; POSIX says it can, and it might in the future.

    * Rewrite fetch_read() and fetch_writev() to be more similar to each
      other.  The main difference is that a partial read is treated as
      success while a partial write is treated as failure.

    PR:             296316
    MFC after:      1 week
    Reviewed by:    op
    Differential Revision:  https://reviews.freebsd.org/D57906

    (cherry picked from commit 32c341bd1c8b1154128f62fafa6988ed29db564a)

 lib/libfetch/common.c | 206 ++++++++++++++++++++++++++++++--------------------
 lib/libfetch/common.h |   4 +-
 2 files changed, 127 insertions(+), 83 deletions(-)