mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-08-09 09:51:49 +08:00
Commit 5cf457b4 ("Removed the listen-clear-file config option", Dec
2020) deleted the only code path that could ever construct a
SOCK_TYPE_UNIX worker connection, but left every branch that handled
it in place. Since then ws->session has been unconditionally non-NULL
and ws->conn_type unconditionally not SOCK_TYPE_UNIX for every worker,
making all of the following unreachable:
- tlslib.c: recv_remaining(), _cstp_recv_packet() (the non-TLS CSTP
reassembly path hardened in the previous commit), and
tls_has_session_cert() (zero callers) deleted outright; cstp_cork/
cstp_uncork/cstp_send/cstp_recv_packet/cstp_recv/cstp_close/
cstp_fatal_close collapsed to their TLS-only body.
- worker-http.c, worker-auth.c, worker-vpn.c, main.c: dead
ws->session == NULL / ws->conn_type == SOCK_TYPE_UNIX branches
removed or simplified to their live half.
- worker-proxyproto.c: parse_ssl_tlvs() and its TLV structs/macros
removed. This proxy-protocol SSL-CN extraction was itself only
ever invoked from the same dead SOCK_TYPE_UNIX branch, so it has
been as unreachable as the rest since 2020 despite a recent,
otherwise-correct bug fix.
- tests/cstp-recv.c deleted (exercised only the removed reassembly
path); tests/proxyproto-v2.c trimmed to the still-live IPv6
address-parsing regression test.
- doc/sample.config: dropped the stale "TCP or UNIX socket" wording
for listen-proxy-proto left over from the same 2020 removal; only
a TCP socket is ever listened on for the proxy protocol now.
Narrows the worker's attack surface to the code paths a client can
actually reach, and removes a source of wasted maintenance effort.
Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
196 lines
5.1 KiB
C
196 lines
5.1 KiB
C
/*
|
|
* Copyright (C) 2024 Nikos Mavrogiannopoulos
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Unit test for proxy protocol v2 IPv6 address block parsing. */
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
#define UNDER_TEST
|
|
#define force_read_timeout(fd, buf, count, time) read(fd, buf, count)
|
|
#include "../src/worker-proxyproto.c"
|
|
|
|
/* Proxy protocol v2 signature (12 bytes) */
|
|
static const uint8_t PP2_SIG[] =
|
|
"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
|
|
|
|
/*
|
|
* Build a proxy protocol v2 packet with an IPv6 address block into buf[].
|
|
* Returns the total number of bytes written.
|
|
*/
|
|
static size_t build_v2_ipv6_packet(uint8_t *buf, size_t bufsz,
|
|
const struct in6_addr *src_addr,
|
|
uint16_t src_port,
|
|
const struct in6_addr *dst_addr,
|
|
uint16_t dst_port)
|
|
{
|
|
size_t pos = 0;
|
|
uint16_t sp, dp, plen;
|
|
|
|
memcpy(buf + pos, PP2_SIG, 12);
|
|
pos += 12;
|
|
buf[pos++] = 0x21; /* ver=2, PROXY command */
|
|
buf[pos++] = 0x21; /* AF_INET6 (0x2) | TCP (0x1) */
|
|
|
|
size_t len_offset = pos;
|
|
buf[pos++] = 0;
|
|
buf[pos++] = 0;
|
|
|
|
size_t payload_start = pos;
|
|
|
|
/* IPv6 address block (36 bytes):
|
|
* src IP (16 bytes)
|
|
* dst IP (16 bytes)
|
|
* src port (2 bytes, network order)
|
|
* dst port (2 bytes, network order)
|
|
*/
|
|
memcpy(buf + pos, src_addr, 16);
|
|
pos += 16;
|
|
memcpy(buf + pos, dst_addr, 16);
|
|
pos += 16;
|
|
sp = htons(src_port);
|
|
dp = htons(dst_port);
|
|
memcpy(buf + pos, &sp, 2);
|
|
pos += 2;
|
|
memcpy(buf + pos, &dp, 2);
|
|
pos += 2;
|
|
|
|
plen = (uint16_t)(pos - payload_start);
|
|
buf[len_offset] = (plen >> 8) & 0xff;
|
|
buf[len_offset + 1] = plen & 0xff;
|
|
|
|
assert(pos <= bufsz);
|
|
return pos;
|
|
}
|
|
|
|
/*
|
|
* Feed pkt into a pipe and call parse_proxy_proto_header.
|
|
* ws->conn_type must be set by the caller.
|
|
*/
|
|
static int run_parse(struct worker_st *ws, const uint8_t *pkt, size_t pkt_len)
|
|
{
|
|
int fds[2];
|
|
int ret;
|
|
|
|
if (pipe(fds) < 0) {
|
|
perror("pipe");
|
|
return -1;
|
|
}
|
|
|
|
/* write all bytes; pipe buffer is large enough for our small packets */
|
|
if (write(fds[1], pkt, pkt_len) != (ssize_t)pkt_len) {
|
|
perror("write");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
return -1;
|
|
}
|
|
close(fds[1]);
|
|
|
|
ret = parse_proxy_proto_header(ws, fds[0]);
|
|
close(fds[0]);
|
|
return ret;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
uint8_t pkt[256];
|
|
size_t pkt_len;
|
|
struct worker_st ws = { 0 };
|
|
int ret;
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* IPv6 proxy protocol v2 — both remote and local addr correct */
|
|
/* */
|
|
/* Reproducer for the bug where sa->sin6_family was assigned before sa */
|
|
/* was redirected to &ws->our_addr, leaving our_addr.sin6_family == 0. */
|
|
/* ------------------------------------------------------------------ */
|
|
{
|
|
struct in6_addr src6, dst6;
|
|
struct sockaddr_in6 *rem6, *loc6;
|
|
|
|
memset(&ws, 0, sizeof(ws));
|
|
ws.conn_type = SOCK_TYPE_TCP;
|
|
|
|
inet_pton(AF_INET6, "2001:db8::1", &src6);
|
|
inet_pton(AF_INET6, "2001:db8::2", &dst6);
|
|
|
|
pkt_len = build_v2_ipv6_packet(pkt, sizeof(pkt), &src6, 1234,
|
|
&dst6, 443);
|
|
ret = run_parse(&ws, pkt, pkt_len);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "parse failed (%d)\n", ret);
|
|
return 1;
|
|
}
|
|
|
|
if (ws.remote_addr_len != sizeof(struct sockaddr_in6)) {
|
|
fprintf(stderr, "remote_addr_len wrong (%u)\n",
|
|
ws.remote_addr_len);
|
|
return 1;
|
|
}
|
|
if (ws.our_addr_len != sizeof(struct sockaddr_in6)) {
|
|
fprintf(stderr, "our_addr_len wrong (%u)\n",
|
|
ws.our_addr_len);
|
|
return 1;
|
|
}
|
|
|
|
rem6 = (void *)&ws.remote_addr;
|
|
loc6 = (void *)&ws.our_addr;
|
|
|
|
if (rem6->sin6_family != AF_INET6) {
|
|
fprintf(stderr, "remote sin6_family wrong: %d\n",
|
|
rem6->sin6_family);
|
|
return 1;
|
|
}
|
|
/* This check catches the bug: sin6_family was written to
|
|
* remote_addr a second time instead of to our_addr. */
|
|
if (loc6->sin6_family != AF_INET6) {
|
|
fprintf(stderr,
|
|
"local sin6_family wrong: %d (expected %d)\n",
|
|
loc6->sin6_family, AF_INET6);
|
|
return 1;
|
|
}
|
|
|
|
if (memcmp(&rem6->sin6_addr, &src6, 16) != 0) {
|
|
fprintf(stderr, "remote address mismatch\n");
|
|
return 1;
|
|
}
|
|
if (memcmp(&loc6->sin6_addr, &dst6, 16) != 0) {
|
|
fprintf(stderr, "local address mismatch\n");
|
|
return 1;
|
|
}
|
|
if (ntohs(rem6->sin6_port) != 1234) {
|
|
fprintf(stderr, "remote port wrong: %u\n",
|
|
ntohs(rem6->sin6_port));
|
|
return 1;
|
|
}
|
|
if (ntohs(loc6->sin6_port) != 443) {
|
|
fprintf(stderr, "local port wrong: %u\n",
|
|
ntohs(loc6->sin6_port));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|