Path: blob/main/crypto/openssl/ssl/rio/rio_notifier.c
48262 views
/*1* Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/89#include "internal/sockets.h"10#include <openssl/bio.h>11#include <openssl/err.h>12#include "internal/thread_once.h"13#include "internal/rio_notifier.h"1415/*16* Sets a socket as close-on-exec, except that this is a no-op if we are certain17* we do not need to do this or the OS does not support the concept.18*/19static int set_cloexec(int fd)20{21#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC)22return fcntl(fd, F_SETFD, FD_CLOEXEC) >= 0;23#else24return 1;25#endif26}2728#if defined(OPENSSL_SYS_WINDOWS)2930static CRYPTO_ONCE ensure_wsa_startup_once = CRYPTO_ONCE_STATIC_INIT;31static int wsa_started;3233static void ossl_wsa_cleanup(void)34{35if (wsa_started) {36wsa_started = 0;37WSACleanup();38}39}4041DEFINE_RUN_ONCE_STATIC(do_wsa_startup)42{43WORD versionreq = 0x0202; /* Version 2.2 */44WSADATA wsadata;4546if (WSAStartup(versionreq, &wsadata) != 0)47return 0;48wsa_started = 1;49OPENSSL_atexit(ossl_wsa_cleanup);50return 1;51}5253static ossl_inline int ensure_wsa_startup(void)54{55return RUN_ONCE(&ensure_wsa_startup_once, do_wsa_startup);56}5758#endif5960#if RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKET6162/* Create a close-on-exec socket. */63static int create_socket(int domain, int socktype, int protocol)64{65int fd;66# if defined(OPENSSL_SYS_WINDOWS)67static const int on = 1;6869/*70* Use WSASocketA to create a socket which is immediately marked as71* non-inheritable, avoiding race conditions if another thread is about to72* call CreateProcess.73* NOTE: windows xp (0x501) doesn't support the non-inheritance flag here74* but preventing inheritance isn't mandatory, just a safety precaution75* so we can get away with not including it for older platforms76*/7778# ifdef WSA_FLAG_NO_HANDLE_INHERIT79fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0,80WSA_FLAG_NO_HANDLE_INHERIT);8182/*83* Its also possible that someone is building a binary on a newer windows84* SDK, but running it on a runtime that doesn't support inheritance85* supression. In that case the above will return INVALID_SOCKET, and86* our response for those older platforms is to try the call again87* without the flag88*/89if (fd == INVALID_SOCKET)90fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0);91# else92fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0);93# endif94if (fd == INVALID_SOCKET) {95int err = get_last_socket_error();9697ERR_raise_data(ERR_LIB_SYS, err,98"calling WSASocketA() = %d", err);99return INVALID_SOCKET;100}101102/* Prevent interference with the socket from other processes on Windows. */103if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *)&on, sizeof(on)) < 0) {104ERR_raise_data(ERR_LIB_SYS, get_last_socket_error(),105"calling setsockopt()");106BIO_closesocket(fd);107return INVALID_SOCKET;108}109110# else111# if defined(SOCK_CLOEXEC)112socktype |= SOCK_CLOEXEC;113# endif114115fd = BIO_socket(domain, socktype, protocol, 0);116if (fd == INVALID_SOCKET) {117ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),118"calling BIO_socket()");119return INVALID_SOCKET;120}121122/*123* Make socket close-on-exec unless this was already done above at socket124* creation time.125*/126if (!set_cloexec(fd)) {127ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),128"calling set_cloexec()");129BIO_closesocket(fd);130return INVALID_SOCKET;131}132# endif133134return fd;135}136137/*138* The SOCKET notifier method manually creates a connected TCP socket pair by139* temporarily creating a TCP listener on a random port and connecting back to140* it.141*142* Win32 does not support socketpair(2), and Win32 pipes are not compatible with143* Winsock select(2). This means our only means of making select(2) wakeable is144* to artifically create a loopback TCP connection and send bytes to it.145*/146int ossl_rio_notifier_init(RIO_NOTIFIER *nfy)147{148int rc, lfd = -1, rfd = -1, wfd = -1;149struct sockaddr_in sa = {0}, accept_sa;150socklen_t sa_len = sizeof(sa), accept_sa_len = sizeof(accept_sa);151152# if defined(OPENSSL_SYS_WINDOWS)153if (!ensure_wsa_startup()) {154ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR,155"Cannot start Windows sockets");156return 0;157}158# endif159/* Create a close-on-exec socket. */160lfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);161if (lfd == INVALID_SOCKET) {162ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),163"calling create_socket()");164return 0;165}166167/* Bind the socket to a random loopback port. */168sa.sin_family = AF_INET;169sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);170rc = bind(lfd, (const struct sockaddr *)&sa, sizeof(sa));171if (rc < 0) {172ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),173"calling bind()");174goto err;175}176177/* Determine what random port was allocated. */178rc = getsockname(lfd, (struct sockaddr *)&sa, &sa_len);179if (rc < 0) {180ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),181"calling getsockname()");182goto err;183}184185/* Start listening. */186rc = listen(lfd, 1);187if (rc < 0) {188ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),189"calling listen()");190goto err;191}192193/* Create another socket to connect to the listener. */194wfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);195if (wfd == INVALID_SOCKET) {196ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),197"calling create_socket()");198goto err;199}200201/*202* Disable Nagle's algorithm on the writer so that wakeups happen203* immediately.204*/205if (!BIO_set_tcp_ndelay(wfd, 1)) {206ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),207"calling BIO_set_tcp_ndelay()");208goto err;209}210211/*212* Connect the writer to the listener.213*/214rc = connect(wfd, (struct sockaddr *)&sa, sizeof(sa));215if (rc < 0) {216ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),217"calling connect()");218goto err;219}220221/*222* The connection accepted from the listener is the read side.223*/224rfd = accept(lfd, (struct sockaddr *)&accept_sa, &accept_sa_len);225if (rfd == INVALID_SOCKET) {226ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),227"calling accept()");228goto err;229}230231rc = getsockname(wfd, (struct sockaddr *)&sa, &sa_len);232if (rc < 0) {233ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),234"calling getsockname()");235goto err;236}237238/* Close the listener, which we don't need anymore. */239BIO_closesocket(lfd);240lfd = -1;241242/*243* Sanity check - ensure someone else didn't connect to our listener during244* the brief window of possibility above.245*/246if (accept_sa.sin_family != AF_INET || accept_sa.sin_port != sa.sin_port) {247ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR,248"connected address differs from accepted address");249goto err;250}251252/* Make both sides of the connection non-blocking. */253if (!BIO_socket_nbio(rfd, 1)) {254ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),255"calling BIO_socket_nbio()");256goto err;257}258259if (!BIO_socket_nbio(wfd, 1)) {260ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),261"calling BIO_socket_nbio()");262goto err;263}264265nfy->rfd = rfd;266nfy->wfd = wfd;267return 1;268269err:270if (lfd != INVALID_SOCKET)271BIO_closesocket(lfd);272if (wfd != INVALID_SOCKET)273BIO_closesocket(wfd);274if (rfd != INVALID_SOCKET)275BIO_closesocket(rfd);276return 0;277}278279#elif RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKETPAIR280281int ossl_rio_notifier_init(RIO_NOTIFIER *nfy)282{283int fds[2], domain = AF_INET, type = SOCK_STREAM;284285# if defined(SOCK_CLOEXEC)286type |= SOCK_CLOEXEC;287# endif288# if defined(SOCK_NONBLOCK)289type |= SOCK_NONBLOCK;290# endif291292# if defined(OPENSSL_SYS_UNIX) && defined(AF_UNIX)293domain = AF_UNIX;294# endif295296if (socketpair(domain, type, 0, fds) < 0) {297ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),298"calling socketpair()");299return 0;300}301302if (!set_cloexec(fds[0]) || !set_cloexec(fds[1])) {303ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),304"calling set_cloexec()");305goto err;306}307308# if !defined(SOCK_NONBLOCK)309if (!BIO_socket_nbio(fds[0], 1) || !BIO_socket_nbio(fds[1], 1)) {310ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),311"calling BIO_socket_nbio()");312goto err;313}314# endif315316if (domain == AF_INET && !BIO_set_tcp_ndelay(fds[1], 1)) {317ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),318"calling BIO_set_tcp_ndelay()");319goto err;320}321322nfy->rfd = fds[0];323nfy->wfd = fds[1];324return 1;325326err:327BIO_closesocket(fds[1]);328BIO_closesocket(fds[0]);329return 0;330}331332#endif333334void ossl_rio_notifier_cleanup(RIO_NOTIFIER *nfy)335{336if (nfy->rfd < 0)337return;338339BIO_closesocket(nfy->wfd);340BIO_closesocket(nfy->rfd);341nfy->rfd = nfy->wfd = -1;342}343344int ossl_rio_notifier_signal(RIO_NOTIFIER *nfy)345{346static const unsigned char ch = 0;347ossl_ssize_t wr;348349do350/*351* Note: If wr returns 0 the buffer is already full so we don't need to352* do anything.353*/354wr = writesocket(nfy->wfd, (void *)&ch, sizeof(ch));355while (wr < 0 && get_last_socket_error_is_eintr());356357return 1;358}359360int ossl_rio_notifier_unsignal(RIO_NOTIFIER *nfy)361{362unsigned char buf[16];363ossl_ssize_t rd;364365/*366* signal() might have been called multiple times. Drain the buffer until367* it's empty.368*/369do370rd = readsocket(nfy->rfd, (void *)buf, sizeof(buf));371while (rd == sizeof(buf)372|| (rd < 0 && get_last_socket_error_is_eintr()));373374if (rd < 0 && !BIO_fd_non_fatal_error(get_last_socket_error()))375return 0;376377return 1;378}379380381