mirror of https://github.com/openssl/openssl.git
380 lines
10 KiB
C
380 lines
10 KiB
C
/*
|
|
* 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
|
|
* suppression. 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 artificially 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;
|
|
}
|