xref: /freebsd/crypto/openssl/ssl/rio/rio_notifier.c (revision e7be843b4a162e68651d3911f0357ed464915629)
1 /*
2  * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9 
10 #include "internal/sockets.h"
11 #include <openssl/bio.h>
12 #include <openssl/err.h>
13 #include "internal/thread_once.h"
14 #include "internal/rio_notifier.h"
15 
16 /*
17  * Sets a socket as close-on-exec, except that this is a no-op if we are certain
18  * we do not need to do this or the OS does not support the concept.
19  */
set_cloexec(int fd)20 static int set_cloexec(int fd)
21 {
22 #if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC)
23     return fcntl(fd, F_SETFD, FD_CLOEXEC) >= 0;
24 #else
25     return 1;
26 #endif
27 }
28 
29 #if defined(OPENSSL_SYS_WINDOWS)
30 
31 static CRYPTO_ONCE ensure_wsa_startup_once = CRYPTO_ONCE_STATIC_INIT;
32 static int wsa_started;
33 
ossl_wsa_cleanup(void)34 static void ossl_wsa_cleanup(void)
35 {
36     if (wsa_started) {
37         wsa_started = 0;
38         WSACleanup();
39     }
40 }
41 
DEFINE_RUN_ONCE_STATIC(do_wsa_startup)42 DEFINE_RUN_ONCE_STATIC(do_wsa_startup)
43 {
44     WORD versionreq = 0x0202; /* Version 2.2 */
45     WSADATA wsadata;
46 
47     if (WSAStartup(versionreq, &wsadata) != 0)
48         return 0;
49     wsa_started = 1;
50     OPENSSL_atexit(ossl_wsa_cleanup);
51     return 1;
52 }
53 
ensure_wsa_startup(void)54 static ossl_inline int ensure_wsa_startup(void)
55 {
56     return RUN_ONCE(&ensure_wsa_startup_once, do_wsa_startup);
57 }
58 
59 #endif
60 
61 #if RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKET
62 
63 /* Create a close-on-exec socket. */
create_socket(int domain,int socktype,int protocol)64 static int create_socket(int domain, int socktype, int protocol)
65 {
66     int fd;
67 # if defined(OPENSSL_SYS_WINDOWS)
68     static const int on = 1;
69 
70     /*
71      * Use WSASocketA to create a socket which is immediately marked as
72      * non-inheritable, avoiding race conditions if another thread is about to
73      * call CreateProcess.
74      * NOTE: windows xp (0x501) doesn't support the non-inheritance flag here
75      * but preventing inheritance isn't mandatory, just a safety precaution
76      * so we can get away with not including it for older platforms
77      */
78 
79 #  ifdef WSA_FLAG_NO_HANDLE_INHERIT
80     fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0,
81                          WSA_FLAG_NO_HANDLE_INHERIT);
82 
83     /*
84      * Its also possible that someone is building a binary on a newer windows
85      * SDK, but running it on a runtime that doesn't support inheritance
86      * supression.  In that case the above will return INVALID_SOCKET, and
87      * our response for those older platforms is to try the call again
88      * without the flag
89      */
90     if (fd == INVALID_SOCKET)
91         fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0);
92 #  else
93     fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0);
94 #  endif
95     if (fd == INVALID_SOCKET) {
96         int err = get_last_socket_error();
97 
98         ERR_raise_data(ERR_LIB_SYS, err,
99                        "calling WSASocketA() = %d", err);
100         return INVALID_SOCKET;
101     }
102 
103     /* Prevent interference with the socket from other processes on Windows. */
104     if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *)&on, sizeof(on)) < 0) {
105         ERR_raise_data(ERR_LIB_SYS, get_last_socket_error(),
106                        "calling setsockopt()");
107         BIO_closesocket(fd);
108         return INVALID_SOCKET;
109     }
110 
111 # else
112 #  if defined(SOCK_CLOEXEC)
113     socktype |= SOCK_CLOEXEC;
114 #  endif
115 
116     fd = BIO_socket(domain, socktype, protocol, 0);
117     if (fd == INVALID_SOCKET) {
118         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
119                        "calling BIO_socket()");
120         return INVALID_SOCKET;
121     }
122 
123     /*
124      * Make socket close-on-exec unless this was already done above at socket
125      * creation time.
126      */
127     if (!set_cloexec(fd)) {
128         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
129                        "calling set_cloexec()");
130         BIO_closesocket(fd);
131         return INVALID_SOCKET;
132     }
133 # endif
134 
135     return fd;
136 }
137 
138 /*
139  * The SOCKET notifier method manually creates a connected TCP socket pair by
140  * temporarily creating a TCP listener on a random port and connecting back to
141  * it.
142  *
143  * Win32 does not support socketpair(2), and Win32 pipes are not compatible with
144  * Winsock select(2). This means our only means of making select(2) wakeable is
145  * to artifically create a loopback TCP connection and send bytes to it.
146  */
ossl_rio_notifier_init(RIO_NOTIFIER * nfy)147 int ossl_rio_notifier_init(RIO_NOTIFIER *nfy)
148 {
149     int rc, lfd = -1, rfd = -1, wfd = -1;
150     struct sockaddr_in sa = {0}, accept_sa;
151     socklen_t sa_len = sizeof(sa), accept_sa_len = sizeof(accept_sa);
152 
153 # if defined(OPENSSL_SYS_WINDOWS)
154     if (!ensure_wsa_startup()) {
155         ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR,
156                        "Cannot start Windows sockets");
157         return 0;
158     }
159 # endif
160     /* Create a close-on-exec socket. */
161     lfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
162     if (lfd == INVALID_SOCKET) {
163         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
164                        "calling create_socket()");
165         return 0;
166     }
167 
168     /* Bind the socket to a random loopback port. */
169     sa.sin_family       = AF_INET;
170     sa.sin_addr.s_addr  = htonl(INADDR_LOOPBACK);
171     rc = bind(lfd, (const struct sockaddr *)&sa, sizeof(sa));
172     if (rc < 0) {
173         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
174                        "calling bind()");
175         goto err;
176     }
177 
178     /* Determine what random port was allocated. */
179     rc = getsockname(lfd, (struct sockaddr *)&sa, &sa_len);
180     if (rc < 0) {
181         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
182                        "calling getsockname()");
183         goto err;
184     }
185 
186     /* Start listening. */
187     rc = listen(lfd, 1);
188     if (rc < 0) {
189         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
190                        "calling listen()");
191         goto err;
192     }
193 
194     /* Create another socket to connect to the listener. */
195     wfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
196     if (wfd == INVALID_SOCKET) {
197         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
198                        "calling create_socket()");
199         goto err;
200     }
201 
202     /*
203      * Disable Nagle's algorithm on the writer so that wakeups happen
204      * immediately.
205      */
206     if (!BIO_set_tcp_ndelay(wfd, 1)) {
207         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
208                        "calling BIO_set_tcp_ndelay()");
209         goto err;
210     }
211 
212     /*
213      * Connect the writer to the listener.
214      */
215     rc = connect(wfd, (struct sockaddr *)&sa, sizeof(sa));
216     if (rc < 0) {
217         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
218                        "calling connect()");
219         goto err;
220     }
221 
222     /*
223      * The connection accepted from the listener is the read side.
224      */
225     rfd = accept(lfd, (struct sockaddr *)&accept_sa, &accept_sa_len);
226     if (rfd == INVALID_SOCKET) {
227         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
228                        "calling accept()");
229         goto err;
230     }
231 
232     rc = getsockname(wfd, (struct sockaddr *)&sa, &sa_len);
233     if (rc < 0) {
234         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
235                        "calling getsockname()");
236         goto err;
237     }
238 
239     /* Close the listener, which we don't need anymore. */
240     BIO_closesocket(lfd);
241     lfd = -1;
242 
243     /*
244      * Sanity check - ensure someone else didn't connect to our listener during
245      * the brief window of possibility above.
246      */
247     if (accept_sa.sin_family != AF_INET || accept_sa.sin_port != sa.sin_port) {
248         ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR,
249                        "connected address differs from accepted address");
250         goto err;
251     }
252 
253     /* Make both sides of the connection non-blocking. */
254     if (!BIO_socket_nbio(rfd, 1)) {
255         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
256                        "calling BIO_socket_nbio()");
257         goto err;
258     }
259 
260     if (!BIO_socket_nbio(wfd, 1)) {
261         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
262                        "calling BIO_socket_nbio()");
263         goto err;
264     }
265 
266     nfy->rfd = rfd;
267     nfy->wfd = wfd;
268     return 1;
269 
270 err:
271     if (lfd != INVALID_SOCKET)
272         BIO_closesocket(lfd);
273     if (wfd != INVALID_SOCKET)
274         BIO_closesocket(wfd);
275     if (rfd != INVALID_SOCKET)
276         BIO_closesocket(rfd);
277     return 0;
278 }
279 
280 #elif RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKETPAIR
281 
ossl_rio_notifier_init(RIO_NOTIFIER * nfy)282 int ossl_rio_notifier_init(RIO_NOTIFIER *nfy)
283 {
284     int fds[2], domain = AF_INET, type = SOCK_STREAM;
285 
286 # if defined(SOCK_CLOEXEC)
287     type |= SOCK_CLOEXEC;
288 # endif
289 # if defined(SOCK_NONBLOCK)
290     type |= SOCK_NONBLOCK;
291 # endif
292 
293 # if defined(OPENSSL_SYS_UNIX) && defined(AF_UNIX)
294     domain = AF_UNIX;
295 # endif
296 
297     if (socketpair(domain, type, 0, fds) < 0) {
298         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
299                        "calling socketpair()");
300         return 0;
301     }
302 
303     if (!set_cloexec(fds[0]) || !set_cloexec(fds[1])) {
304         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
305                        "calling set_cloexec()");
306         goto err;
307     }
308 
309 # if !defined(SOCK_NONBLOCK)
310     if (!BIO_socket_nbio(fds[0], 1) || !BIO_socket_nbio(fds[1], 1)) {
311         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
312                        "calling BIO_socket_nbio()");
313         goto err;
314     }
315 # endif
316 
317     if (domain == AF_INET && !BIO_set_tcp_ndelay(fds[1], 1)) {
318         ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
319                        "calling BIO_set_tcp_ndelay()");
320         goto err;
321     }
322 
323     nfy->rfd = fds[0];
324     nfy->wfd = fds[1];
325     return 1;
326 
327 err:
328     BIO_closesocket(fds[1]);
329     BIO_closesocket(fds[0]);
330     return 0;
331 }
332 
333 #endif
334 
ossl_rio_notifier_cleanup(RIO_NOTIFIER * nfy)335 void ossl_rio_notifier_cleanup(RIO_NOTIFIER *nfy)
336 {
337     if (nfy->rfd < 0)
338         return;
339 
340     BIO_closesocket(nfy->wfd);
341     BIO_closesocket(nfy->rfd);
342     nfy->rfd = nfy->wfd = -1;
343 }
344 
ossl_rio_notifier_signal(RIO_NOTIFIER * nfy)345 int ossl_rio_notifier_signal(RIO_NOTIFIER *nfy)
346 {
347     static const unsigned char ch = 0;
348     ossl_ssize_t wr;
349 
350     do
351         /*
352          * Note: If wr returns 0 the buffer is already full so we don't need to
353          * do anything.
354          */
355         wr = writesocket(nfy->wfd, (void *)&ch, sizeof(ch));
356     while (wr < 0 && get_last_socket_error_is_eintr());
357 
358     return 1;
359 }
360 
ossl_rio_notifier_unsignal(RIO_NOTIFIER * nfy)361 int ossl_rio_notifier_unsignal(RIO_NOTIFIER *nfy)
362 {
363     unsigned char buf[16];
364     ossl_ssize_t rd;
365 
366     /*
367      * signal() might have been called multiple times. Drain the buffer until
368      * it's empty.
369      */
370     do
371         rd = readsocket(nfy->rfd, (void *)buf, sizeof(buf));
372     while (rd == sizeof(buf)
373            || (rd < 0 && get_last_socket_error_is_eintr()));
374 
375     if (rd < 0 && !BIO_fd_non_fatal_error(get_last_socket_error()))
376         return 0;
377 
378     return 1;
379 }
380