diff options
Diffstat (limited to 'crypto/openssl/ssl/rio/rio_notifier.c')
| -rw-r--r-- | crypto/openssl/ssl/rio/rio_notifier.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/crypto/openssl/ssl/rio/rio_notifier.c b/crypto/openssl/ssl/rio/rio_notifier.c new file mode 100644 index 000000000000..6dbb2bdc4766 --- /dev/null +++ b/crypto/openssl/ssl/rio/rio_notifier.c @@ -0,0 +1,379 @@ +/* + * Copyright 2024-2025 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 + */ + +#include "internal/sockets.h" +#include <openssl/bio.h> +#include <openssl/err.h> +#include "internal/thread_once.h" +#include "internal/rio_notifier.h" + +/* + * Sets a socket as close-on-exec, except that this is a no-op if we are certain + * we do not need to do this or the OS does not support the concept. + */ +static int set_cloexec(int fd) +{ +#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC) + return fcntl(fd, F_SETFD, FD_CLOEXEC) >= 0; +#else + return 1; +#endif +} + +#if defined(OPENSSL_SYS_WINDOWS) + +static CRYPTO_ONCE ensure_wsa_startup_once = CRYPTO_ONCE_STATIC_INIT; +static int wsa_started; + +static void ossl_wsa_cleanup(void) +{ + if (wsa_started) { + wsa_started = 0; + WSACleanup(); + } +} + +DEFINE_RUN_ONCE_STATIC(do_wsa_startup) +{ + WORD versionreq = 0x0202; /* Version 2.2 */ + WSADATA wsadata; + + if (WSAStartup(versionreq, &wsadata) != 0) + return 0; + wsa_started = 1; + OPENSSL_atexit(ossl_wsa_cleanup); + return 1; +} + +static ossl_inline int ensure_wsa_startup(void) +{ + return RUN_ONCE(&ensure_wsa_startup_once, do_wsa_startup); +} + +#endif + +#if RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKET + +/* Create a close-on-exec socket. */ +static int create_socket(int domain, int socktype, int protocol) +{ + int fd; +# if defined(OPENSSL_SYS_WINDOWS) + static const int on = 1; + + /* + * Use WSASocketA to create a socket which is immediately marked as + * non-inheritable, avoiding race conditions if another thread is about to + * call CreateProcess. + * NOTE: windows xp (0x501) doesn't support the non-inheritance flag here + * but preventing inheritance isn't mandatory, just a safety precaution + * so we can get away with not including it for older platforms + */ + +# ifdef WSA_FLAG_NO_HANDLE_INHERIT + fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, + WSA_FLAG_NO_HANDLE_INHERIT); + + /* + * Its also possible that someone is building a binary on a newer windows + * SDK, but running it on a runtime that doesn't support inheritance + * supression. In that case the above will return INVALID_SOCKET, and + * our response for those older platforms is to try the call again + * without the flag + */ + if (fd == INVALID_SOCKET) + fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0); +# else + fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0); +# endif + if (fd == INVALID_SOCKET) { + int err = get_last_socket_error(); + + ERR_raise_data(ERR_LIB_SYS, err, + "calling WSASocketA() = %d", err); + return INVALID_SOCKET; + } + + /* Prevent interference with the socket from other processes on Windows. */ + if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *)&on, sizeof(on)) < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_socket_error(), + "calling setsockopt()"); + BIO_closesocket(fd); + return INVALID_SOCKET; + } + +# else +# if defined(SOCK_CLOEXEC) + socktype |= SOCK_CLOEXEC; +# endif + + fd = BIO_socket(domain, socktype, protocol, 0); + if (fd == INVALID_SOCKET) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling BIO_socket()"); + return INVALID_SOCKET; + } + + /* + * Make socket close-on-exec unless this was already done above at socket + * creation time. + */ + if (!set_cloexec(fd)) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling set_cloexec()"); + BIO_closesocket(fd); + return INVALID_SOCKET; + } +# endif + + return fd; +} + +/* + * The SOCKET notifier method manually creates a connected TCP socket pair by + * temporarily creating a TCP listener on a random port and connecting back to + * it. + * + * Win32 does not support socketpair(2), and Win32 pipes are not compatible with + * Winsock select(2). This means our only means of making select(2) wakeable is + * to artifically create a loopback TCP connection and send bytes to it. + */ +int ossl_rio_notifier_init(RIO_NOTIFIER *nfy) +{ + int rc, lfd = -1, rfd = -1, wfd = -1; + struct sockaddr_in sa = {0}, accept_sa; + socklen_t sa_len = sizeof(sa), accept_sa_len = sizeof(accept_sa); + +# if defined(OPENSSL_SYS_WINDOWS) + if (!ensure_wsa_startup()) { + ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR, + "Cannot start Windows sockets"); + return 0; + } +# endif + /* Create a close-on-exec socket. */ + lfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (lfd == INVALID_SOCKET) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling create_socket()"); + return 0; + } + + /* Bind the socket to a random loopback port. */ + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + rc = bind(lfd, (const struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling bind()"); + goto err; + } + + /* Determine what random port was allocated. */ + rc = getsockname(lfd, (struct sockaddr *)&sa, &sa_len); + if (rc < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling getsockname()"); + goto err; + } + + /* Start listening. */ + rc = listen(lfd, 1); + if (rc < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling listen()"); + goto err; + } + + /* Create another socket to connect to the listener. */ + wfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (wfd == INVALID_SOCKET) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling create_socket()"); + goto err; + } + + /* + * Disable Nagle's algorithm on the writer so that wakeups happen + * immediately. + */ + if (!BIO_set_tcp_ndelay(wfd, 1)) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling BIO_set_tcp_ndelay()"); + goto err; + } + + /* + * Connect the writer to the listener. + */ + rc = connect(wfd, (struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling connect()"); + goto err; + } + + /* + * The connection accepted from the listener is the read side. + */ + rfd = accept(lfd, (struct sockaddr *)&accept_sa, &accept_sa_len); + if (rfd == INVALID_SOCKET) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling accept()"); + goto err; + } + + rc = getsockname(wfd, (struct sockaddr *)&sa, &sa_len); + if (rc < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling getsockname()"); + goto err; + } + + /* Close the listener, which we don't need anymore. */ + BIO_closesocket(lfd); + lfd = -1; + + /* + * Sanity check - ensure someone else didn't connect to our listener during + * the brief window of possibility above. + */ + if (accept_sa.sin_family != AF_INET || accept_sa.sin_port != sa.sin_port) { + ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR, + "connected address differs from accepted address"); + goto err; + } + + /* Make both sides of the connection non-blocking. */ + if (!BIO_socket_nbio(rfd, 1)) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling BIO_socket_nbio()"); + goto err; + } + + if (!BIO_socket_nbio(wfd, 1)) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling BIO_socket_nbio()"); + goto err; + } + + nfy->rfd = rfd; + nfy->wfd = wfd; + return 1; + +err: + if (lfd != INVALID_SOCKET) + BIO_closesocket(lfd); + if (wfd != INVALID_SOCKET) + BIO_closesocket(wfd); + if (rfd != INVALID_SOCKET) + BIO_closesocket(rfd); + return 0; +} + +#elif RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKETPAIR + +int ossl_rio_notifier_init(RIO_NOTIFIER *nfy) +{ + int fds[2], domain = AF_INET, type = SOCK_STREAM; + +# if defined(SOCK_CLOEXEC) + type |= SOCK_CLOEXEC; +# endif +# if defined(SOCK_NONBLOCK) + type |= SOCK_NONBLOCK; +# endif + +# if defined(OPENSSL_SYS_UNIX) && defined(AF_UNIX) + domain = AF_UNIX; +# endif + + if (socketpair(domain, type, 0, fds) < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling socketpair()"); + return 0; + } + + if (!set_cloexec(fds[0]) || !set_cloexec(fds[1])) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling set_cloexec()"); + goto err; + } + +# if !defined(SOCK_NONBLOCK) + if (!BIO_socket_nbio(fds[0], 1) || !BIO_socket_nbio(fds[1], 1)) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling BIO_socket_nbio()"); + goto err; + } +# endif + + if (domain == AF_INET && !BIO_set_tcp_ndelay(fds[1], 1)) { + ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(), + "calling BIO_set_tcp_ndelay()"); + goto err; + } + + nfy->rfd = fds[0]; + nfy->wfd = fds[1]; + return 1; + +err: + BIO_closesocket(fds[1]); + BIO_closesocket(fds[0]); + return 0; +} + +#endif + +void ossl_rio_notifier_cleanup(RIO_NOTIFIER *nfy) +{ + if (nfy->rfd < 0) + return; + + BIO_closesocket(nfy->wfd); + BIO_closesocket(nfy->rfd); + nfy->rfd = nfy->wfd = -1; +} + +int ossl_rio_notifier_signal(RIO_NOTIFIER *nfy) +{ + static const unsigned char ch = 0; + ossl_ssize_t wr; + + do + /* + * Note: If wr returns 0 the buffer is already full so we don't need to + * do anything. + */ + wr = writesocket(nfy->wfd, (void *)&ch, sizeof(ch)); + while (wr < 0 && get_last_socket_error_is_eintr()); + + return 1; +} + +int ossl_rio_notifier_unsignal(RIO_NOTIFIER *nfy) +{ + unsigned char buf[16]; + ossl_ssize_t rd; + + /* + * signal() might have been called multiple times. Drain the buffer until + * it's empty. + */ + do + rd = readsocket(nfy->rfd, (void *)buf, sizeof(buf)); + while (rd == sizeof(buf) + || (rd < 0 && get_last_socket_error_is_eintr())); + + if (rd < 0 && !BIO_fd_non_fatal_error(get_last_socket_error())) + return 0; + + return 1; +} |
