Coverage Report

Created: 2026-06-16 06:32

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