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
+68
View File
@@ -87,6 +87,72 @@ void receiver(int fd)
}
}
/* Writes a CSTP header announcing a BODY_SIZE-byte body, but only
* BODY_SIZE/2 bytes of body, then closes the socket - simulating a
* proxy connection dropped mid-packet. A correct _cstp_recv_packet()
* must report this as an error, not as a successful, fully-populated
* packet built from a truncated buffer. */
#define NEG_BODY_SIZE 64
void neg_writer(int fd)
{
unsigned char buf[8 + NEG_BODY_SIZE] = { 0 };
buf[4] = (NEG_BODY_SIZE >> 8) & 0xff;
buf[5] = NEG_BODY_SIZE & 0xff;
assert(write(fd, buf, 8 + NEG_BODY_SIZE / 2) == 8 + NEG_BODY_SIZE / 2);
close(fd);
}
void neg_receiver(int fd)
{
worker_st ws = { 0 };
unsigned char buf[8 + NEG_BODY_SIZE];
int ret;
ws.conn_fd = fd;
ret = _cstp_recv_packet(&ws, buf, sizeof(buf));
if (verbose)
fprintf(stderr, "negative test received %d\n", ret);
if (ret > 0) {
fprintf(stderr,
"FAIL: expected error on truncated packet, got %d\n",
ret);
exit(1);
}
}
void run_negative_test(void)
{
int sockets[2];
pid_t child;
int status = 0;
assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) >= 0);
child = fork();
assert(child >= 0);
if (child) {
close(sockets[1]);
neg_receiver(sockets[0]);
wait(&status);
if (WEXITSTATUS(status) != 0) {
fprintf(stderr, "negative test child failed %d!\n",
(int)WEXITSTATUS(status));
exit(1);
}
} else {
close(sockets[0]);
neg_writer(sockets[1]);
exit(0);
}
}
int main(int argc, char *argv[])
{
int sockets[2];
@@ -116,5 +182,7 @@ int main(int argc, char *argv[])
return 0;
}
run_negative_test();
return 0;
}