From 5ad1ea1e257f17e481b5b539a5820fcda9083aed Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 6 Apr 2025 11:46:19 +0200 Subject: [PATCH 01/21] complete changelog for v2.3.0 --- ChangeLog | 11 +++++++++-- echosrv-conf.c | 2 +- echosrv-conf.h | 2 +- sslh-conf.c | 2 +- sslh-conf.h | 2 +- version.h | 2 +- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index f096723..5c49bb7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,18 @@ -vNEXT: +v2.2.0: Added a boolean setting "is_unix" for listen and protocol entries. This will use the 'host' setting as a path name to a socket file, and connections (listening or connecting) will be performed on Unix socket instead of Internet sockets. - Support proxyprotocol on the backend server side. + Support HAProxy's proxyprotocol on the backend + server side. + + Lots of documentation about a new, simpler way to + perform transparent proxying. + + New "verbose" option that overrides all other + verbose settings. v2.1.3: Fix Landlock access to /etc/hosts.deny and diff --git a/echosrv-conf.c b/echosrv-conf.c index 8747a31..a46ee1e 100644 --- a/echosrv-conf.c +++ b/echosrv-conf.c @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Mon Feb 24 18:37:24 2025. + * on Sun Apr 6 11:44:59 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle diff --git a/echosrv-conf.h b/echosrv-conf.h index 1436146..de4b439 100644 --- a/echosrv-conf.h +++ b/echosrv-conf.h @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Mon Feb 24 18:37:24 2025. + * on Sun Apr 6 11:44:59 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle diff --git a/sslh-conf.c b/sslh-conf.c index a7edc8f..325e300 100644 --- a/sslh-conf.c +++ b/sslh-conf.c @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Mon Feb 24 18:37:24 2025. + * on Sun Apr 6 11:44:58 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle diff --git a/sslh-conf.h b/sslh-conf.h index 55c63e9..86058f4 100644 --- a/sslh-conf.h +++ b/sslh-conf.h @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Mon Feb 24 18:37:24 2025. + * on Sun Apr 6 11:44:58 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle diff --git a/version.h b/version.h index 39a8717..dcf9f0b 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "v2.1.4-40-g416a82f-dirty" +#define VERSION "v2.1.4-42-g4978641-dirty" #endif From 0e7885bc9f4db1c3cde2b9c41ef227465db8158c Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 6 Apr 2025 11:59:08 +0200 Subject: [PATCH 02/21] fix compilation when libproxyprotocol is not present --- proxyprotocol.c | 4 ++++ proxyprotocol.h | 2 +- version.h | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/proxyprotocol.c b/proxyprotocol.c index aab218c..f609aa1 100644 --- a/proxyprotocol.c +++ b/proxyprotocol.c @@ -20,6 +20,8 @@ */ +#ifdef HAVE_PROXYPROTOCOL + #include #include "common.h" #include "log.h" @@ -112,3 +114,5 @@ int pp_write_header(int pp_version, struct connection* cnx) return 0; } + +#endif /* HAVE_PROXYPROTOCOL */ diff --git a/proxyprotocol.h b/proxyprotocol.h index fab0157..46a1b69 100644 --- a/proxyprotocol.h +++ b/proxyprotocol.h @@ -8,7 +8,7 @@ int pp_write_header(int pp_version, struct connection* cnx); #else /* HAVE_PROXYPROTOCOL */ -static inline int pp_write_header(int pp_version, struct connection* cnx) {} +static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; } #endif /* HAVE_PROXYPROTOCOL */ diff --git a/version.h b/version.h index dcf9f0b..5bb09ef 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "v2.1.4-42-g4978641-dirty" +#define VERSION "v2.2.0-dirty" #endif From 1f462ba1669aede6257edc4c6cee08823f6abb8b Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 6 Apr 2025 11:59:51 +0200 Subject: [PATCH 03/21] update tag to v2.2.1 --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 5c49bb7..25043e5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,4 @@ -v2.2.0: +v2.2.1: Added a boolean setting "is_unix" for listen and protocol entries. This will use the 'host' setting as a path name to a socket file, and connections From e0bcf282ff0860503da0bee387d4718ac376f56a Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 8 Apr 2025 21:36:12 +0200 Subject: [PATCH 04/21] include config.h. unclear how this worked without that --- proxyprotocol.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxyprotocol.c b/proxyprotocol.c index f609aa1..a7d7004 100644 --- a/proxyprotocol.c +++ b/proxyprotocol.c @@ -20,6 +20,8 @@ */ +#include "config.h" + #ifdef HAVE_PROXYPROTOCOL #include From eccf7dbdc4e95f6684aacc88adeed8b1950e86f0 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Mon, 7 Apr 2025 14:10:19 +0800 Subject: [PATCH 05/21] common.h: add a declaration of hosts_ctl for macOS Fixes: https://github.com/yrutschle/sslh/issues/492 --- common.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common.h b/common.h index 3503a95..7243104 100644 --- a/common.h +++ b/common.h @@ -34,6 +34,10 @@ #include #endif +#ifdef __APPLE__ +#include +#endif + #include "config.h" #include "version.h" @@ -182,6 +186,10 @@ extern struct sslhcfg_item cfg; extern struct addrinfo *addr_listen; extern const char* server_type; +#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080) +extern int hosts_ctl(); +#endif + /* sslh-fork.c */ void start_shoveler(int); From 0fe9bd5a956a123342ff12352b25bff8025dac69 Mon Sep 17 00:00:00 2001 From: npt-1707 Date: Tue, 15 Apr 2025 05:01:07 +0800 Subject: [PATCH 06/21] common.c: Fix that symlink does not interferer --- common.c | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/common.c b/common.c index 4545560..22c81a3 100644 --- a/common.c +++ b/common.c @@ -989,23 +989,44 @@ void drop_privileges(const char* user_name, const char* chroot_path) /* Writes my PID */ void write_pid_file(const char* pidfile) { - FILE *f; - int res; + int fd; + char pidbuf[32]; + size_t len, written = 0; + ssize_t res; - f = fopen(pidfile, "w"); - if (!f) { + /* Format PID as string */ + len = snprintf(pidbuf, sizeof(pidbuf), "%d\n", getpid()); + if (len >= sizeof(pidbuf)) { + print_message(msg_system_error, "write_pid_file: PID string too long\n"); + return; + } + + /* Open file with O_NOFOLLOW to prevent symlink attacks */ + fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC +#ifdef O_NOFOLLOW + | O_NOFOLLOW +#endif + ,0644); + + if (fd == -1) { print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno)); return; } - res = fprintf(f, "%d\n", getpid()); - if (res < 0) { - print_message(msg_system_error, "write_pid_file: fprintf: %s\n", strerror(errno)); + /* Write PID to file with proper error handling */ + while (written < len) { + res = write(fd, pidbuf + written, len - written); + if (res == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + print_message(msg_system_error, "write_pid_file: write: %s\n", strerror(errno)); + break; + } + written += res; } - res = fclose(f); - if (res == EOF) { - print_message(msg_system_error, "write_pid_file: fclose: %s\n", strerror(errno)); - return; + /* Close file */ + if (close(fd) == -1) { + print_message(msg_system_error, "write_pid_file: close: %s\n", strerror(errno)); } } From c0cc45975c39e5a13f704958b14eb4e12496e0bc Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 15 Apr 2025 22:27:27 +0200 Subject: [PATCH 07/21] clean up inline define --- common.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common.c b/common.c index 22c81a3..db97490 100644 --- a/common.c +++ b/common.c @@ -986,6 +986,11 @@ void drop_privileges(const char* user_name, const char* chroot_path) } } + +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif + /* Writes my PID */ void write_pid_file(const char* pidfile) { @@ -1001,12 +1006,8 @@ void write_pid_file(const char* pidfile) return; } - /* Open file with O_NOFOLLOW to prevent symlink attacks */ - fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC -#ifdef O_NOFOLLOW - | O_NOFOLLOW -#endif - ,0644); + /* Open file with O_NOFOLLOW to prevent symlink attacks (Similar to CVE-2020-28935) */ + fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW ,0644); if (fd == -1) { print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno)); From 8fb62445c4fcf3efbe0a5180d61044c2b331b88f Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 4 May 2025 11:28:11 +0200 Subject: [PATCH 08/21] v2.2.2 ChangeLog --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index 25043e5..b3bfb4e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +v2.2.2: + Fix potential vulnerability similar to CVE-2020-28935 + v2.2.1: Added a boolean setting "is_unix" for listen and protocol entries. This will use the 'host' setting From b2bcfc26b290d309d00c19aca1f72d623d26449a Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 4 May 2025 11:33:58 +0200 Subject: [PATCH 09/21] version.h --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 5bb09ef..f5779c4 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "v2.2.0-dirty" +#define VERSION "v2.2.2-dirty" #endif From 6e6d94752a5f4d99f1980cfae00485efb71df7b8 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 4 May 2025 11:45:33 +0200 Subject: [PATCH 10/21] Revert "include version.h in repo" This reverts commit acdbb79d43ca867ccc86a340d9cc90858e38059f. --- .gitignore | 1 + Makefile.in | 4 +--- version.h | 5 ----- 3 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 version.h diff --git a/.gitignore b/.gitignore index 6b9d908..493642b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ sslh-ev systemd-sslh-generator sslh.8.gz tags +version.h /config.status /config.log /config.h diff --git a/Makefile.in b/Makefile.in index f614984..96f9656 100644 --- a/Makefile.in +++ b/Makefile.in @@ -70,9 +70,8 @@ all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS) $(OBJS_A): $(OBJS) $(AR) rcs $(OBJS_A) $(OBJS) -version.h: .FORCE +version.h: ./genver.sh >version.h -.FORCE: $(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h @@ -145,7 +144,6 @@ distclean: clean clean: rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info - echo "// this is a placeholder for version.h, to make code-checking editors happy" > version.h tags: ctags --globals -T *.[ch] diff --git a/version.h b/version.h deleted file mode 100644 index 5bb09ef..0000000 --- a/version.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef VERSION_H -#define VERSION_H - -#define VERSION "v2.2.0-dirty" -#endif From b5d4d4662bdc188b665a6ee05443698c014829c3 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 4 May 2025 11:47:44 +0200 Subject: [PATCH 11/21] v2.2.3 ChangeLog --- ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index b3bfb4e..d842658 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +v2.2.3: + Reverse older commit: version.h cannot be included + without breaking the build (everything recompiles + every time) and the release archive creation (which + relies on git tags). + v2.2.2: Fix potential vulnerability similar to CVE-2020-28935 From 8298daf686901d2d2b1895c97cdbafc019df20c2 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 6 May 2025 22:12:29 +0200 Subject: [PATCH 12/21] change magic numbers for constants --- probe.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/probe.c b/probe.c index 00a6137..cf55833 100644 --- a/probe.c +++ b/probe.c @@ -367,12 +367,12 @@ static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protoco int res, i, j; res = sscanf(p, "<%d>", &i); - if (res == 1) return 1; + if (res == 1) return PROBE_MATCH; res = sscanf(p, "%d <%d>", &i, &j); - if (res == 2) return 1; + if (res == 2) return PROBE_MATCH; - return 0; + return PROBE_NEXT; } static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) From ff8206f7c8a47f901b78a1b78db5a4c788f6aa6f Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 8 May 2025 17:58:51 +0200 Subject: [PATCH 13/21] fix segmentation fault upon too many UDP connection. Also, accepts multiple connection in one cycle --- sslh-select.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sslh-select.c b/sslh-select.c index acac6eb..5ee679e 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -156,13 +156,15 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) /* Check main socket for new connections */ for (i = 0; i < num_addr_listen; i++) { if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) { - struct connection* new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]); - - if (fd_out_of_range(new_cnx->q[0].fd)) - tidy_connection(new_cnx, &fd_info); - /* don't also process it as a read socket */ FD_CLR(listen_sockets[i].socketfd, &readfds); + + struct connection* new_cnx; + while ((new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]))) { + if (fd_out_of_range(new_cnx->q[0].fd)) + tidy_connection(new_cnx, &fd_info); + } + } } From ad1f5d68e96eec389668d1139cb281b1f3f13725 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Fri, 9 May 2025 17:08:30 +0200 Subject: [PATCH 14/21] fix potential parsing of undefined data is syslog probe and make probe more robust --- probe.c | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/probe.c b/probe.c index cf55833..61f2cb7 100644 --- a/probe.c +++ b/probe.c @@ -25,6 +25,7 @@ #define PCRE2_CODE_UNIT_WIDTH 8 #include #endif +#include #include #include "probe.h" #include "log.h" @@ -362,17 +363,38 @@ static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_prot return PROBE_MATCH; } +/* ****************** + * is_syslog_protocol + * */ +static regex_t syslog_preg; +static int configured_syslog_regex = 0; + +static void config_syslog_regex(void) +{ + /* two patterns for syslog messages: + * <12> My message + * 15 <12> My message + * 12 is 'priority', 1 to 3 digits (RFC4234) + * 15 is 'message length', a TCP-only option (RFC6587) + */ + int res = regcomp(&syslog_preg, "^([0-9]{1,3} )?<[0-9]{1,3}>", REG_EXTENDED); + if (res) { + print_message(msg_system_error, "regcomp"); + exit(1); + } + configured_syslog_regex = 1; +} + static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { - int res, i, j; + char buf[len+1]; - res = sscanf(p, "<%d>", &i); - if (res == 1) return PROBE_MATCH; + if (!configured_syslog_regex) config_syslog_regex(); - res = sscanf(p, "%d <%d>", &i, &j); - if (res == 2) return PROBE_MATCH; + strncpy(buf, p, len); + buf[len] = 0; - return PROBE_NEXT; + return (regexec(&syslog_preg, buf, (size_t)0, NULL, 0) == 0); } static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) From 43e75a0a8c6b50f6ad57fc812bde9e3b7b2727f4 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 22 May 2025 21:14:18 +0200 Subject: [PATCH 15/21] upgrade API to Conf::Libconfig 1.0.3 --- t | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/t b/t index 63137cb..3e6ddf5 100755 --- a/t +++ b/t @@ -9,7 +9,7 @@ use strict; use IO::Socket::INET6; use Test::More qw/no_plan/; -use Conf::Libconfig; +use Conf::Libconfig 1.0.3; my $conf = new Conf::Libconfig; $conf->read_file("test.cfg"); @@ -17,7 +17,7 @@ $conf->read_file("test.cfg"); my $no_listen = 8083; # Port on which no-one listens my $pidfile = $conf->lookup_value("pidfile"); -my $sslh_port = $conf->fetch_array("listen")->[0]->{port}; +my $sslh_port = $conf->value("listen")->[0]->{port}; my $user = (getpwuid $<)[0]; # Run under current username # Which tests do we run @@ -119,7 +119,7 @@ sub test_probe { sub test_probes { my (%in_opts) = @_; - my @probes = @{$conf->fetch_array("protocols")}; + my @probes = @{$conf->value("protocols")}; foreach my $p (@probes) { my %protocols = ( 'ssh' => { data => "SSH-2.0 tester" }, @@ -194,7 +194,7 @@ sub test_probes { # Start an echoserver for each service -foreach my $s (@{$conf->fetch_array("protocols")}) { +foreach my $s (@{$conf->value("protocols")}) { my $prefix = $s->{name}; $prefix =~ s/^ssl/tls/; # To remove in 1.21 @@ -339,11 +339,11 @@ if ($RB_CNX_NOSERVER) { } -my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->fetch_array("protocols")})[0]; +my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->value("protocols")})[0]; my $ssh_address = $ssh_conf->{host} . ":" . $ssh_conf->{port}; # Use the last TLS echoserv (no SNI/ALPN) -my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->fetch_array("protocols")})[-1]; +my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->value ("protocols")})[-1]; my $ssl_address = $ssl_conf->{host} . ":" . $ssl_conf->{port}; From 0f96ed8adbce977703dbfab05a1ed603f98568ee Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 25 May 2025 11:27:05 +0200 Subject: [PATCH 16/21] fix test script for new command line behaviour --- t | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t b/t index 3e6ddf5..c54b1f8 100755 --- a/t +++ b/t @@ -84,8 +84,9 @@ sub make_sni_alpn_name { sub test_probe { my (%opts) = @_; + print "test_probe [$opts{expected}] $sslh_port\n"; my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); - warn "$!\n" unless $cnx; + warn "t: $!\n" unless $cnx; return unless $cnx; my $pattern = $opts{data}; @@ -217,7 +218,7 @@ for my $binary (@binaries) { my ($sslh_pid, $valgrind); if (!($sslh_pid = fork)) { my $user = (getpwuid $<)[0]; # Run under current username - my $cmd = "./$binary -v 4 -f -u $user -F test.cfg"; + my $cmd = "./$binary -u $user -F test.cfg"; #$valgrind = 1; #$cmd = "valgrind --leak-check=full $cmd"; verbose_exec $cmd; From 204305a88fb32cffaf1349253a2b052186ca8d39 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 25 May 2025 11:27:26 +0200 Subject: [PATCH 17/21] fix unaligned read in OpenVPN UDP probe --- Makefile.in | 2 +- probe.c | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Makefile.in b/Makefile.in index 96f9656..adc3470 100644 --- a/Makefile.in +++ b/Makefile.in @@ -22,7 +22,7 @@ MAN=sslh.8.gz # man page name # itself ifneq ($(strip $(ENABLE_SANITIZER)),) - CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined + CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment endif ifneq ($(strip $(COV_TEST)),) diff --git a/probe.c b/probe.c index 61f2cb7..37f9bb6 100644 --- a/probe.c +++ b/probe.c @@ -147,6 +147,7 @@ static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_ #define OVPN_OPCODE_MASK 0xF8 #define OVPN_CONTROL_HARD_RESET_CLIENT_V1 (0x01 << 3) #define OVPN_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3) +#define OVPN_CONTROL_HARD_RESET_CLIENT_V3 (0x0A << 3) #define OVPN_HMAC_128 16 #define OVPN_HMAC_160 20 #define OVPN_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size) @@ -165,8 +166,12 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco if (len < 1) return PROBE_NEXT; + printf("opcode: %d\n", (p[0] & OVPN_OPCODE_MASK) >> 3); + if ((p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V1 && - (p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2) + (p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2 && + (p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V3 + ) return PROBE_NEXT; /* The detection pattern above may not be reliable enough. @@ -177,12 +182,18 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128) + sizeof(uint32_t)) return PROBE_NEXT; - if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))) <= 5u) + uint32_t i; + /* OVPN_HMAC_128 is unaligned, which requires special care e.g. on ARM */ + memcpy(&i, (p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128)), sizeof(i)); + i = ntohl(i); + if (i <= 5u) return PROBE_MATCH; if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160) + sizeof(uint32_t)) return PROBE_NEXT; + memcpy(&i, (p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160)), sizeof(i)); + i = ntohl(i); if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))) <= 5u) return PROBE_MATCH; From b12220e64097d32aa93814b2894e87b9b29f641c Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Wed, 28 May 2025 17:51:04 +0200 Subject: [PATCH 18/21] v2.2.4 ChangeLog --- ChangeLog | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ChangeLog b/ChangeLog index d842658..fa75b28 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +v2.2.4: + Fix CVE-2025-46806 for "Misaligned Memory Accesses in `is_openvpn_protocol()`" + + Fix CVE-2025-46807 for "File Descriptor Exhaustion in sslh-select and sslh-ev" + + Fix potential parsing of undefined data in syslog + probe (no CVE assigned) + + Thanks to Matthias Gerstner of the SUSE security + team for the security review that found these + defects! + v2.2.3: Reverse older commit: version.h cannot be included without breaking the build (everything recompiles From ad66e79f46c1a96b8692e6340d427efa91551cad Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 29 May 2025 15:14:28 +0200 Subject: [PATCH 19/21] Fix proxyprotocol target field (#496) --- ChangeLog | 3 +++ proxyprotocol.c | 30 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index fa75b28..e4bee24 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +vNEXT: + Fix proxyprotocol target field (Thanks to Github user 404-Not-Found) + v2.2.4: Fix CVE-2025-46806 for "Misaligned Memory Accesses in `is_openvpn_protocol()`" diff --git a/proxyprotocol.c b/proxyprotocol.c index a7d7004..6c19035 100644 --- a/proxyprotocol.c +++ b/proxyprotocol.c @@ -50,22 +50,38 @@ static int family_to_pp(int af_family) typedef char libpp_addr[108]; /* This is hardcoded in libproxyprotocol/proxy_protocol.h */ + +typedef enum { + PEER, + SOCK +} sockpeer_t; + /* Fills *addr, *host and *serv with the connection information corresponding - * to fd. *host is the IP address as string and *serv is the service (port) + * to fd. sockpeer indicates if if is filled with the remote, or local, part of + * the socket. + * *host is the IP address as string and *serv is the service (port) * */ -static int get_info(int fd, struct addrinfo* addr, libpp_addr* host, uint16_t* serv) +static int get_info(int fd, sockpeer_t sockpeer, struct addrinfo* addr, libpp_addr* host, uint16_t* serv) { char serv_str[NI_MAXSERV]; int res; - res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen); - CHECK_RES_RETURN(res, "getpeername", -1); + if (sockpeer == PEER) { + res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen); + CHECK_RES_RETURN(res, "getpeername", -1); + } else { + res = getsockname(fd, addr->ai_addr, &addr->ai_addrlen); + CHECK_RES_RETURN(res, "getsockname", -1); + } res = getnameinfo(addr->ai_addr, addr->ai_addrlen, (char*)host, sizeof(*host), serv_str, sizeof(serv_str), NI_NUMERICHOST | NI_NUMERICSERV ); - CHECK_RES_RETURN(res, "getnameinfo", -1); + if (res != 0) { + print_message(msg_system_error, "getnameinfo: %s\n", gai_strerror(res)); + return -1; + } *serv = atoi(serv_str); @@ -89,14 +105,14 @@ int pp_write_header(int pp_version, struct connection* cnx) addr.ai_addr = (struct sockaddr*)&ss; addr.ai_addrlen = sizeof(ss); - res = get_info(cnx->q[0].fd, + res = get_info(cnx->q[0].fd, PEER, &addr, &pp_info_in_v1.src_addr, &pp_info_in_v1.src_port); if (res == -1) return -1; pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family); - res = get_info(cnx->q[1].fd, + res = get_info(cnx->q[1].fd, SOCK, &addr, &pp_info_in_v1.dst_addr, &pp_info_in_v1.dst_port From 6b9ec3a46ee2cb0602334f35c525d7130d855b4f Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 29 May 2025 15:46:08 +0200 Subject: [PATCH 20/21] remove obsolete getopt definitions (fix #489) --- echosrv.c | 4 ---- sslh-main.c | 3 --- 2 files changed, 7 deletions(-) diff --git a/echosrv.c b/echosrv.c index 4fa8e4e..54719ca 100644 --- a/echosrv.c +++ b/echosrv.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #define cfg sslhcfg @@ -323,9 +322,6 @@ int start_listen_sockets(struct listen_endpoint *sockfd[]) int main(int argc, char *argv[]) { - - extern char *optarg; - extern int optind; int num_addr_listen; struct listen_endpoint *listen_sockets; diff --git a/sslh-main.c b/sslh-main.c index 160d9fc..ba3de13 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -280,9 +280,6 @@ void close_std(void) int main(int argc, char *argv[], char* envp[]) { - - extern char *optarg; - extern int optind; int res, num_addr_listen; struct listen_endpoint *listen_sockets; From b7556c07bddf0e666ea9bf7bdfb0a140044190b0 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 29 May 2025 16:07:57 +0200 Subject: [PATCH 21/21] added podman transparent setup (fix #448) --- README.md | 1 + doc/podman.md | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 doc/podman.md diff --git a/README.md b/README.md index 1fcaeaa..58e2e85 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ interfaces. The principle and basic setup is described [here](doc/simple_transparent_proxy.md), with further scenarios described [there](doc/scenarios-for-simple-transparent-proxy.md). +There is also a guide to use [podman](doc/podman.md). Another method uses iptable packet marking features, and is highly dependent on your network environment and diff --git a/doc/podman.md b/doc/podman.md new file mode 100644 index 0000000..7e268ff --- /dev/null +++ b/doc/podman.md @@ -0,0 +1,120 @@ +Here is an example of deployment of sslh in transparent mode +using ansible and podman, [submitted by Github user +olegstepura](https://github.com/yrutschle/sslh/issues/448) + +```yaml +# ansible podman task +- name: "Create sslh-co pod" + containers.podman.podman_pod: + name: sslh-co # read as "sslh and company" + state: started + ports: + - "80:80" + - "443:444" + # ... (more ports if needed) + network: + - '{{ containers.config.network }}' # other services from this network can access containers in this network for example prometheus can read caddy metrics at sslh-co:2020, also caddy itself can connect to other services to act as a reverse proxy + +- name: "Create the sslh container" + containers.podman.podman_container: + name: sslh + image: "yrutschle/sslh:latest" + pod: sslh-co + capabilities: + - NET_RAW + - NET_BIND_SERVICE + - NET_ADMIN + sysctl: + net.ipv4.conf.default.route_localnet: 1 + net.ipv4.conf.all.route_localnet: 1 + expose: + - 444 + volume: + # ... (make sure to mount config as you like) + command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script + state: started + +- name: "Create the caddy container" + containers.podman.podman_container: + name: caddy + image: "lucaslorentz/caddy-docker-proxy:alpine" # regular caddy or nginx image will also work + pod: sslh-co + expose: + - 80 + - 443 + - 2020 # metrics, since caddy-docker-proxy uses :2019 internally + volume: + # ... (mount your configs and other stuff here) + - "/var/run/podman/podman.sock:/var/run/docker.sock" + state: started + notify: podman restart sslh + +- name: "Create the SSH proxy to host container" + containers.podman.podman_container: + name: ssh-proxy + image: "alpine/socat:latest" + pod: sslh-co + expose: + - 222 + command: TCP-LISTEN:222,fork TCP:host.containers.internal:22 + state: started +``` + +```ini +# sslh config +foreground: true; +inetd: false; +numeric: true; +transparent: true; +timeout: 5; + +listen: +( + { host: "0.0.0.0"; port: "444"; keepalive: true; }, +); + +protocols: +( + { + name: "ssh"; + service: "ssh"; + host: "localhost"; + port: "222"; + fork: true; + }, + { + name: "http"; + host: "localhost"; + port: "80"; + }, + { + name: "tls"; + host: "localhost"; + port: "443"; + }, +); +``` + +I omitted caddy configs here as it's not important. Some unrelated container configs were also dropped. +In the example above `sslh`, `caddy` and `ssh-proxy` are 3 containers in the same pod, all listening on `localhost`. SSLH has to listen on `444` because caddy already listens on `443` and it's more complex to reconfigure caddy port because of let's encrypt (caddy itself "thinks" it is bound to your host interface). + +Scheme of port mapping is (all containers share same `localhost`): +``` +host 443 → pod 444 (sslh) → pod 443 (caddy) +``` +- podman connects host `443` port to pod's `444` port +- `sslh` listens `444` on `localhost`, reroutes tls to `caddy` on `localhost:443` +- `caddy` listens `443` on `localhost` (reverse-proxies to other apps on private network) +Reverse-proxied services get correct IP in `X-Forwarded-For` header. + +Takeaways: +- `net.ipv4.conf.default.route_localnet` is setup only in container, not on host, which is nice. +- same with iptables and route rules applied by `init` script in sslh container +- `sslh` proxy to other services in the private network will not work (because transparent mode is enabled) even if that other service does not need real IP + - with transparent mode all services that `sslh` will connect to should be attached to this pod making it listen on `localhost` of the pod + - ports of containers should not clash + - ports should be published on pod level, not on containers + - containers should not be configured to connect to custom networks +- because of the above proxying ssh to host will also not work out of the box, `sslh` should connect to `localhost` and not to a random IP. While adding another proxy as a container to the same pod sounds like an overkill I don't see any other solution, so `socat` is used as an additional proxy. +- `reverse_proxy` by caddy to other containers in the same custom network (as pod is attached to) works (e.g. caddy can connect to other IPs from custom network). `socat` can also connect to host and/or other containers in custom network. +