tlslib: harden recv_remaining() against truncated CSTP reads

recv_remaining() is used only on the non-TLS CSTP path (a UNIX socket
proxying plaintext CSTP in front of ocserv). On a recv() failure or
peer close mid-read, it discarded the error and returned whatever
partial byte count it had accumulated so far. Since every caller only
checks "ret <= 0" and otherwise trusts the count as a complete read,
a truncated body could come back as a positive, non-zero total that
looked like success: _cstp_recv_packet() would then report the full
8+pktlen size to its caller with only part of the buffer actually
populated from the network, feeding stale/uninitialized bytes into
parse_cstp_data() as if they were received client data.

Make the contract unambiguous: recv_remaining() now returns either
exactly the requested byte count or a negative error - never a
partial positive count a caller could mistake for success.

Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
This commit is contained in:
Nikos Mavrogiannopoulos
2026-07-28 08:00:06 +02:00
parent d79eda6013
commit 879f723953
4 changed files with 105 additions and 4 deletions
+27
View File
@@ -427,6 +427,33 @@ activates for that session even though `udp-port` is globally configured;
(b) configure `udp-port = 0` and confirm the same.
**Links:** REQ-WORKER-SEC-003
### REQ-WORKER-NET-004 — Non-TLS CSTP packet reassembly reports failure on incomplete reads, never a truncated packet as success
**Requirement:** `_cstp_recv_packet()`, when `ws->session == NULL` (CSTP
carried in plaintext over a UNIX socket, e.g. terminated by a
TLS-terminating proxy in front of ocserv), MUST treat any `recv()` failure
or peer-closed condition encountered while assembling the 8-byte CSTP
header or the declared-length body as a hard error for that read, and MUST
NOT return a byte count implying a complete packet unless the full
requested length was actually received. `recv_remaining()` MUST return
either exactly the requested `left` byte count, or a negative error code
(`GNUTLS_E_PREMATURE_TERMINATION` on peer close, or the `recv()` error
otherwise) — it MUST NOT return a partial, positive byte count that a
caller's `ret <= 0` check would treat as success, since that would surface
stale/uninitialized buffer bytes to `parse_cstp_data` as if they were
received client data.
**Strength:** MUST
**Status:** DERIVED
**Source:** src/tlslib.c:158-183 (`recv_remaining`), src/tlslib.c:189-225
(`_cstp_recv_packet`)
**Acceptance:** negative, local, unit — `tests/cstp-recv.c`: a peer sends a
full 8-byte CSTP header declaring an N-byte body, writes fewer than N body
bytes, then closes the socket; confirm `_cstp_recv_packet()` returns a
negative value, not `8+N` as if the packet were fully received.
**Links:** REQ-PROTO-DATA-001 (this requirement supplies the precondition —
a truthful `buf_size` — that REQ-PROTO-DATA-001's length-mismatch check
depends on)
## Completeness notes
- **`worker-vpn.c` main loops** (`tls_mainloop`, `dtls_mainloop`,
+7 -1
View File
@@ -459,7 +459,13 @@ OC-PROTO itself does not mandate the close-on-violation behavior (it is silent);
OCSERV's choice (close) is the only behavior on record from any source, so there
is no competing variant — this is "MAJORITY of 1 source that addresses the
question at all."
**Links**: REQ-PROTO-CONN-006
**Note**: this requirement's `buf_size != 8 + pktlen` check can only detect a
truncated body if `buf_size` (the worker's `data.size`, ultimately the return
value of `_cstp_recv_packet`/`recv_remaining` in `src/tlslib.c`) truthfully
reflects the number of bytes actually read from the socket — see
REQ-WORKER-NET-004, which makes that precondition hold for the non-TLS
(UNIX-socket-proxied) CSTP path.
**Links**: REQ-PROTO-CONN-006, REQ-WORKER-NET-004
### REQ-PROTO-DATA-002
**Requirement**: The CSTP/DTLS payload type byte MUST be one of `0x00` (DATA),