Commit 9c32abf541 for openssl.org
commit 9c32abf54125b50a6cc9441708c523609feaed69
Author: Igor Ustinov <igus68@gmail.com>
Date: Tue Feb 17 14:36:04 2026 +0100
Added BIO_set_send_flags() function to set flags passed to send(), sendto(), and sendmsg().
The main intention is to allow setting the MSG_NOSIGNAL flag to avoid
a crash on receiving the SIGPIPE signal.
Fixes #16399
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
(Merged from https://github.com/openssl/openssl/pull/30044)
diff --git a/CHANGES.md b/CHANGES.md
index 3fb60df8da..147e13149d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -329,6 +329,12 @@ OpenSSL 4.0
* Bob Beck *
+ * Added BIO_set_send_flags() function that allows setting flags passed to
+ send(), sendto(), and sendmsg(). The main intention is to allow setting
+ the MSG_NOSIGNAL flag to avoid a crash on receiving the SIGPIPE signal.
+
+ *Igor Ustinov*
+
OpenSSL 3.6
-----------
diff --git a/crypto/bio/bio_lib.c b/crypto/bio/bio_lib.c
index 5be3acca5a..f9093a7355 100644
--- a/crypto/bio/bio_lib.c
+++ b/crypto/bio/bio_lib.c
@@ -211,6 +211,11 @@ void BIO_set_flags(BIO *b, int flags)
b->flags |= flags;
}
+long BIO_set_send_flags(BIO *b, int flags)
+{
+ return BIO_ctrl(b, BIO_C_SET_SEND_FLAGS, (long)flags, NULL);
+}
+
#ifndef OPENSSL_NO_DEPRECATED_3_0
BIO_callback_fn BIO_get_callback(const BIO *b)
{
diff --git a/crypto/bio/bss_conn.c b/crypto/bio/bss_conn.c
index 044ee07a50..56adbd07a5 100644
--- a/crypto/bio/bss_conn.c
+++ b/crypto/bio/bss_conn.c
@@ -415,7 +415,7 @@ static int conn_write(BIO *b, const char *in, int inl)
clear_socket_error();
#ifndef OPENSSL_NO_KTLS
if (BIO_should_ktls_ctrl_msg_flag(b)) {
- ret = ktls_send_ctrl_message(b->num, data->record_type, in, inl);
+ ret = ktls_send_ctrl_message(b->num, data->record_type, in, inl, 0);
if (ret >= 0) {
ret = inl;
BIO_clear_ktls_ctrl_msg_flag(b);
diff --git a/crypto/bio/bss_sock.c b/crypto/bio/bss_sock.c
index ea128ab892..f8db8c10f2 100644
--- a/crypto/bio/bss_sock.c
+++ b/crypto/bio/bss_sock.c
@@ -34,6 +34,7 @@ struct bss_sock_st {
#ifndef OPENSSL_NO_KTLS
unsigned char ktls_record_type;
#endif
+ int sflags;
};
static int sock_write(BIO *h, const char *buf, int num);
@@ -129,15 +130,13 @@ static int sock_read(BIO *b, char *out, int outl)
static int sock_write(BIO *b, const char *in, int inl)
{
int ret = 0;
-#if !defined(OPENSSL_NO_KTLS) || defined(OSSL_TFO_SENDTO)
struct bss_sock_st *data = (struct bss_sock_st *)b->ptr;
-#endif
clear_socket_error();
#ifndef OPENSSL_NO_KTLS
if (BIO_should_ktls_ctrl_msg_flag(b)) {
unsigned char record_type = data->ktls_record_type;
- ret = ktls_send_ctrl_message(b->num, record_type, in, inl);
+ ret = ktls_send_ctrl_message(b->num, record_type, in, inl, data->sflags);
if (ret >= 0) {
ret = inl;
BIO_clear_ktls_ctrl_msg_flag(b);
@@ -146,15 +145,14 @@ static int sock_write(BIO *b, const char *in, int inl)
#endif
#if defined(OSSL_TFO_SENDTO)
if (data->tfo_first) {
- struct bss_sock_st *data = (struct bss_sock_st *)b->ptr;
socklen_t peerlen = BIO_ADDR_sockaddr_size(&data->tfo_peer);
- ret = sendto(b->num, in, inl, OSSL_TFO_SENDTO,
+ ret = sendto(b->num, in, inl, OSSL_TFO_SENDTO | data->sflags,
BIO_ADDR_sockaddr(&data->tfo_peer), peerlen);
data->tfo_first = 0;
} else
#endif
- ret = writesocket(b->num, in, inl);
+ ret = writesocket_ex(b->num, in, inl, data->sflags);
BIO_clear_retry_flags(b);
if (ret <= 0) {
if (BIO_sock_should_retry(ret))
@@ -265,6 +263,20 @@ static long sock_ctrl(BIO *b, int cmd, long num, void *ptr)
ret = 0;
}
break;
+ case BIO_C_SET_SEND_FLAGS:
+ if (num > INT_MAX || num < INT_MIN) {
+ ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
+ return 0;
+ }
+#if defined(OPENSSL_SYS_VXWORKS) || defined(OPENSSL_SYS_TANDEM)
+ if (num != 0) {
+ ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
+ return 0;
+ }
+#endif
+ data->sflags = (int)num;
+ ret = 1;
+ break;
default:
ret = 0;
break;
diff --git a/doc/man3/BIO_s_socket.pod b/doc/man3/BIO_s_socket.pod
index d8b1647fbe..8200cb20fe 100644
--- a/doc/man3/BIO_s_socket.pod
+++ b/doc/man3/BIO_s_socket.pod
@@ -2,7 +2,7 @@
=head1 NAME
-BIO_s_socket, BIO_new_socket - socket BIO
+BIO_s_socket, BIO_new_socket, BIO_set_send_flags - socket BIO
=head1 SYNOPSIS
@@ -12,6 +12,8 @@ BIO_s_socket, BIO_new_socket - socket BIO
BIO *BIO_new_socket(int sock, int close_flag);
+ long BIO_set_send_flags(BIO *b, int flags);
+
=head1 DESCRIPTION
BIO_s_socket() returns the socket BIO method. This is a wrapper
@@ -25,6 +27,11 @@ when the BIO is freed.
BIO_new_socket() returns a socket BIO using B<sock> and B<close_flag>.
+BIO_set_send_flags() sets flags passed to send(), sendto() and sendmsg().
+The set of available flags is platform-dependent. The main intention is to
+allow setting the MSG_NOSIGNAL flag to avoid a crash on receiving the SIGPIPE
+signal.
+
=head1 NOTES
Socket BIOs also support any relevant functionality of file descriptor
@@ -42,9 +49,15 @@ BIO_s_socket() returns the socket BIO method.
BIO_new_socket() returns the newly allocated BIO or NULL is an error
occurred.
+BIO_set_send_flags() returns 1 on success, 0 on errors.
+
+=head1 HISTORY
+
+BIO_set_send_flags() was added in OpenSSL 4.0.
+
=head1 COPYRIGHT
-Copyright 2000-2016 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2000-2026 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use
this file except in compliance with the License. You can obtain a copy
diff --git a/include/internal/ktls.h b/include/internal/ktls.h
index a6d15e006a..04dfb1e463 100644
--- a/include/internal/ktls.h
+++ b/include/internal/ktls.h
@@ -97,8 +97,8 @@ static ossl_inline int ktls_enable_tx_zerocopy_sendfile(int fd)
* the entire record is pushed to TCP. It is impossible to send a partial
* record using this control message.
*/
-static ossl_inline int ktls_send_ctrl_message(int fd, unsigned char record_type,
- const void *data, size_t length)
+static ossl_inline int ktls_send_ctrl_message(int fd,
+ unsigned char record_type, const void *data, size_t lengthi, int flags)
{
struct msghdr msg = { 0 };
int cmsg_len = sizeof(record_type);
@@ -120,7 +120,7 @@ static ossl_inline int ktls_send_ctrl_message(int fd, unsigned char record_type,
msg.msg_iov = &msg_iov;
msg.msg_iovlen = 1;
- return sendmsg(fd, &msg, 0);
+ return sendmsg(fd, &msg, flags);
}
#ifdef OPENSSL_NO_KTLS_RX
@@ -334,8 +334,8 @@ static ossl_inline int ktls_enable_tx_zerocopy_sendfile(int fd)
* the entire record is pushed to TCP. It is impossible to send a partial
* record using this control message.
*/
-static ossl_inline int ktls_send_ctrl_message(int fd, unsigned char record_type,
- const void *data, size_t length)
+static ossl_inline int ktls_send_ctrl_message(int fd,
+ unsigned char record_type, const void *data, size_t length, int flags)
{
struct msghdr msg;
int cmsg_len = sizeof(record_type);
@@ -361,7 +361,7 @@ static ossl_inline int ktls_send_ctrl_message(int fd, unsigned char record_type,
msg.msg_iov = &msg_iov;
msg.msg_iovlen = 1;
- return sendmsg(fd, &msg, 0);
+ return sendmsg(fd, &msg, flags);
}
/*
diff --git a/include/internal/sockets.h b/include/internal/sockets.h
index 877bbd7145..38c95981a8 100644
--- a/include/internal/sockets.h
+++ b/include/internal/sockets.h
@@ -176,23 +176,40 @@ typedef size_t socklen_t; /* Currently appears to be missing on VMS */
#define get_last_socket_error_is_eintr() (get_last_socket_error() == WSAEINTR)
#define readsocket(s, b, n) recv((s), (b), (n), 0)
#define writesocket(s, b, n) send((s), (b), (n), 0)
+#define writesocket_ex(s, b, n, f) send((s), (b), (n), (f))
#elif defined(__DJGPP__)
#define closesocket(s) close_s(s)
#define readsocket(s, b, n) read_s(s, b, n)
#define writesocket(s, b, n) send(s, b, n, 0)
+#define writesocket_ex(s, b, n, f) send(s, b, n, f)
#elif defined(OPENSSL_SYS_VMS)
#define ioctlsocket(a, b, c) ioctl(a, b, c)
#define closesocket(s) close(s)
#define readsocket(s, b, n) recv((s), (b), (n), 0)
#define writesocket(s, b, n) send((s), (b), (n), 0)
+#define writesocket_ex(s, b, n, f) send((s), (b), (n), (f))
#elif defined(OPENSSL_SYS_VXWORKS)
#define ioctlsocket(a, b, c) ioctl((a), (b), (int)(c))
#define closesocket(s) close(s)
#define readsocket(s, b, n) read((s), (b), (n))
#define writesocket(s, b, n) write((s), (char *)(b), (n))
+static ossl_inline int writesocket_ex(int s, char *b, int n, int f)
+{
+ if (f == 0)
+ return writesocket(s, b, n);
+ errno = EINVAL;
+ return -1;
+}
#elif defined(OPENSSL_SYS_TANDEM)
#define readsocket(s, b, n) read((s), (b), (n))
#define writesocket(s, b, n) write((s), (b), (n))
+static ossl_inline int writesocket_ex(int s, const void *b, int n, int f)
+{
+ if (f == 0)
+ return writesocket(s, b, n);
+ errno = EINVAL;
+ return -1;
+}
#define ioctlsocket(a, b, c) ioctl(a, b, c)
#define closesocket(s) close(s)
#else
@@ -200,6 +217,7 @@ typedef size_t socklen_t; /* Currently appears to be missing on VMS */
#define closesocket(s) close(s)
#define readsocket(s, b, n) read((s), (b), (n))
#define writesocket(s, b, n) write((s), (b), (n))
+#define writesocket_ex(s, b, n, f) ((f) == 0) ? write((s), (b), (n)) : send((s), (b), (n), (f))
#endif
/* also in apps/include/apps.h */
diff --git a/include/openssl/bio.h.in b/include/openssl/bio.h.in
index 47e565174d..39afb75fc8 100644
--- a/include/openssl/bio.h.in
+++ b/include/openssl/bio.h.in
@@ -484,6 +484,7 @@ typedef struct bio_poll_descriptor_st {
#define BIO_C_SET_SOCK_TYPE 157
#define BIO_C_GET_SOCK_TYPE 158
#define BIO_C_GET_DGRAM_BIO 159
+#define BIO_C_SET_SEND_FLAGS 160
#define BIO_set_app_data(s, arg) BIO_set_ex_data(s, 0, arg)
#define BIO_get_app_data(s) BIO_get_ex_data(s, 0)
@@ -806,6 +807,7 @@ int BIO_sock_non_fatal_error(int error);
int BIO_err_is_non_fatal(unsigned int errcode);
int BIO_socket_wait(int fd, int for_read, time_t max_time);
#endif
+long BIO_set_send_flags(BIO *b, int flags);
int BIO_wait(BIO *bio, time_t max_time, unsigned int nap_milliseconds);
int BIO_do_connect_retry(BIO *bio, int timeout, int nap_milliseconds);
diff --git a/test/bio_socket_sigpipe_test.c b/test/bio_socket_sigpipe_test.c
new file mode 100644
index 0000000000..b8d38593da
--- /dev/null
+++ b/test/bio_socket_sigpipe_test.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#if !defined(OPENSSL_SYS_WINDOWS) && !defined(OPENSSL_NO_SOCK) && !defined(__DJGPP__)
+
+#include "internal/sockets.h"
+#include <openssl/bio.h>
+#include <internal/bio.h>
+#include <openssl/err.h>
+
+#include "testutil.h"
+
+#include <signal.h>
+#include <errno.h>
+
+static volatile sig_atomic_t sigpipe_seen = 0;
+
+static void sigpipe_handler(int sig)
+{
+ (void)sig;
+ sigpipe_seen++;
+}
+
+/*
+ * 0 - normal flow
+ * 1 - kTLS
+ * 2 - TFO
+ */
+static int test_bio_write_triggers_sigpipe(int test)
+{
+#if defined(MSG_NOSIGNAL)
+ int fds[2] = { -1, -1 };
+ BIO *b = NULL;
+ const char c = 'x';
+ int ret;
+ int ok = 0;
+ struct sigaction sa, oldsa;
+
+ /* Install SIGPIPE handler */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sigpipe_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ if (!TEST_int_eq(sigaction(SIGPIPE, &sa, &oldsa), 0))
+ goto end;
+
+ /* Create a pair of connected sockets. */
+ if (!TEST_int_eq(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0))
+ goto end;
+
+ /* Close peer end to make writes hit a broken pipe. */
+ if (!TEST_int_eq(closesocket(fds[1]), 0))
+ goto end;
+ fds[1] = -1;
+
+ b = BIO_new_socket(fds[0], BIO_NOCLOSE);
+ if (!TEST_ptr(b))
+ goto end;
+ /*
+ * Attempt write. We don't care about return value beyond
+ * "it attempted", the point is SIGPIPE delivery.
+ */
+ ERR_clear_error();
+ errno = 0;
+ sigpipe_seen = 0;
+
+ if (test == 1) {
+#ifndef OPENSSL_NO_KTLS
+ BIO_set_ktls_ctrl_msg_flag(b);
+#else
+ TEST_skip("OPENSSL_NO_KTLS is defined\n");
+ ok = 1;
+ goto end;
+#endif
+ }
+
+ if (test == 2) {
+#ifdef OSSL_TFO_SENDTO
+ struct in_addr a4;
+ BIO_ADDR *peer = BIO_ADDR_new();
+ if (!TEST_ptr(peer))
+ goto end;
+ inet_pton(AF_INET, "127.0.0.1", &a4);
+ BIO_ADDR_rawmake(peer, AF_INET, &a4, sizeof(a4), 443);
+ ret = BIO_ctrl(b, BIO_C_SET_CONNECT, 2, peer);
+ BIO_ADDR_free(peer);
+ if (!TEST_int_eq(ret, 1))
+ goto end;
+#else
+ TEST_skip("OSSL_TFO_SENDTO is not defined\n");
+ ok = 1;
+ goto end;
+#endif
+ }
+
+ if (!TEST_int_eq(BIO_set_send_flags(b, MSG_NOSIGNAL), 1))
+ goto end;
+ ret = BIO_write(b, &c, 1);
+ (void)ret;
+
+ /* PASS only if SIGPIPE wasn't delivered. */
+ if (!TEST_int_eq((int)sigpipe_seen, 0))
+ goto end;
+
+ ok = 1;
+
+end:
+ BIO_free(b);
+
+ if (fds[0] != -1) {
+ closesocket(fds[0]);
+ fds[0] = -1;
+ }
+ if (fds[1] != -1) {
+ closesocket(fds[1]);
+ fds[1] = -1;
+ }
+
+ /* Restore previous handler. */
+ (void)sigaction(SIGPIPE, &oldsa, NULL);
+
+ return ok;
+#else
+ /* No MSG_NOSIGNAL on this platform -> skip. */
+ TEST_skip("MSG_NOSIGNAL is not defined on this platform");
+ return 1;
+#endif
+}
+
+int setup_tests(void)
+{
+ ADD_ALL_TESTS(test_bio_write_triggers_sigpipe, 3);
+ return 1;
+}
+#endif
diff --git a/test/build.info b/test/build.info
index 23bc75c7ca..eec0553cc8 100644
--- a/test/build.info
+++ b/test/build.info
@@ -563,6 +563,16 @@ IF[{- !$disabled{tests} -}]
INCLUDE[bio_eof_test]=../include ../apps/include
DEPEND[bio_eof_test]=../libcrypto libtestutil.a
+ IF[{- !$disabled{sock}
+ && $config{target} !~ /djgpp/i
+ && $config{target} !~ /^(?:VC-|mingw|BC-|Cygwin)/i
+ -}]
+ PROGRAMS{noinst}=bio_socket_sigpipe_test
+ SOURCE[bio_socket_sigpipe_test]=bio_socket_sigpipe_test.c
+ INCLUDE[bio_socket_sigpipe_test]=../include ../apps/include
+ DEPEND[bio_socket_sigpipe_test]=../libcrypto libtestutil.a
+ ENDIF
+
SOURCE[bioprinttest]=bioprinttest.c
INCLUDE[bioprinttest]=../include ../apps/include
IF[{- $config{target} =~ /^VC/ -}]
diff --git a/test/recipes/61-test_bio_socketsigpipe.t b/test/recipes/61-test_bio_socketsigpipe.t
new file mode 100644
index 0000000000..cab0288251
--- /dev/null
+++ b/test/recipes/61-test_bio_socketsigpipe.t
@@ -0,0 +1,26 @@
+#! /usr/bin/env perl
+# Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License"). You may not use
+# this file except in compliance with the License. You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use OpenSSL::Test qw/:DEFAULT srctop_file/;
+use OpenSSL::Test::Utils;
+
+setup("test_bio_socketsigpipe");
+
+plan skip_all => "SIGPIPE is not supported on Windows"
+ if $^O eq 'MSWin32';
+
+plan skip_all => "DJGPP target does not support this test"
+ if config('target') =~ /djgpp/i;
+
+plan skip_all => "sockets are disabled (no-sock)"
+ if disabled("sock");
+
+plan tests => 1;
+
+ok(run(test(["bio_socket_sigpipe_test"])), "bio_socket_sigpipe_test");
+
diff --git a/util/libcrypto.num b/util/libcrypto.num
index e94360e702..954486f390 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -2857,6 +2857,7 @@ BIO_get_new_index ? 4_0_0 EXIST::FUNCTION:
BIO_set_flags ? 4_0_0 EXIST::FUNCTION:
BIO_test_flags ? 4_0_0 EXIST::FUNCTION:
BIO_clear_flags ? 4_0_0 EXIST::FUNCTION:
+BIO_set_send_flags ? 4_0_0 EXIST::FUNCTION:
BIO_get_callback ? 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_3_0
BIO_set_callback ? 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_3_0
BIO_debug_callback ? 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_3_0
diff --git a/util/platform_symbols/unix-symbols.txt b/util/platform_symbols/unix-symbols.txt
index 166bc5d8df..62720f35fd 100644
--- a/util/platform_symbols/unix-symbols.txt
+++ b/util/platform_symbols/unix-symbols.txt
@@ -118,6 +118,7 @@ recvfrom
recvmmsg
secure_getenv
select
+send
sendmmsg
sendto
setbuf