Coverage Report

Created: 2026-08-08 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/server/request_data.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: a71a643df71c7440ef642ad35df0a242ee5b0b7b $
19
 *
20
 * @brief Functions for allocating requests and storing internal data in them.
21
 * @file src/lib/server/request_data.c
22
 *
23
 * @copyright 2019 The FreeRADIUS server project
24
 */
25
RCSID("$Id: a71a643df71c7440ef642ad35df0a242ee5b0b7b $")
26
27
#include <freeradius-devel/util/debug.h>
28
#include <freeradius-devel/server/request_data.h>
29
30
/** Per-request opaque data, added by modules
31
 *
32
 */
33
struct request_data_s {
34
  fr_dlist_t  list;     //!< Next opaque request data struct linked to this request.
35
36
  void const  *unique_ptr;    //!< Key to lookup request data.
37
  int   unique_int;   //!< Alternative key to lookup request data.
38
  char const  *type;      //!< Opaque type e.g. fr_pair_t, fr_dict_attr_t etc...
39
  void    *opaque;    //!< Opaque data.
40
  bool    free_on_replace;  //!< Whether to talloc_free(opaque) when the request data is removed.
41
  bool    free_on_parent;   //!< Whether to talloc_free(opaque) when the request is freed
42
  bool    persist;    //!< Whether this data should be transferred to a session_entry_t
43
            //!< after we're done processing this request.
44
45
#ifndef NDEBUG
46
  char const  *file;      //!< File where this request data was added.
47
  int   line;     //!< Line where this request data was added.
48
#endif
49
};
50
51
static char *request_data_description(TALLOC_CTX *ctx, request_data_t *rd)
52
0
{
53
0
    char *where;
54
0
    char *what;
55
0
    char *out;
56
57
    /*
58
     *  Where was the request data added
59
     */
60
0
#ifndef NDEBUG
61
0
    where = talloc_typed_asprintf(NULL, " added at %s:%i", rd->file, rd->line);
62
#else
63
    where = NULL;
64
#endif
65
66
    /*
67
     *  What was added
68
     */
69
0
    if (rd->type) {
70
0
      what = talloc_typed_asprintf(NULL, "%p (%s)", rd->opaque, rd->type);
71
0
    } else {
72
0
      what = talloc_typed_asprintf(NULL, "%p", rd->opaque);
73
0
    }
74
75
0
    out = talloc_typed_asprintf(ctx, "[0x%012"PRIxPTR":%i]%s %p, opaque %s%s",
76
0
              (uintptr_t)rd->unique_ptr,
77
0
              rd->unique_int,
78
0
              rd->persist ? "[P]" : "",
79
0
              rd,
80
0
              what,
81
0
              where ? where : "");
82
0
    talloc_free(what);
83
0
    talloc_free(where);
84
85
0
    return out;
86
0
}
87
88
/* Initialise a dlist for storing request data
89
 *
90
 * @param[in] list to initialise.
91
 */
92
void request_data_list_init(fr_dlist_head_t *data)
93
0
{
94
0
  fr_dlist_talloc_init(data, request_data_t, list);
95
0
}
96
97
/** Ensure opaque data is freed by binding its lifetime to the request_data_t
98
 *
99
 * @param rd  Request data being freed.
100
 * @return
101
 *  - 0 if free on parent is false or there's no opaque data.
102
 *  - ...else whatever the destructor for the opaque data returned.
103
 */
104
static int _request_data_free(request_data_t *rd)
105
0
{
106
0
  char *desc = NULL;
107
108
  /*
109
   *  In the vast majority of cases the request data will
110
   *  unlinked from its list before being freed.
111
   *  But in case it's not, do this now.
112
   *
113
   *  This helps in a very specific case where there's a list
114
   *  of request_data_t, and the state_ctx that the
115
   *  request_data_t is parented off is freed without the
116
   *  request_data_t being unlinked explicitly, but before
117
   *  the request itself is freed something attempts to access
118
   *  the request_data_t list, and runs into freed memory.
119
   *
120
   *  It's a similar pattern to structs removing themselves
121
   *  from trees when they're freed, but with the added bonus
122
   *  of never running into use after free errors/
123
   */
124
0
  fr_dlist_entry_unlink(&rd->list);
125
126
0
  if (DEBUG_ENABLED4) desc = request_data_description(rd, rd);
127
128
0
  if (rd->free_on_parent && rd->opaque) {
129
0
    int ret;
130
131
0
    DEBUG4("%s - freed with opaque data", desc);
132
133
0
    ret = talloc_free(rd->opaque);
134
0
    rd->opaque = NULL;
135
136
0
    return ret;
137
0
  }
138
139
0
  DEBUG4("%s - freed, but leaving opaque data", desc);
140
141
0
  return 0;
142
0
}
143
144
/** Allocate request data
145
 *
146
 * @param[in] ctx to allocate request data in.
147
 * @return new request data.
148
 */
149
static inline request_data_t *request_data_alloc(TALLOC_CTX *ctx)
150
0
{
151
0
  request_data_t *rd;
152
153
0
  MEM(rd = talloc_zero(ctx, request_data_t));
154
0
  talloc_set_destructor(rd, _request_data_free);
155
156
0
  return rd;
157
0
}
158
159
/** Add opaque data to a request_t
160
 *
161
 * The unique ptr is meant to be a module configuration, and the unique
162
 * integer allows the caller to have multiple opaque data associated with a request_t.
163
 *
164
 * @param[in] request   to associate data with.
165
 * @param[in] unique_ptr  Identifier for the data.
166
 * @param[in] unique_int  Qualifier for the identifier.
167
 * @param[in] type    Type of data (if talloced)
168
 * @param[in] opaque    Data to associate with the request.  May be NULL.
169
 * @param[in] free_on_replace Free opaque data if this request_data is replaced.
170
 * @param[in] free_on_parent  Free opaque data if the request or session is freed.
171
 *        Must not be set if the opaque data is also parented by
172
 *        the request or state (double free).
173
 * @param[in] persist   Transfer request data to an #fr_state_entry_t, and
174
 *        add it back to the next request we receive for the
175
 *        session.
176
 * @param[in] file    request data was added in.
177
 * @param[in] line    request data was added on.
178
 * @return
179
 *  - -2 on bad arguments.
180
 *  - -1 on memory allocation error.
181
 *  - 0 on success.
182
 */
183
int _request_data_add(request_t *request, void const *unique_ptr, int unique_int, char const *type, void *opaque,
184
          bool free_on_replace, bool free_on_parent, bool persist,
185
#ifndef NDEBUG
186
          char const *file, int line
187
#else
188
          UNUSED char const *file, UNUSED int line
189
#endif
190
          )
191
0
{
192
0
  request_data_t  *rd = NULL;
193
194
  /*
195
   *  Request must have a state ctx
196
   */
197
0
  fr_assert(request);
198
0
  fr_assert(!persist || request->session_state_ctx);
199
0
  fr_assert(!persist ||
200
0
       (talloc_parent(opaque) == request->session_state_ctx) ||
201
0
       (talloc_parent(opaque) == talloc_null_ctx()));
202
0
  fr_assert(!free_on_parent || (talloc_parent(opaque) != request));
203
204
0
#ifndef TALLOC_GET_TYPE_ABORT_NOOP
205
0
  if (type) opaque = _talloc_get_type_abort(opaque, type, __location__);
206
0
#endif
207
208
0
  while ((rd = fr_dlist_next(&request->data, rd))) {
209
0
    if ((rd->unique_ptr != unique_ptr) || (rd->unique_int != unique_int)) continue;
210
211
0
    fr_dlist_remove(&request->data, rd);  /* Unlink from the list */
212
213
    /*
214
     *  If caller requires custom behaviour on free
215
     *  they must set a destructor.
216
     */
217
0
    if (rd->free_on_replace && rd->opaque) {
218
0
      RDEBUG4("%s: Freeing %s%s%p at %p:%i via replacement",
219
0
        __FUNCTION__,
220
0
        rd->type ? rd->type : "", rd->type ? " " : "",
221
0
        rd->opaque, rd->unique_ptr, rd->unique_int);
222
0
      talloc_free(rd->opaque);
223
0
    }
224
    /*
225
     *  Need a new one, rd one's parent is wrong.
226
     *  And no, we can't just steal.
227
     */
228
0
    if (rd->persist != persist) {
229
0
      rd->free_on_parent = false;
230
0
      TALLOC_FREE(rd);
231
0
    }
232
233
0
    break;  /* replace the existing entry */
234
0
  }
235
236
  /*
237
   *  Only alloc new memory if we're not replacing
238
   *  an existing entry.
239
   *
240
   *  Tie the lifecycle of the data to either the state_ctx
241
   *  or the request, depending on whether it should
242
   *  persist or not.
243
   */
244
0
  if (!rd) {
245
0
    if (persist) {
246
0
      fr_assert(request->session_state_ctx);
247
0
      rd = request_data_alloc(request->session_state_ctx);
248
0
    } else {
249
0
      rd = request_data_alloc(request);
250
0
    }
251
252
0
  }
253
0
  if (!rd) return -1;
254
255
0
  rd->unique_ptr = unique_ptr;
256
0
  rd->unique_int = unique_int;
257
0
  rd->type = type;
258
0
  rd->opaque = opaque;
259
0
  rd->free_on_replace = free_on_replace;
260
0
  rd->free_on_parent = free_on_parent;
261
0
  rd->persist = persist;
262
0
#ifndef NDEBUG
263
0
  rd->file = file;
264
0
  rd->line = line;
265
0
#endif
266
267
0
  fr_dlist_insert_head(&request->data, rd);
268
269
0
  RDEBUG4("%s: %s%s%p at %p:%i, free_on_replace: %s, free_on_parent: %s, persist: %s",
270
0
    __FUNCTION__,
271
0
    rd->type ? rd->type : "", rd->type ? " " : "",
272
0
    rd->opaque, rd->unique_ptr, rd->unique_int,
273
0
    free_on_replace ? "yes" : "no",
274
0
    free_on_parent ? "yes" : "no",
275
0
    persist ? "yes" : "no");
276
277
0
  return 0;
278
0
}
279
280
/** Get opaque data from a request
281
 *
282
 * @note The unique ptr is meant to be a module configuration, and the unique
283
 *  integer allows the caller to have multiple opaque data associated with a request_t.
284
 *
285
 * @param[in] request   to retrieve data from.
286
 * @param[in] unique_ptr  Identifier for the data.
287
 * @param[in] unique_int  Qualifier for the identifier.
288
 * @return
289
 *  - NULL if no opaque data could be found.
290
 *  - the opaque data. The entry holding the opaque data is removed from the request.
291
 */
292
void *request_data_get(request_t *request, void const *unique_ptr, int unique_int)
293
0
{
294
0
  request_data_t  *rd = NULL;
295
296
0
  if (!request) return NULL;
297
298
0
  while ((rd = fr_dlist_next(&request->data, rd))) {
299
0
    void *ptr;
300
301
0
    if ((rd->unique_ptr != unique_ptr) || (rd->unique_int != unique_int)) continue;
302
303
0
    ptr = rd->opaque;
304
305
0
    rd->free_on_parent = false; /* Don't free opaque data we're handing back */
306
0
    fr_dlist_remove(&request->data, rd);
307
308
0
#ifndef TALLOC_GET_TYPE_ABORT_NOOP
309
0
    if (rd->type) ptr = _talloc_get_type_abort(ptr, rd->type, __location__);
310
0
#endif
311
312
0
    RDEBUG4("%s: %s%s%p at %p:%i retrieved and unlinked",
313
0
      __FUNCTION__,
314
0
      rd->type ? rd->type : "", rd->type ? " " : "",
315
0
      rd->opaque, rd->unique_ptr, rd->unique_int);
316
317
0
    talloc_free(rd);
318
319
0
    return ptr;
320
0
  }
321
322
0
  RDEBUG4("%s: No request data found at %p:%i", __FUNCTION__, unique_ptr, unique_int);
323
324
0
  return NULL;   /* wasn't found, too bad... */
325
0
}
326
327
/** Get opaque data from a request without removing it
328
 *
329
 * @note The unique ptr is meant to be a module configuration, and the unique
330
 *  integer allows the caller to have multiple opaque data associated with a request_t.
331
 *
332
 * @param request to retrieve data from.
333
 * @param unique_ptr  Identifier for the data.
334
 * @param unique_int  Qualifier for the identifier.
335
 * @return
336
 *  - NULL if no opaque data could be found.
337
 *  - the opaque data.
338
 */
339
void *request_data_reference(request_t *request, void const *unique_ptr, int unique_int)
340
0
{
341
0
  request_data_t  *rd = NULL;
342
343
0
  if (!request) return NULL;
344
345
0
  while ((rd = fr_dlist_next(&request->data, rd))) {
346
0
    if ((rd->unique_ptr != unique_ptr) || (rd->unique_int != unique_int)) continue;
347
348
0
#ifndef TALLOC_GET_TYPE_ABORT_NOOP
349
0
    if (rd->type) rd->opaque = _talloc_get_type_abort(rd->opaque, rd->type, __location__);
350
0
#endif
351
352
0
    RDEBUG4("%s: %s%s%p at %p:%i retrieved",
353
0
      __FUNCTION__,
354
0
      rd->type ? rd->type : "", rd->type ? " " : "",
355
0
      rd->opaque, rd->unique_ptr, rd->unique_int);
356
357
0
    return rd->opaque;
358
0
  }
359
360
0
  RDEBUG4("%s: No request data found at %p:%i", __FUNCTION__, unique_ptr, unique_int);
361
362
0
  return NULL;   /* wasn't found, too bad... */
363
0
}
364
365
/** Loop over all the request data, pulling out ones matching persist state
366
 *
367
 * @param[out] out  Head of result list.
368
 * @param[in] request to search for request_data_t in.
369
 * @param[in] persist Whether to pull persistable or non-persistable data.
370
 * @return number of request_data_t retrieved.
371
 */
372
int request_data_by_persistance(fr_dlist_head_t *out, request_t *request, bool persist)
373
0
{
374
0
  int   count = 0;
375
0
  request_data_t  *rd = NULL, *prev;
376
377
0
  while ((rd = fr_dlist_next(&request->data, rd))) {
378
0
    if (rd->persist != persist) continue;
379
380
0
    prev = fr_dlist_remove(&request->data, rd);
381
0
    fr_dlist_insert_tail(out, rd);
382
0
    rd = prev;
383
0
    count++;
384
0
  }
385
386
0
  return count;
387
0
}
388
389
/** Loop over all the request data, copying, then freeing ones matching persist state
390
 *
391
 * @param[in] ctx To allocate new request_data_t.
392
 * @param[out] out  Head of result list. If NULL, data
393
 *      will be reparented in place.
394
 * @param[in] request to search for request_data_t in.
395
 * @param[in] persist Whether to pull persistable or non-persistable data.
396
 * @return number of request_data_t retrieved.
397
 */
398
int request_data_by_persistance_reparent(TALLOC_CTX *ctx, fr_dlist_head_t *out, request_t *request, bool persist)
399
0
{
400
0
  int     count = 0;
401
0
  request_data_t    *rd = NULL, *new, *prev;
402
0
  fr_dlist_head_t   head;
403
404
0
  fr_dlist_talloc_init(&head, request_data_t, list);
405
406
0
  while ((rd = fr_dlist_next(&request->data, rd))) {
407
0
    if (rd->persist != persist) continue;
408
409
0
    prev = fr_dlist_remove(&request->data, rd);
410
411
0
    new = request_data_alloc(ctx);
412
0
    memcpy(new, rd, sizeof(*new));
413
414
    /*
415
     *  Clear the list pointers...
416
     */
417
0
    memset(&new->list, 0, sizeof(new->list));
418
0
    rd->free_on_parent = false;
419
0
    talloc_free(rd);
420
421
0
    if (out) {
422
0
      fr_dlist_insert_tail(out, new);
423
0
    } else {
424
0
      fr_dlist_insert_tail(&head, new);
425
0
    }
426
0
    rd = prev;
427
0
    count++;
428
0
  }
429
430
0
  if (!out) fr_dlist_move(&request->data, &head);
431
432
0
  return count;
433
0
}
434
435
/** Return how many request data entries exist of a given persistence
436
 *
437
 * @param[in] request to check in.
438
 * @param[in] persist Whether to count persistable or non-persistable data.
439
 * @return number of request_data_t that exist in persistable or non-persistable form
440
 */
441
int request_data_by_persistance_count(request_t *request, bool persist)
442
0
{
443
0
  int     count = 0;
444
0
  request_data_t  *rd = NULL;
445
446
0
  while ((rd = fr_dlist_next(&request->data, rd))) {
447
0
    if (rd->persist != persist) continue;
448
449
0
    count++;
450
0
  }
451
452
0
  return count;
453
0
}
454
455
/** Add request data back to a request
456
 *
457
 * @note May add multiple entries (if they're linked).
458
 * @note Will not check for duplicates.
459
 *
460
 * @param request to add data to.
461
 * @param in    Data to add.
462
 */
463
void request_data_restore(request_t *request, fr_dlist_head_t *in)
464
0
{
465
0
  fr_dlist_move(&request->data, in);
466
0
}
467
468
/** Used for removing data from subrequests that are about to be freed
469
 *
470
 * @param[in] request to remove persistable data from.
471
 */
472
void request_data_persistable_free(request_t *request)
473
0
{
474
0
  fr_dlist_head_t head;
475
476
0
  fr_dlist_talloc_init(&head, request_data_t, list);
477
478
0
  request_data_by_persistance(&head, request, true);
479
480
0
  fr_dlist_talloc_free(&head);
481
0
}
482
483
484
void request_data_list_dump(request_t *request, fr_dlist_head_t *head)
485
0
{
486
0
  request_data_t  *rd = NULL;
487
488
0
  if (fr_dlist_empty(head)) return;
489
490
0
  while ((rd = fr_dlist_next(head, rd))) {
491
0
    char *desc;
492
493
0
    desc = request_data_description(NULL, rd);
494
0
    ROPTIONAL(RDEBUG, DEBUG, "%s", desc);
495
0
    talloc_free(desc);
496
0
  }
497
0
}
498
499
void request_data_dump(request_t *request)
500
0
{
501
0
  request_data_list_dump(request, &request->data);
502
0
}
503
504
#ifdef WITH_VERIFY_PTR
505
bool request_data_persistable(request_data_t *rd)
506
0
{
507
0
  return rd->persist;
508
0
}
509
510
/** Verify all request data is parented by the specified context
511
 *
512
 * @note Only available if built with WITH_VERIFY_PTR
513
 *
514
 * @param parent  that should hold the request data.
515
 * @param entry   to verify.
516
 * @return
517
 *  - true if chunk lineage is correct.
518
 *  - false if one of the chunks is parented by something else.
519
 */
520
bool request_data_verify_parent(TALLOC_CTX *parent, fr_dlist_head_t *entry)
521
0
{
522
0
  request_data_t  *rd = NULL;
523
524
0
  while ((rd = fr_dlist_next(entry, rd))) if (talloc_parent(rd) != parent) return false;
525
526
0
  return true;
527
0
}
528
#endif