Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/tls/bio.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program 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
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/**
18
 * $Id: 5b51eed1726e45e4874e9df0ddf8e3f7807278b6 $
19
 *
20
 * @file tls/bio.c
21
 * @brief Custom BIOs to pass to OpenSSL's functions
22
 *
23
 * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24
 */
25
RCSID("$Id: 5b51eed1726e45e4874e9df0ddf8e3f7807278b6 $")
26
USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
27
28
#ifdef WITH_TLS
29
#include <freeradius-devel/util/atexit.h>
30
31
#include "bio.h"
32
33
/** Holds the state of a talloc aggregation 'write' BIO
34
 *
35
 * With these BIOs OpenSSL is the producer, and we're the consumer.
36
 */
37
struct fr_tls_bio_dbuff_s {
38
  BIO     *bio;   //!< Logging bio to write to.
39
  fr_dbuff_t    dbuff_in; //!< dbuff used to write data to our talloced buffer.
40
  fr_dbuff_t    dbuff_out;  //!< dbuff used to read data from our talloced buffer.
41
  fr_dbuff_uctx_talloc_t  tctx;   //!< extra talloc information for the dbuff.
42
  bool      free_buff;  //!< Free the talloced buffer when this structure is freed.
43
};
44
45
/** Template for the thread local request log BIOs
46
 */
47
static BIO_METHOD *tls_bio_talloc_meth;
48
49
/** Thread local aggregation BIO
50
 */
51
static _Thread_local  fr_tls_bio_dbuff_t    *tls_bio_talloc_agg;
52
53
/** Aggregates BIO_write() calls into a talloc'd buffer
54
 *
55
 * @param[in] bio that was written to.
56
 * @param[in] in  data being written to BIO.
57
 * @param[in] len Length of data being written.
58
 */
59
static int _tls_bio_talloc_write_cb(BIO *bio, char const *in, int len)
60
0
{
61
0
  fr_tls_bio_dbuff_t  *bd = talloc_get_type_abort(BIO_get_data(bio), fr_tls_bio_dbuff_t);
62
63
0
  fr_assert_msg(bd->dbuff_in.buff, "BIO not initialised");
64
65
  /*
66
   *  Shift out any data which has been read
67
   *  since we were last called.
68
   */
69
0
  fr_dbuff_shift(&bd->dbuff_in, fr_dbuff_used(&bd->dbuff_out));
70
71
0
  return fr_dbuff_in_memcpy_partial(&bd->dbuff_in, (uint8_t const *)in, (size_t)len);
72
0
}
73
74
/** Aggregates BIO_puts() calls into a talloc'd buffer
75
 *
76
 * @param[in] bio that was written to.
77
 * @param[in] in  data being written to BIO.
78
 */
79
static int _tls_bio_talloc_puts_cb(BIO *bio, char const *in)
80
0
{
81
0
  return _tls_bio_talloc_write_cb(bio, in, strlen(in));
82
0
}
83
84
/** Serves BIO_read() from a talloced buffer
85
 *
86
 * @param[in] bio performing the read operation.
87
 * @param[out] buf  to write data to.
88
 * @param[in] size  of data to write (maximum).
89
 * @return The amount of data written.
90
 */
91
static int _tls_bio_talloc_read_cb(BIO *bio, char *buf, int size)
92
{
93
  fr_tls_bio_dbuff_t  *bd = talloc_get_type_abort(BIO_get_data(bio), fr_tls_bio_dbuff_t);
94
  size_t      to_copy;
95
  ssize_t     slen;
96
97
  fr_assert_msg(bd->dbuff_out.buff, "BIO not initialised");
98
99
  to_copy = fr_dbuff_remaining(&bd->dbuff_out);
100
  if (to_copy > (size_t)size) to_copy = (size_t)size;
101
102
  slen = fr_dbuff_out_memcpy((uint8_t *)buf, &bd->dbuff_out, to_copy);
103
  if (!fr_cond_assert(slen >= 0)) { /* Shouldn't happen */
104
    buf[0] = '\0';
105
    return (int)slen;
106
  }
107
108
  fr_dbuff_shift(&bd->dbuff_in, (size_t)slen);  /* Shift contents */
109
110
  return (int)slen;
111
}
112
113
/** Serves BIO_gets() from a talloced buffer
114
 *
115
 * Writes all data up to size, or up to and including the next \n to
116
 * the provided buffer.
117
 *
118
 * @param[in] bio performing the gets operation.
119
 * @param[out] buf  to write data to.
120
 * @param[in] size  of data to write (maximum).
121
 * @return The amount of data written.
122
 */
123
static int _tls_bio_talloc_gets_cb(BIO *bio, char *buf, int size)
124
{
125
  fr_tls_bio_dbuff_t  *bd = talloc_get_type_abort(BIO_get_data(bio), fr_tls_bio_dbuff_t);
126
  size_t      to_copy;
127
  uint8_t     *p;
128
  ssize_t     slen;
129
130
  fr_assert_msg(bd->dbuff_out.buff, "BIO not initialised");
131
132
  /*
133
   *  Deal with stupid corner case
134
   */
135
  if (unlikely(size == 1)) {
136
    buf[0] = '\0';
137
    return 0;
138
  } else if (unlikely(size == 0)) {
139
    return 0;
140
  }
141
142
  /*
143
   *  Copy up to the next line, or the end of the buffer
144
   */
145
  p = memchr(fr_dbuff_current(&bd->dbuff_out), '\n', fr_dbuff_remaining(&bd->dbuff_out));
146
  if (!p) {
147
    to_copy = fr_dbuff_remaining(&bd->dbuff_out);
148
  } else {
149
    to_copy = (p - fr_dbuff_current(&bd->dbuff_out)) + 1; /* Preserve the \n as per BIO_read() man page */
150
  }
151
152
  if (to_copy >= (size_t)size) to_copy = (size_t)size - 1; /* Space for \0 */
153
154
  slen = fr_dbuff_out_memcpy((uint8_t *)buf, &bd->dbuff_out, to_copy);
155
  if (!fr_cond_assert(slen >= 0)) { /* Shouldn't happen */
156
    buf[0] = '\0';
157
    return (int)slen;
158
  }
159
160
  buf[to_copy] = '\0';
161
  fr_dbuff_shift(&bd->dbuff_in, (size_t)slen);  /* Shift contents */
162
163
  return (int)to_copy;
164
}
165
166
/** Finalise a talloc aggregation buffer, returning the underlying talloc array holding the data
167
 *
168
 * @return
169
 *  - NULL if the aggregation buffer wasn't initialised.
170
 *  - A talloc_array holding the aggregated data.
171
 */
172
uint8_t *fr_tls_bio_dbuff_finalise(fr_tls_bio_dbuff_t *bd)
173
0
{
174
0
  uint8_t *buff;
175
176
0
  if (unlikely(!bd)) return NULL;
177
0
  if (unlikely(!bd->dbuff_in.buff)) return NULL;
178
179
0
  fr_dbuff_trim_talloc(&bd->dbuff_in, SIZE_MAX);
180
181
0
  buff = bd->dbuff_in.buff;
182
0
  bd->dbuff_in.buff = NULL;
183
0
  bd->dbuff_out.buff = NULL;
184
0
  return buff;
185
0
}
186
187
/** Finalise a talloc aggregation buffer, returning the underlying talloc array holding the data
188
 *
189
 * @return
190
 *  - NULL if the aggregation buffer wasn't initialised.
191
 *  - A talloc_array holding the aggregated data.
192
 */
193
char *fr_tls_bio_dbuff_finalise_bstr(fr_tls_bio_dbuff_t *bd)
194
0
{
195
0
  uint8_t *buff;
196
197
0
  if (unlikely(!bd)) return NULL;
198
0
  if (unlikely(!bd->dbuff_in.buff)) return NULL;
199
200
0
  if (fr_dbuff_in_bytes(&bd->dbuff_in, 0x00) <= 0) return NULL;
201
0
  fr_dbuff_trim_talloc(&bd->dbuff_in, SIZE_MAX);
202
203
0
  buff = bd->dbuff_in.buff;
204
0
  bd->dbuff_in.buff = NULL;
205
0
  bd->dbuff_out.buff = NULL;
206
0
  talloc_set_type(buff, char);
207
208
0
  return (char *)buff;
209
0
}
210
211
/* Reset pointer positions for in/out
212
 *
213
 * Leaves the underlying buffer intact to avoid useless free/malloc.
214
 */
215
void fr_tls_bio_dbuff_reset(fr_tls_bio_dbuff_t *bd)
216
0
{
217
0
  fr_dbuff_set_to_start(&bd->dbuff_in);
218
0
}
219
220
/** Free the underlying BIO, and the buffer if it wasn't finalised
221
 *
222
 */
223
static int _fr_tls_bio_dbuff_free(fr_tls_bio_dbuff_t *bd)
224
0
{
225
0
  BIO_free(bd->bio);
226
0
  if (bd->free_buff) fr_dbuff_free_talloc(&bd->dbuff_out);
227
228
0
  return 0;
229
0
}
230
231
/** Return the output dbuff
232
 *
233
 */
234
fr_dbuff_t *fr_tls_bio_dbuff_out(fr_tls_bio_dbuff_t *bd)
235
0
{
236
0
  return &bd->dbuff_out;
237
0
}
238
239
/** Return the input dbuff
240
 *
241
 */
242
fr_dbuff_t *fr_tls_bio_dbuff_in(fr_tls_bio_dbuff_t *bd)
243
0
{
244
0
  return &bd->dbuff_in;
245
0
}
246
247
/** Allocate a new BIO/talloc buffer
248
 *
249
 * @param[out] out  Where to write a pointer to the #fr_tls_bio_dbuff_t.
250
 *      When this structure is freed the underlying BIO *
251
 *      will also be freed. May be NULL.
252
 * @param[in] bio_ctx to allocate the BIO and wrapper struct in. May be NULL.
253
 * @param[in] buff_ctx  to allocate the expanding buffer in. May be NULL.
254
 * @param[in] init  how much memory to allocate initially.
255
 * @param[in] max the maximum amount of memory to allocate (0 for unlimited).
256
 * @param[in] free_buff free the talloced buffer when the #fr_tls_bio_dbuff_t is
257
 *      freed.
258
 * @return
259
 *  - A new BIO - Do not free manually, free the #fr_tls_bio_dbuff_t or
260
 *    the ctx containing it instead.
261
 */
262
BIO *fr_tls_bio_dbuff_alloc(fr_tls_bio_dbuff_t **out, TALLOC_CTX *bio_ctx, TALLOC_CTX *buff_ctx,
263
           size_t init, size_t max, bool free_buff)
264
0
{
265
0
  fr_tls_bio_dbuff_t  *bd;
266
267
0
  MEM(bd = talloc_zero(bio_ctx, fr_tls_bio_dbuff_t));
268
0
  MEM(bd->bio = BIO_new(tls_bio_talloc_meth));
269
0
  BIO_set_data(bd->bio, bd);
270
271
  /*
272
   *  Initialise the dbuffs
273
   */
274
0
  MEM(fr_dbuff_init_talloc(buff_ctx, &bd->dbuff_out, &bd->tctx, init, max)); /* Where we read from */
275
0
  bd->dbuff_in = FR_DBUFF_BIND_END_ABS(&bd->dbuff_out);       /* Where we write to */
276
0
  bd->dbuff_out.is_const = 1;
277
0
  bd->free_buff = free_buff;
278
279
0
  talloc_set_destructor(bd, _fr_tls_bio_dbuff_free);
280
281
0
  if (out) *out = bd;
282
283
0
  return bd->bio;
284
0
}
285
286
/** Finalise a talloc aggregation buffer, returning the underlying talloc array holding the data
287
 *
288
 * @return
289
 *  - NULL if the aggregation buffer wasn't initialised.
290
 *  - A talloc_array holding the aggregated data.
291
 */
292
uint8_t *fr_tls_bio_dbuff_thread_local_finalise(void)
293
0
{
294
0
  return fr_tls_bio_dbuff_finalise(tls_bio_talloc_agg);
295
0
}
296
297
/** Finalise a talloc aggregation buffer, returning the underlying talloc array holding the data
298
 *
299
 * This variant adds an additional \0 byte, and sets the talloc chunk type to char.
300
 *
301
 * @return
302
 *  - NULL if the aggregation buffer wasn't initialised.
303
 *  - A talloc_array holding the aggregated data.
304
 */
305
char *fr_tls_bio_dbuff_thread_local_finalise_bstr(void)
306
0
{
307
0
  return fr_tls_bio_dbuff_finalise_bstr(tls_bio_talloc_agg);
308
0
}
309
310
/** Discard any data in a talloc aggregation buffer
311
 *
312
 * fr_tls_bio_dbuff_thread_local must be called again before using the BIO
313
 */
314
void fr_tls_bio_dbuff_thread_local_clear(void)
315
0
{
316
0
  fr_tls_bio_dbuff_t *bd = tls_bio_talloc_agg;
317
318
0
  if (unlikely(!bd)) return;
319
0
  if (unlikely(!bd->dbuff_in.buff)) return;
320
321
0
  fr_dbuff_free_talloc(&bd->dbuff_in);
322
0
}
323
324
/** Frees the thread local TALLOC bio and its underlying OpenSSL BIO *
325
 *
326
 */
327
static int _fr_tls_bio_dbuff_thread_local_free(void *bio_talloc_agg)
328
0
{
329
0
  fr_tls_bio_dbuff_t  *our_bio_talloc_agg = talloc_get_type_abort(bio_talloc_agg, fr_tls_bio_dbuff_t);
330
331
0
  return talloc_free(our_bio_talloc_agg);      /* Frees the #fr_tls_bio_dbuff_t and BIO */
332
0
}
333
334
/** Return a BIO which will aggregate data in an expandable talloc buffer
335
 *
336
 * @note Only one of these BIOs may be in use at a given time.
337
 *
338
 * @param[in] init  how much memory to allocate initially.
339
 * @param[in] max the maximum amount of memory to allocate (0 for unlimited).
340
 * @return A thread local BIO to pass to OpenSSL logging functions.
341
 */
342
BIO *fr_tls_bio_dbuff_thread_local(TALLOC_CTX *ctx, size_t init, size_t max)
343
{
344
  fr_tls_bio_dbuff_t *bd = tls_bio_talloc_agg;
345
346
  if (unlikely(!bd)) {
347
    fr_tls_bio_dbuff_alloc(&bd, NULL, ctx, init, max, true);
348
    fr_atexit_thread_local(tls_bio_talloc_agg, _fr_tls_bio_dbuff_thread_local_free, bd);
349
350
    return bd->bio;
351
  }
352
353
  fr_assert_msg(!tls_bio_talloc_agg->dbuff_out.buff, "BIO not finalised");
354
  MEM(fr_dbuff_init_talloc(ctx, &bd->dbuff_out, &bd->tctx, init, max)); /* Where we read from */
355
  bd->dbuff_in = FR_DBUFF_BIND_END_ABS(&bd->dbuff_out);     /* Where we write to */
356
357
  return tls_bio_talloc_agg->bio;
358
}
359
360
/** Initialise the BIO logging meths which are used to create thread local logging BIOs
361
 *
362
 */
363
int fr_tls_bio_init(void)
364
0
{
365
  /*
366
   *  As per the boringSSL documentation
367
   *
368
   *  BIO_TYPE_START is the first user-allocated |BIO| type.
369
   *  No pre-defined type, flag bits aside, may exceed this
370
   *  value.
371
   *
372
   *  The low byte here defines the BIO ID, and the high byte
373
   *  defines its capabilities.
374
   */
375
0
  tls_bio_talloc_meth = BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "fr_tls_bio_dbuff_t");
376
0
  if (unlikely(!tls_bio_talloc_meth)) return -1;
377
378
  /*
379
   *  If BIO_meth_set_create is ever used here be sure to call
380
   *  BIO_set_init(bio, 1); in the create callbacks else all
381
   *  operations on the BIO will fail.
382
   */
383
0
  BIO_meth_set_write(tls_bio_talloc_meth, _tls_bio_talloc_write_cb);
384
0
  BIO_meth_set_puts(tls_bio_talloc_meth, _tls_bio_talloc_puts_cb);
385
0
  BIO_meth_set_read(tls_bio_talloc_meth, _tls_bio_talloc_read_cb);
386
0
  BIO_meth_set_gets(tls_bio_talloc_meth, _tls_bio_talloc_gets_cb);
387
388
0
  return 0;
389
0
}
390
391
/** Free the global log method templates
392
 *
393
 */
394
void fr_tls_bio_free(void)
395
0
{
396
0
  if (tls_bio_talloc_meth) {
397
0
    BIO_meth_free(tls_bio_talloc_meth);
398
    tls_bio_talloc_meth = NULL;
399
0
  }
400
0
}
401
#endif /* WITH_TLS */