/* * 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 #include #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; }