2 Commits
Author SHA1 Message Date
TapTap 334fc5b3e8 fix(protocol): harden STATUS_ERROR_DETAIL receive path
Address review/security findings in the 2.21.0 error-detail feature:

- Keepalive drain no longer erases the terminal detail: capture/clear is
  skipped for STATUS_KEEPALIVE so the reason the peer just sent survives the
  owed keepalive replies.
- Replace the capture path with a dedicated protocol_receive_error_detail:
  the declared length is validated against MAX_ERROR_DETAIL_BYTES before any
  allocation, over-cap bodies are drained through a fixed scratch buffer (so
  the stream never desyncs), in-cap bodies read straight into the thread-local
  detail buffer, and session->max_alloc is never raised.  Lengths beyond
  MAX_STRING_SIZE are treated as a fatal framing error.
- The detail body now honors the caller's deadline (timed/keepalive paths) and
  polls the abort callback between drain chunks.
- Escape peer-controlled detail text with output_escape before logging it in
  client_send.c and config.c.
- Clear io_error_detail in io_set_fds so a new connection on the same thread
  cannot inherit a stale reason.
- Add unit tests for the keepalive-survival, over-cap drain, absurd-length
  fatal framing, and deadline-clamped body read cases.
2026-09-13 12:40:03 +02:00
TapTap 88aee6ce94 feat(protocol): add optional STATUS_ERROR_DETAIL rejection reason (2.21.0)
Today a server rejection sends a bare STATUS_ERROR and the reason only
reaches the server log, so the client cannot say why a transfer was
refused.  Add an optional, bounded server->client error-detail frame:

  - Status gains STATUS_ERROR_DETAIL appended LAST so existing wire
    values are unchanged.
  - send_error_detail(fd, msg) sends STATUS_ERROR_DETAIL followed by the
    existing length-prefixed string primitive, slicing over-long messages
    to MAX_ERROR_DETAIL_BYTES (4096).
  - receive_status() (and the timed/keepalive status readers) always
    consume the detail body and map the status back to STATUS_ERROR,
    capturing the text into a thread-local buffer exposed by
    protocol_last_error(); a bare STATUS_ERROR leaves it cleared.  Every
    existing call site keeps working and the stream cannot desync.
  - Upgrade the daemon module gate / config validation (config.c), the
    final transfer failure (server.c) and receiver-side path/node
    validation (file_receive.c) to send a concrete reason; surface it on
    the client in client_send.c/config.c.
  - Bump PROTOCOL_VERSION to 2.21.0 (CMake VERSION, CHANGELOG, docs) and
    update the pinned config wire golden hash / CLI-version tests.
  - Add tests/test_protocol_error.c covering mapping+capture, the
    over-long bound, bare-error clearing, and thread-locality.
2026-09-13 12:19:46 +02:00