Coverage Report

Created: 2025-06-13 06:57

/src/openssl/ssl/rio/rio_notifier.c
Line
Count
Source (jump to first uncovered line)
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
 */
20
static int set_cloexec(int fd)
21
0
{
22
#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC)
23
    return fcntl(fd, F_SETFD, FD_CLOEXEC) >= 0;
24
#else
25
0
    return 1;
26
0
#endif
27
0
}
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
34
static void ossl_wsa_cleanup(void)
35
{
36
    if (wsa_started) {
37
        wsa_started = 0;
38
        WSACleanup();
39
    }
40
}
41
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
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. */
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
 */
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
282
int ossl_rio_notifier_init(RIO_NOTIFIER *nfy)
283
0
{
284
0
    int fds[2], domain = AF_INET, type = SOCK_STREAM;
285
286
0
# if defined(SOCK_CLOEXEC)
287
0
    type |= SOCK_CLOEXEC;
288
0
# endif
289
0
# if defined(SOCK_NONBLOCK)
290
0
    type |= SOCK_NONBLOCK;
291
0
# endif
292
293
0
# if defined(OPENSSL_SYS_UNIX) && defined(AF_UNIX)
294
0
    domain = AF_UNIX;
295
0
# endif
296
297
0
    if (socketpair(domain, type, 0, fds) < 0) {
298
0
        ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
299
0
                       "calling socketpair()");
300
0
        return 0;
301
0
    }
302
303
0
    if (!set_cloexec(fds[0]) || !set_cloexec(fds[1])) {
304
0
        ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
305
0
                       "calling set_cloexec()");
306
0
        goto err;
307
0
    }
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
0
    if (domain == AF_INET && !BIO_set_tcp_ndelay(fds[1], 1)) {
318
0
        ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
319
0
                       "calling BIO_set_tcp_ndelay()");
320
0
        goto err;
321
0
    }
322
323
0
    nfy->rfd = fds[0];
324
0
    nfy->wfd = fds[1];
325
0
    return 1;
326
327
0
err:
328
0
    BIO_closesocket(fds[1]);
329
0
    BIO_closesocket(fds[0]);
330
0
    return 0;
331
0
}
332
333
#endif
334
335
void ossl_rio_notifier_cleanup(RIO_NOTIFIER *nfy)
336
0
{
337
0
    if (nfy->rfd < 0)
338
0
        return;
339
340
0
    BIO_closesocket(nfy->wfd);
341
0
    BIO_closesocket(nfy->rfd);
342
0
    nfy->rfd = nfy->wfd = -1;
343
0
}
344
345
int ossl_rio_notifier_signal(RIO_NOTIFIER *nfy)
346
0
{
347
0
    static const unsigned char ch = 0;
348
0
    ossl_ssize_t wr;
349
350
0
    do
351
        /*
352
         * Note: If wr returns 0 the buffer is already full so we don't need to
353
         * do anything.
354
         */
355
0
        wr = writesocket(nfy->wfd, (void *)&ch, sizeof(ch));
356
0
    while (wr < 0 && get_last_socket_error_is_eintr());
357
358
0
    return 1;
359
0
}
360
361
int ossl_rio_notifier_unsignal(RIO_NOTIFIER *nfy)
362
0
{
363
0
    unsigned char buf[16];
364
0
    ossl_ssize_t rd;
365
366
    /*
367
     * signal() might have been called multiple times. Drain the buffer until
368
     * it's empty.
369
     */
370
0
    do
371
0
        rd = readsocket(nfy->rfd, (void *)buf, sizeof(buf));
372
0
    while (rd == sizeof(buf)
373
0
           || (rd < 0 && get_last_socket_error_is_eintr()));
374
375
0
    if (rd < 0 && !BIO_fd_non_fatal_error(get_last_socket_error()))
376
0
        return 0;
377
378
0
    return 1;
379
0
}