Coverage Report

Created: 2026-09-12 06:55

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