Coverage Report

Created: 2025-12-31 06:58

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