Coverage Report

Created: 2026-07-19 06:36

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