]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: ssl: disable kTLS on OpenSSL versions affected by a known bug
authorOlivier Houchard <ohouchard@haproxy.com>
Wed, 4 Nov 2026 12:42:28 +0000 (13:42 +0100)
committerOlivier Houchard <cognet@ci0.org>
Wed, 4 Nov 2026 17:42:28 +0000 (18:42 +0100)
With vanilla OpenSSL, a blocked kTLS record keeps referencing the caller's
buffer and the library retries from it, even though
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER is set; the H2 fast-forward reuses that
buffer, so streams get corrupted. Upstream fixed this in 3.4.6 and 3.5.7, so
disable kTLS on older versions and report it, with "tune.ssl.ktls-unsafe" to
opt in anyway.

To be backported to 3.3 and 3.4.

doc/configuration.txt
include/haproxy/openssl-compat.h
include/haproxy/ssl_sock-t.h
include/haproxy/ssl_sock.h
src/cfgparse-ssl.c
src/ssl_sock.c

index 2698b146253ed2b64f179ae94ed6f01750247bb3..b35ae413f9c834c0a4e77c642dcd7f715172ddbd 100644 (file)
@@ -5962,6 +5962,13 @@ tune.ssl.keyupdate-rate-limit <limit>
   KeyUpdate is cpu-expensive, and there is little reason to receive a lot of
   them. Using a value of "0" disables the rate limiting.
   The default value is 100.
+tune.ssl.ktls-unsafe
+  Use kTLS even on OpenSSL versions affected by a bug that corrupts streams
+  when a pending record's buffer is reused (fixed in 3.4.6 and 3.5.7). On
+  those versions kTLS is refused, and a notice reports it when "ktls on" is
+  parsed, unless this option is set. It has no effect on fixed versions nor
+  on other SSL libraries. Disabled by default.
+
 tune.ssl.lifetime <timeout>
   Sets how long a cached SSL session may remain valid. This time is expressed
   in seconds and defaults to 300 (5 min). It is important to understand that it
index 5064a83935d1bb35d1245c4222f63fb97fe23c7f..065600734fed8c090d7e9f0dcc43a6c158467f9a 100644 (file)
@@ -624,6 +624,17 @@ static inline unsigned long ERR_peek_error_func(const char **func)
 #define HA_USE_KTLS
 #endif /* OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC */
 
+/* OpenSSL 3.4.6 and 3.5.7 fixed a kTLS bug where a blocked send kept
+ * referencing the caller's buffer; earlier versions corrupt streams.
+ */
+#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
+#define HAVE_KTLS_TX_RETRY_FIX
+#elif defined(HAVE_VANILLA_OPENSSL) && \
+      (OPENSSL_VERSION_NUMBER >= 0x30500070L || \
+       (OPENSSL_VERSION_NUMBER >= 0x30400060L && OPENSSL_VERSION_NUMBER < 0x30500000L))
+#define HAVE_KTLS_TX_RETRY_FIX
+#endif
+
 #endif /* USE_KTLS */
 
 #endif /* USE_OPENSSL */
index 512f8831de5cd41ca6afe49aa442180b968b60c3..4e29cebb5e112f6f8dbd4553e1de5bae60e8aae4 100644 (file)
@@ -292,6 +292,7 @@ struct global_ssl {
        int  skip_self_issued_ca;
 
        int  async;                 /* whether we use ssl async mode */
+       int ktls_unsafe;             /* allow kTLS despite the library kTLS bug */
 
        char *listen_default_ciphers;
        char *connect_default_ciphers;
index b8dbc9025f7ae8091ce151da9c7787ed759b2951..a2dc8ab3774af279fcf0ce339a42f57a54dc5ce5 100644 (file)
@@ -40,6 +40,8 @@ extern struct eb_root crtlists_tree;
 extern struct eb_root cafile_tree;
 extern int sctl_ex_index;
 extern struct global_ssl global_ssl;
+
+int ssl_sock_ktls_allowed(void);
 extern struct ssl_crtlist_kw ssl_crtlist_kws[];
 extern struct methodVersions methodVersions[];
 __decl_thread(extern HA_SPINLOCK_T ckch_lock);
index d35e4ac520fb5f33a05d59f246443e0361d38a9c..1b9862e1b06f2b214bff9f332b3cb5faf1a6ff17 100644 (file)
@@ -547,6 +547,17 @@ static int ssl_parse_global_keyupdate_ratelimit(char **args, int section_type, s
        return 0;
 }
 
+static int ssl_parse_global_ktls_unsafe(char **args, int section_type, struct proxy *curpx,
+                                         const struct proxy *defpx, const char *file, int line,
+                                         char **err)
+{
+       if (too_many_args(0, args, err, NULL))
+               return -1;
+
+       global_ssl.ktls_unsafe = 1;
+       return 0;
+}
+
 /* Allow to explicitly disable certificate compression when set to "off" */
 #ifdef SSL_OP_NO_RX_CERTIFICATE_COMPRESSION
 static int ssl_parse_certificate_compression(char **args, int section_type, struct proxy *curpx,
@@ -1030,7 +1041,7 @@ static int ssl_bind_parse_ktls(char **args, int cur_arg, struct proxy *px, struc
                return ERR_ALERT | ERR_FATAL;
        }
        if (strcasecmp(args[cur_arg + 1], "on") == 0) {
-               conf->ktls = 1;
+               conf->ktls = ssl_sock_ktls_allowed();
        } else if (strcasecmp(args[cur_arg + 1], "off") == 0) {
                conf->ktls = 0;
        } else {
@@ -2116,7 +2127,8 @@ static int srv_parse_ktls(char **args, int *cur_arg, struct proxy *px, struct se
        }
 
        if (strcasecmp(args[*cur_arg + 1], "on") == 0) {
-               newsrv->ssl_ctx.options |= SRV_SSL_O_KTLS;
+               if (ssl_sock_ktls_allowed())
+                       newsrv->ssl_ctx.options |= SRV_SSL_O_KTLS;
        } else if (strcasecmp(args[*cur_arg + 1], "off") == 0) {
                newsrv->ssl_ctx.options &= ~SRV_SSL_O_KTLS;
        } else {
@@ -2894,6 +2906,7 @@ static struct cfg_kw_list cfg_kws = {ILH, {
        { CFG_GLOBAL, "tune.ssl.ssl-ctx-cache-size", ssl_parse_global_int },
        { CFG_GLOBAL, "tune.ssl.capture-cipherlist-size", ssl_parse_global_capture_buffer },
        { CFG_GLOBAL, "tune.ssl.capture-buffer-size", ssl_parse_global_capture_buffer },
+       { CFG_GLOBAL, "tune.ssl.ktls-unsafe", ssl_parse_global_ktls_unsafe },
        { CFG_GLOBAL, "tune.ssl.keylog", ssl_parse_global_keylog },
        { CFG_GLOBAL, "tune.ssl.keyupdate-rate-limit", ssl_parse_global_keyupdate_ratelimit },
        { CFG_GLOBAL, "ssl-default-bind-ciphers", ssl_parse_global_ciphers },
index ff546670b2d99ab99191b36f68ca51072496a958..b9fcf36c8cda40ec5faf294048d4e3950e04cba5 100644 (file)
@@ -6348,6 +6348,29 @@ void ssl_sock_srv_try_reuse_sess(struct ssl_sock_ctx *ctx, struct server *srv)
  * handshake flag on the connection. It is safe to call it multiple times.
  * It returns 0 on success and -1 in error case.
  */
+/* kTLS silently corrupts streams on libraries that keep referencing the caller's
+ * buffer after a blocked send (fixed in 3.4.6 and 3.5.7), so it is refused by
+ * default and the user has to opt in.
+ */
+int ssl_sock_ktls_allowed(void)
+{
+#ifdef HAVE_KTLS_TX_RETRY_FIX
+       return 1;
+#else
+       static char warned;
+
+       if (global_ssl.ktls_unsafe)
+               return 1;
+       if (!warned) {
+               warned = 1;
+               ha_notice("kTLS disabled: this OpenSSL version is affected by a known "
+                         "kTLS bug; upgrade to at least 3.4.6 or 3.5.7, or set "
+                         "'tune.ssl.ktls-unsafe' to use it anyway.\n");
+       }
+       return 0;
+#endif
+}
+
 static int ssl_sock_init(struct connection *conn, void **xprt_ctx)
 {
        struct ssl_sock_ctx *ctx;
@@ -6427,7 +6450,8 @@ static int ssl_sock_init(struct connection *conn, void **xprt_ctx)
                ssl_sock_srv_try_reuse_sess(ctx, srv);
 
 #ifdef HA_USE_KTLS
-               if ((srv->ssl_ctx.options & SRV_SSL_O_KTLS) && !(global.tune.options & GTUNE_NO_KTLS)) {
+               if ((srv->ssl_ctx.options & SRV_SSL_O_KTLS) &&
+                   !(global.tune.options & GTUNE_NO_KTLS) && ssl_sock_ktls_allowed()) {
 #ifdef HAVE_VANILLA_OPENSSL
                        SSL_set_options(ctx->ssl, SSL_OP_ENABLE_KTLS);
 #endif
@@ -6474,7 +6498,8 @@ static int ssl_sock_init(struct connection *conn, void **xprt_ctx)
 #endif
 
 #ifdef HA_USE_KTLS
-               if (bc->ssl_conf.ktls && !(global.tune.options & GTUNE_NO_KTLS)) {
+               if (bc->ssl_conf.ktls &&
+                   !(global.tune.options & GTUNE_NO_KTLS) && ssl_sock_ktls_allowed()) {
 #ifdef HAVE_VANILLA_OPENSSL
                        SSL_set_options(ctx->ssl, SSL_OP_ENABLE_KTLS);
 #endif