The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
tls_bio_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for the datagram boundaries which the TLS dbuff bio records
18 *
19 * The bio is the boundary between OpenSSL and the application. OpenSSL calls
20 * BIO_write() once per datagram, and a datagram transport has to send each of
21 * those as its own datagram. These tests write to the bio directly, so they
22 * need no socket, no session, and no handshake.
23 *
24 * @file src/lib/tls/test/tls_bio_tests.c
25 *
26 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
27 */
28#include <freeradius-devel/util/test/acutest_common_init.h>
29#include <freeradius-devel/util/test/acutest_helpers.h>
30
31#include <freeradius-devel/tls/bio.h>
32
33/** Build the BIO_METHOD which fr_tls_bio_dbuff_alloc() clones
34 *
35 * The server calls this from fr_tls_init(). These tests do not start a
36 * server, so they call it themselves. acutest runs each test in its own
37 * process, so the flag is per test rather than per run.
38 */
39static void tls_bio_test_init(void)
40{
41 static bool done = false;
42
43 if (done) return;
44
45 TEST_CHECK(fr_tls_bio_init() == 0);
46 done = true;
47}
48
49/** Allocate a bio, without datagram boundaries
50 */
51static BIO *stream_bio_alloc(fr_tls_bio_dbuff_t **out)
52{
54
55 return fr_tls_bio_dbuff_alloc(out, NULL, NULL, 1024, 0, true);
56}
57
58/** Allocate a bio with datagram boundaries turned on
59 */
60static BIO *datagram_bio_alloc(fr_tls_bio_dbuff_t **out)
61{
62 BIO *bio;
63
64 bio = stream_bio_alloc(out);
65 TEST_CHECK(bio != NULL);
66 if (!bio) return NULL;
67
68 TEST_CHECK(fr_tls_bio_dbuff_datagram_init(*out) == 0);
69
70 return bio;
71}
72
73/** Without datagram mode the bio reports no boundaries at all
74 */
76{
77 fr_tls_bio_dbuff_t *bd = NULL;
78 BIO *bio;
79
80 bio = stream_bio_alloc(&bd);
81 TEST_CHECK(bio != NULL);
82 if (!bio) return;
83
84 TEST_CHECK(BIO_write(bio, "hello", 5) == 5);
85 TEST_CHECK(BIO_write(bio, "world", 5) == 5);
86
87 /*
88 * The data is there, the boundaries are not.
89 */
90 TEST_CHECK(fr_dbuff_remaining(fr_tls_bio_dbuff_out(bd)) == 10);
91 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
92
93 talloc_free(bd);
94}
95
96/** One write is one datagram
97 */
99{
100 fr_tls_bio_dbuff_t *bd = NULL;
101 BIO *bio;
102
103 bio = datagram_bio_alloc(&bd);
104 if (!bio) return;
105
106 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
107
108 TEST_CHECK(BIO_write(bio, "hello", 5) == 5);
109 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 5);
110
111 fr_tls_bio_dbuff_datagram_sent(bd);
112 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
113
114 talloc_free(bd);
115}
116
117/** Writes of different lengths come back in the order they were written
118 *
119 * This is the case the whole change exists for: OpenSSL writes a flight as
120 * several datagrams of unequal length, and joining them back together would
121 * produce one datagram larger than the MTU OpenSSL was given.
122 */
124{
125 fr_tls_bio_dbuff_t *bd = NULL;
126 BIO *bio;
127 static size_t const len[] = { 71, 1103, 314, 308, 12 };
128 uint8_t buf[1200];
129 size_t i;
130
131 bio = datagram_bio_alloc(&bd);
132 if (!bio) return;
133
134 memset(buf, 0xa5, sizeof(buf));
135
136 for (i = 0; i < NUM_ELEMENTS(len); i++) {
137 TEST_CHECK(BIO_write(bio, (char const *) buf, (int) len[i]) == (int) len[i]);
138 }
139
140 for (i = 0; i < NUM_ELEMENTS(len); i++) {
141 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), len[i]);
142 fr_tls_bio_dbuff_datagram_sent(bd);
143 }
144
145 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
146
147 talloc_free(bd);
148}
149
150/** A flight which is fully drained is followed by another
151 *
152 * This is what actually happens on a connection: OpenSSL writes a flight,
153 * the application sends all of it, and the next flight arrives later. The
154 * queue has to be reusable rather than fill up once.
155 */
157{
158 fr_tls_bio_dbuff_t *bd = NULL;
159 BIO *bio;
160 uint8_t buf[64];
161 size_t i;
162
163 bio = datagram_bio_alloc(&bd);
164 if (!bio) return;
165
166 memset(buf, 0, sizeof(buf));
167
168 for (i = 0; i < 16; i++) {
169 size_t flight;
170
171 /*
172 * Two datagrams, drained to nothing, over and over.
173 * The lengths change each time round so that a stale
174 * entry does not read as a correct one.
175 */
176 for (flight = 0; flight < 2; flight++) {
177 size_t len = (i * 2) + flight + 1;
178
179 TEST_CHECK(BIO_write(bio, (char const *) buf, (int) len) == (int) len);
180 }
181
182 for (flight = 0; flight < 2; flight++) {
183 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), (i * 2) + flight + 1);
184 fr_tls_bio_dbuff_datagram_sent(bd);
185 }
186
187 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
188 }
189
190 talloc_free(bd);
191}
192
193/** Draining part of a flight leaves the rest in order
194 */
195static void test_partial_drain(void)
196{
197 fr_tls_bio_dbuff_t *bd = NULL;
198 BIO *bio;
199 uint8_t buf[64];
200
201 bio = datagram_bio_alloc(&bd);
202 if (!bio) return;
203
204 memset(buf, 0, sizeof(buf));
205
206 TEST_CHECK(BIO_write(bio, (char const *) buf, 10) == 10);
207 TEST_CHECK(BIO_write(bio, (char const *) buf, 20) == 20);
208
209 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 10);
210 fr_tls_bio_dbuff_datagram_sent(bd);
211
212 /*
213 * A write which arrives while a datagram is still queued must
214 * not disturb the one which is waiting.
215 */
216 TEST_CHECK(BIO_write(bio, (char const *) buf, 30) == 30);
217
218 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 20);
219 fr_tls_bio_dbuff_datagram_sent(bd);
220 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 30);
221 fr_tls_bio_dbuff_datagram_sent(bd);
222 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
223
224 talloc_free(bd);
225}
226
227/** Sending more datagrams than were written is harmless
228 */
229static void test_over_send(void)
230{
231 fr_tls_bio_dbuff_t *bd = NULL;
232 BIO *bio;
233
234 bio = datagram_bio_alloc(&bd);
235 if (!bio) return;
236
237 fr_tls_bio_dbuff_datagram_sent(bd);
238 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
239
240 TEST_CHECK(BIO_write(bio, "x", 1) == 1);
241 fr_tls_bio_dbuff_datagram_sent(bd);
242 fr_tls_bio_dbuff_datagram_sent(bd);
243 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
244
245 talloc_free(bd);
246}
247
248/** Vary the datagram length, so that a mis-ordered queue is visible
249 *
250 * A test which writes the same length every time cannot tell a correctly
251 * ordered queue from a shuffled one. The period is 7 and the test below
252 * drains 3, so a queue which is off by 3 reads differently at every entry.
253 * A period which divides the drain hides exactly the bug this is here to
254 * find.
255 */
256static size_t cap_test_len(size_t i)
257{
258 return (i % 7) + 1;
259}
260
261/** The queue is capped, a write past the cap fails, and draining makes room
262 *
263 * Filling the queue and then draining part of it is what makes the entries
264 * wrap, so this is also the test which covers moving them back down.
265 */
266static void test_queue_cap(void)
267{
268 fr_tls_bio_dbuff_t *bd = NULL;
269 BIO *bio;
270 size_t i;
271 uint8_t buf[16]; /* post-drain writes are >8 bytes */
272
273 bio = datagram_bio_alloc(&bd);
274 if (!bio) return;
275
276 memset(buf, 0, sizeof(buf));
277
278 for (i = 0; i < FR_TLS_MAX_DATAGRAMS; i++) {
279 TEST_CHECK(BIO_write(bio, (char const *) buf, (int) cap_test_len(i)) == (int) cap_test_len(i));
280 }
281
282 /*
283 * Nothing has been drained, so there is nowhere to put the
284 * next boundary.
285 */
286 TEST_CHECK(BIO_write(bio, (char const *) buf, 1) <= 0);
287
288 /*
289 * Draining three makes room for three more, and the three new
290 * ones go behind the 253 which are still queued.
291 */
292 for (i = 0; i < 3; i++) {
293 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), cap_test_len(i));
294 fr_tls_bio_dbuff_datagram_sent(bd);
295 }
296
297 for (i = 0; i < 3; i++) {
298 TEST_CHECK(BIO_write(bio, (char const *) buf, 7 + (int) i) == 7 + (int) i);
299 }
300
301 /*
302 * What is left is entries 3..255 of the first run, in order,
303 * and then the three which were written after the drain.
304 */
305 for (i = 3; i < FR_TLS_MAX_DATAGRAMS; i++) {
306 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), cap_test_len(i));
307 fr_tls_bio_dbuff_datagram_sent(bd);
308 }
309
310 for (i = 0; i < 3; i++) {
311 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 7 + i);
312 fr_tls_bio_dbuff_datagram_sent(bd);
313 }
314
315 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
316
317 talloc_free(bd);
318}
319
320/** A datagram which does not fit records no boundary at all
321 *
322 * The buffer here cannot extend, so a write larger than the room left copies
323 * part of the data. Half a datagram is not a datagram, so the write has to
324 * fail rather than queue a length with nothing behind it.
325 */
327{
328 fr_tls_bio_dbuff_t *bd = NULL;
329 BIO *bio;
330 uint8_t buf[128];
331
333
334 /*
335 * init == max, so the buffer is 64 octets and stays that way.
336 */
337 bio = fr_tls_bio_dbuff_alloc(&bd, NULL, NULL, 64, 64, true);
338 TEST_CHECK(bio != NULL);
339 if (!bio) return;
340
341 TEST_CHECK(fr_tls_bio_dbuff_datagram_init(bd) == 0);
342
343 memset(buf, 0, sizeof(buf));
344
345 TEST_CHECK(BIO_write(bio, (char const *) buf, 40) == 40);
346 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 40);
347
348 /*
349 * Only 24 octets of room are left, so this cannot be buffered
350 * whole.
351 */
352 TEST_CHECK(BIO_write(bio, (char const *) buf, 100) <= 0);
353
354 /*
355 * The first datagram is still the one at the head, and the
356 * write which failed left no second entry behind it.
357 */
358 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 40);
359 fr_tls_bio_dbuff_datagram_sent(bd);
360 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
361
362 talloc_free(bd);
363}
364
365/** Clearing the buffer discards the boundaries with the data
366 */
368{
369 fr_tls_bio_dbuff_t *bd = NULL;
370 BIO *bio;
371
372 bio = datagram_bio_alloc(&bd);
373 if (!bio) return;
374
375 TEST_CHECK(BIO_write(bio, "hello", 5) == 5);
376 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 5);
377
378 fr_tls_bio_dbuff_clear(bd);
379
380 TEST_CHECK(fr_dbuff_remaining(fr_tls_bio_dbuff_out(bd)) == 0);
381 TEST_CHECK(fr_tls_bio_dbuff_datagram_len(bd) == 0);
382
383 talloc_free(bd);
384}
385
386/** Enabling datagram mode twice is not an error
387 */
389{
390 fr_tls_bio_dbuff_t *bd = NULL;
391 BIO *bio;
392
393 bio = datagram_bio_alloc(&bd);
394 if (!bio) return;
395
396 TEST_CHECK(BIO_write(bio, "hello", 5) == 5);
397 TEST_CHECK(fr_tls_bio_dbuff_datagram_init(bd) == 0);
398
399 /*
400 * The second call must not throw away what is already queued.
401 */
402 TEST_CHECK_LEN(fr_tls_bio_dbuff_datagram_len(bd), 5);
403
404 talloc_free(bd);
405}
406
408 { "stream_has_no_boundaries", test_stream_has_no_boundaries },
409 { "one_write_one_datagram", test_one_write_one_datagram },
410 { "flight_keeps_its_boundaries", test_flight_keeps_its_boundaries },
411 { "flight_after_flight", test_flight_after_flight },
412 { "partial_drain", test_partial_drain },
413 { "over_send", test_over_send },
414 { "queue_cap", test_queue_cap },
415 { "partial_write_records_nothing", test_partial_write_records_nothing },
416 { "clear_discards_boundaries", test_clear_discards_boundaries },
417 { "enable_is_idempotent", test_enable_is_idempotent },
419};
#define TEST_CHECK(cond)
Definition acutest.h:101
#define TEST_TERMINATOR
Definition acutest.h:77
#define TEST_CHECK_LEN(_got, _exp)
#define NUM_ELEMENTS(_t)
Definition build.h:437
#define fr_dbuff_remaining(_dbuff_or_marker)
Return the number of bytes remaining between the dbuff or marker and the end of the buffer.
Definition dbuff.h:795
talloc_free(hp)
unsigned char uint8_t
static bool done
Definition radclient.c:80
static void test_partial_drain(void)
Draining part of a flight leaves the rest in order.
static BIO * datagram_bio_alloc(fr_tls_bio_dbuff_t **out)
Allocate a bio with datagram boundaries turned on.
static void test_queue_cap(void)
The queue is capped, a write past the cap fails, and draining makes room.
TEST_LIST
static void test_clear_discards_boundaries(void)
Clearing the buffer discards the boundaries with the data.
static void tls_bio_test_init(void)
Build the BIO_METHOD which fr_tls_bio_dbuff_alloc() clones.
static void test_one_write_one_datagram(void)
One write is one datagram.
static BIO * stream_bio_alloc(fr_tls_bio_dbuff_t **out)
Allocate a bio, without datagram boundaries.
static void test_flight_keeps_its_boundaries(void)
Writes of different lengths come back in the order they were written.
static size_t cap_test_len(size_t i)
Vary the datagram length, so that a mis-ordered queue is visible.
static void test_enable_is_idempotent(void)
Enabling datagram mode twice is not an error.
static void test_stream_has_no_boundaries(void)
Without datagram mode the bio reports no boundaries at all.
static void test_flight_after_flight(void)
A flight which is fully drained is followed by another.
static void test_over_send(void)
Sending more datagrams than were written is harmless.
static void test_partial_write_records_nothing(void)
A datagram which does not fit records no boundary at all.
static fr_sbuff_err_t char ** out
Definition value.h:1061
static fr_sbuff_err_t char size_t * len
Definition value.h:1061