Coverage Report

Created: 2026-09-04 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/cache/shm.c
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
20
/***************************************************************************
21
 * Copyright (C) 2017-2026 ZmartZone Holding BV
22
 * Copyright (C) 2013-2017 Ping Identity Corporation
23
 * All rights reserved.
24
 *
25
 * DISCLAIMER OF WARRANTIES:
26
 *
27
 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
28
 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
29
 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
30
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
31
 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
32
 * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
33
 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
34
 * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
35
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
36
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
37
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
39
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
 *
41
 * bounded shared-memory cache with hash lookup and exact LRU eviction
42
 *
43
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
44
 */
45
46
#include "cache/shm.h"
47
#include "cache/cache.h"
48
#include "cfg/cache.h"
49
#include "cfg/cfg_int.h"
50
#include <apr_general.h>
51
#include <apr_shm.h>
52
#include <stdint.h>
53
54
/* size of key in cached key/value pairs */
55
33.7k
#define OIDC_CACHE_SHM_KEY_MAX OIDC_CACHE_KEY_SIZE_MAX
56
57
3.10k
#define OIDC_CACHE_SHM_PRESSURE_WARN_INTERVAL apr_time_from_sec(60)
58
/* Reject accidental configurations large enough to monopolize a host even on 64-bit platforms. */
59
38
#define OIDC_CACHE_SHM_SEGMENT_SIZE_MAX ((apr_uint64_t)16 * 1024 * 1024 * 1024)
60
61
typedef struct oidc_cache_cfg_shm_t {
62
  apr_shm_t *shm;
63
  oidc_cache_mutex_t *mutex;
64
  apr_byte_t mutex_ready;
65
  apr_byte_t is_parent;
66
} oidc_cache_cfg_shm_t;
67
68
/*
69
 * Layout: header | buckets | slots. Links use 1-based indexes because processes may map the segment
70
 * at different addresses. One cross-process mutex protects the buckets, slots and free list.
71
 */
72
typedef struct oidc_cache_shm_header_t {
73
  /* number of (fixed size) cache entry slots */
74
  apr_uint32_t nslots;
75
  /* number of hash buckets; a power of two >= nslots */
76
  apr_uint32_t nbuckets;
77
  /* configured size of one slot, including the entry struct itself */
78
  apr_uint32_t entry_size;
79
  /* per-segment SipHash key, initialized before workers fork */
80
  apr_uint64_t hash_key[2];
81
  /* fields below are serialized by the cache mutex */
82
  apr_uint32_t free_head;
83
  apr_time_t last_pressure_warning;
84
} oidc_cache_shm_header_t;
85
86
/* represents one (fixed size) cache entry, cq. name/value string pair */
87
typedef __attribute__((aligned(64))) struct oidc_cache_shm_entry_t {
88
89
  /* name of the cache entry */
90
  char section_key[OIDC_CACHE_SHM_KEY_MAX];
91
  /* last read or write access timestamp */
92
  apr_time_t access;
93
  /* expiry timestamp */
94
  apr_time_t expires;
95
  /* 1-based index of the next entry in the bucket chain or free list, 0 = none */
96
  apr_uint32_t next;
97
  /* value of the cache entry */
98
  char value[];
99
} oidc_cache_shm_entry_t;
100
101
1.64M
static apr_uint32_t *oidc_cache_shm_buckets(oidc_cache_shm_header_t *hdr) {
102
1.64M
  return (apr_uint32_t *)((uint8_t *)hdr + APR_ALIGN(sizeof(oidc_cache_shm_header_t), 64));
103
1.64M
}
104
105
1.61M
static oidc_cache_shm_entry_t *oidc_cache_shm_slot(oidc_cache_shm_header_t *hdr, apr_uint32_t idx) {
106
1.61M
  uint8_t *slots =
107
1.61M
      (uint8_t *)oidc_cache_shm_buckets(hdr) + APR_ALIGN((apr_size_t)hdr->nbuckets * sizeof(apr_uint32_t), 64);
108
1.61M
  return (oidc_cache_shm_entry_t *)(slots + (apr_size_t)(idx - 1) * hdr->entry_size);
109
1.61M
}
110
111
/*
112
 * Align the layout base to match oidc_cache_shm_entry_t; APR does not guarantee raw alignment.
113
 * The anonymous segment is fork-inherited at the same base, so every worker selects the same
114
 * offset. Segment sizing includes space for this adjustment.
115
 */
116
29.4k
static oidc_cache_shm_header_t *oidc_cache_shm_base(const oidc_cache_cfg_shm_t *context) {
117
29.4k
  return (oidc_cache_shm_header_t *)APR_ALIGN((uintptr_t)apr_shm_baseaddr_get(context->shm), 64);
118
29.4k
}
119
120
38
apr_byte_t oidc_cache_shm_segment_size(int size_max, int entry_size_max, apr_uint32_t nbuckets, apr_size_t *result) {
121
  /* Calculate in 64 bits first: apr_size_t is only 32 bits on supported 32-bit APR builds. */
122
38
  const apr_uint64_t header_size = ((apr_uint64_t)sizeof(oidc_cache_shm_header_t) + 63) & ~63ULL;
123
38
  const apr_uint64_t bucket_size = ((apr_uint64_t)nbuckets * sizeof(apr_uint32_t) + 63) & ~63ULL;
124
38
  const apr_uint64_t slot_size = ((apr_uint64_t)entry_size_max + 63) & ~63ULL;
125
38
  const apr_uint64_t slots_size = slot_size * (apr_uint64_t)size_max;
126
38
  const apr_uint64_t total = 64 + header_size + bucket_size + slots_size;
127
128
38
  if ((result == NULL) || (size_max <= 0) || (entry_size_max <= 0) || (nbuckets == 0) ||
129
38
      (slots_size / slot_size != (apr_uint64_t)size_max) || (total < slots_size) ||
130
38
      (total > (apr_uint64_t)SIZE_MAX) || (total > OIDC_CACHE_SHM_SEGMENT_SIZE_MAX))
131
0
    return FALSE;
132
133
38
  *result = (apr_size_t)total;
134
38
  return TRUE;
135
38
}
136
137
3.11M
static apr_uint64_t oidc_cache_shm_rotate_left(apr_uint64_t v, unsigned int n) {
138
3.11M
  return (v << n) | (v >> (64 - n));
139
3.11M
}
140
141
162k
static apr_uint64_t oidc_cache_shm_load64_le(const unsigned char *p) {
142
162k
  return (apr_uint64_t)p[0] | ((apr_uint64_t)p[1] << 8) | ((apr_uint64_t)p[2] << 16) |
143
162k
         ((apr_uint64_t)p[3] << 24) | ((apr_uint64_t)p[4] << 32) | ((apr_uint64_t)p[5] << 40) |
144
162k
         ((apr_uint64_t)p[6] << 48) | ((apr_uint64_t)p[7] << 56);
145
162k
}
146
147
#define OIDC_CACHE_SHM_SIPROUND(v0, v1, v2, v3)                                                                        \
148
519k
  do {                                                                                                           \
149
519k
    (v0) += (v1);                                                                                          \
150
519k
    (v1) = oidc_cache_shm_rotate_left((v1), 13);                                                           \
151
519k
    (v1) ^= (v0);                                                                                          \
152
519k
    (v0) = oidc_cache_shm_rotate_left((v0), 32);                                                           \
153
519k
    (v2) += (v3);                                                                                          \
154
519k
    (v3) = oidc_cache_shm_rotate_left((v3), 16);                                                           \
155
519k
    (v3) ^= (v2);                                                                                          \
156
519k
    (v0) += (v3);                                                                                          \
157
519k
    (v3) = oidc_cache_shm_rotate_left((v3), 21);                                                           \
158
519k
    (v3) ^= (v0);                                                                                          \
159
519k
    (v2) += (v1);                                                                                          \
160
519k
    (v1) = oidc_cache_shm_rotate_left((v1), 17);                                                           \
161
519k
    (v1) ^= (v2);                                                                                          \
162
519k
    (v2) = oidc_cache_shm_rotate_left((v2), 32);                                                           \
163
519k
  } while (0)
164
165
/* SipHash-2-4 prevents predictable bucket flooding while remaining cheap for short cache keys. */
166
32.4k
apr_uint64_t oidc_cache_shm_siphash(const unsigned char *data, apr_size_t len, const apr_uint64_t key[2]) {
167
32.4k
  const unsigned char *p = data;
168
32.4k
  const unsigned char *end = p + (len & ~(apr_size_t)7);
169
32.4k
  apr_uint64_t v0 = 0x736f6d6570736575ULL ^ key[0];
170
32.4k
  apr_uint64_t v1 = 0x646f72616e646f6dULL ^ key[1];
171
32.4k
  apr_uint64_t v2 = 0x6c7967656e657261ULL ^ key[0];
172
32.4k
  apr_uint64_t v3 = 0x7465646279746573ULL ^ key[1];
173
32.4k
  apr_uint64_t tail = (apr_uint64_t)len << 56;
174
175
194k
  while (p != end) {
176
162k
    const apr_uint64_t m = oidc_cache_shm_load64_le(p);
177
162k
    v3 ^= m;
178
162k
    OIDC_CACHE_SHM_SIPROUND(v0, v1, v2, v3);
179
162k
    OIDC_CACHE_SHM_SIPROUND(v0, v1, v2, v3);
180
162k
    v0 ^= m;
181
162k
    p += 8;
182
162k
  }
183
194k
  for (apr_size_t i = 0; i < (len & 7); i++)
184
162k
    tail |= (apr_uint64_t)p[i] << (8 * i);
185
186
32.4k
  v3 ^= tail;
187
32.4k
  OIDC_CACHE_SHM_SIPROUND(v0, v1, v2, v3);
188
32.4k
  OIDC_CACHE_SHM_SIPROUND(v0, v1, v2, v3);
189
32.4k
  v0 ^= tail;
190
32.4k
  v2 ^= 0xff;
191
162k
  for (int i = 0; i < 4; i++)
192
129k
    OIDC_CACHE_SHM_SIPROUND(v0, v1, v2, v3);
193
32.4k
  return v0 ^ v1 ^ v2 ^ v3;
194
32.4k
}
195
196
32.4k
static apr_uint64_t oidc_cache_shm_hash(const char *s, const apr_uint64_t key[2]) {
197
32.4k
  return oidc_cache_shm_siphash((const unsigned char *)s, _oidc_strlen(s), key);
198
32.4k
}
199
200
6.17k
apr_ssize_t oidc_cache_shm_value_size_max(int entry_size_max) {
201
6.17k
  return (apr_ssize_t)entry_size_max - (apr_ssize_t)sizeof(oidc_cache_shm_entry_t);
202
6.17k
}
203
204
#undef OIDC_CACHE_SHM_SIPROUND
205
206
/* Put an already-unlinked slot on the free list; the cache mutex must be held. */
207
0
static void oidc_cache_shm_slot_free(oidc_cache_shm_header_t *hdr, oidc_cache_shm_entry_t *t, apr_uint32_t idx) {
208
0
  t->section_key[0] = '\0';
209
0
  t->access = 0;
210
0
  t->next = hdr->free_head;
211
0
  hdr->free_head = idx;
212
0
}
213
214
static apr_byte_t oidc_cache_shm_validate_buckets(request_rec *r, oidc_cache_shm_header_t *hdr, apr_byte_t *visited,
215
0
              const char **error) {
216
0
  for (apr_uint32_t bucket_idx = 0; bucket_idx < hdr->nbuckets; bucket_idx++) {
217
0
    for (apr_uint32_t idx = oidc_cache_shm_buckets(hdr)[bucket_idx]; idx != 0;
218
0
         idx = oidc_cache_shm_slot(hdr, idx)->next) {
219
0
      if ((idx > hdr->nslots) || (visited[idx] != 0)) {
220
0
        *error = apr_psprintf(r->pool, "invalid or repeated occupied slot %u", idx);
221
0
        return FALSE;
222
0
      }
223
0
      const oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, idx);
224
0
      const apr_uint32_t expected_bucket =
225
0
          (apr_uint32_t)oidc_cache_shm_hash(t->section_key, hdr->hash_key) & (hdr->nbuckets - 1);
226
0
      if ((t->section_key[0] == '\0') || (expected_bucket != bucket_idx)) {
227
0
        *error = apr_psprintf(r->pool, "slot %u is linked from the wrong bucket", idx);
228
0
        return FALSE;
229
0
      }
230
0
      visited[idx] = 1;
231
0
    }
232
0
  }
233
0
  return TRUE;
234
0
}
235
236
static apr_byte_t oidc_cache_shm_validate_free_list(request_rec *r, oidc_cache_shm_header_t *hdr, apr_byte_t *visited,
237
0
                const char **error) {
238
0
  for (apr_uint32_t idx = hdr->free_head; idx != 0; idx = oidc_cache_shm_slot(hdr, idx)->next) {
239
0
    if ((idx > hdr->nslots) || (visited[idx] != 0) ||
240
0
        (oidc_cache_shm_slot(hdr, idx)->section_key[0] != '\0')) {
241
0
      *error = apr_psprintf(r->pool, "invalid or repeated free slot %u", idx);
242
0
      return FALSE;
243
0
    }
244
0
    visited[idx] = 1;
245
0
  }
246
0
  return TRUE;
247
0
}
248
249
static apr_byte_t oidc_cache_shm_validate_ownership(request_rec *r, const oidc_cache_shm_header_t *hdr,
250
0
                const apr_byte_t *visited, const char **error) {
251
0
  for (apr_uint32_t idx = 1; idx <= hdr->nslots; idx++) {
252
0
    if (visited[idx] == 0) {
253
0
      *error = apr_psprintf(r->pool, "orphaned slot %u", idx);
254
0
      return FALSE;
255
0
    }
256
0
  }
257
0
  return TRUE;
258
0
}
259
260
/* Validate bucket/free-list ownership; used by stress tests and available for diagnostics. */
261
0
apr_byte_t oidc_cache_shm_validate(request_rec *r, const char **error) {
262
0
  if (error == NULL)
263
0
    return FALSE;
264
265
0
  oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
266
0
  const oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg;
267
0
  oidc_cache_shm_header_t *hdr = oidc_cache_shm_base(context);
268
0
  apr_byte_t valid = TRUE;
269
0
  *error = NULL;
270
271
0
  if (oidc_cache_mutex_lock(r->pool, r->server, context->mutex) == FALSE)
272
0
    return FALSE;
273
0
  apr_byte_t *visited = apr_pcalloc(r->pool, (apr_size_t)hdr->nslots + 1);
274
0
  valid = oidc_cache_shm_validate_buckets(r, hdr, visited, error) &&
275
0
    oidc_cache_shm_validate_free_list(r, hdr, visited, error) &&
276
0
    oidc_cache_shm_validate_ownership(r, hdr, visited, error);
277
0
  if (oidc_cache_mutex_unlock(r->pool, r->server, context->mutex) == FALSE)
278
0
    valid = FALSE;
279
0
  return valid;
280
0
}
281
282
/* create the cache context */
283
38
static void *oidc_cache_shm_cfg_create(apr_pool_t *pool) {
284
38
  oidc_cache_cfg_shm_t *context = apr_pcalloc(pool, sizeof(oidc_cache_cfg_shm_t));
285
38
  context->shm = NULL;
286
38
  context->mutex = oidc_cache_mutex_create(pool, TRUE);
287
38
  context->is_parent = TRUE;
288
38
  return context;
289
38
}
290
291
/*
292
 * initialized the shared memory block in the parent process
293
 */
294
38
static int oidc_cache_shm_post_config(apr_pool_t *pool, server_rec *s) {
295
38
  oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module);
296
38
  const int size_max = oidc_cfg_cache_shm_size_max_get(cfg);
297
38
  const int entry_size_max = oidc_cfg_cache_shm_entry_size_max_get(cfg);
298
299
38
  if (cfg->cache.cfg != NULL)
300
0
    return OK;
301
38
  oidc_cache_cfg_shm_t *context = oidc_cache_shm_cfg_create(pool);
302
38
  cfg->cache.cfg = context;
303
304
  /* a power-of-two number of hash buckets >= the number of slots */
305
38
  apr_uint32_t nbuckets = 1;
306
380
  while ((nbuckets < (apr_uint32_t)size_max) && (nbuckets <= UINT32_MAX / 2))
307
342
    nbuckets <<= 1;
308
38
  if (nbuckets < (apr_uint32_t)size_max) {
309
0
    oidc_serror(s, "could not size the shared memory hash bucket array");
310
0
    return HTTP_INTERNAL_SERVER_ERROR;
311
0
  }
312
313
38
  apr_size_t segment_size = 0;
314
38
  if (oidc_cache_shm_segment_size(size_max, entry_size_max, nbuckets, &segment_size) == FALSE) {
315
0
    oidc_serror(s,
316
0
          "requested shared memory cache is too large; reduce " OIDCCacheShmMax
317
0
          " or " OIDCCacheShmEntrySizeMax " (maximum segment size is %" APR_UINT64_T_FMT " bytes)",
318
0
          OIDC_CACHE_SHM_SEGMENT_SIZE_MAX);
319
0
    return HTTP_INTERNAL_SERVER_ERROR;
320
0
  }
321
322
  /* create the shared memory segment */
323
38
  apr_status_t rv = apr_shm_create(&context->shm, segment_size, NULL, pool);
324
38
  if (rv != APR_SUCCESS) {
325
0
    oidc_serror(s, "apr_shm_create failed to create shared memory segment");
326
0
    return HTTP_INTERNAL_SERVER_ERROR;
327
0
  }
328
329
  /* initialize the header, the (empty) hash buckets and the free list holding all slots */
330
38
  oidc_cache_shm_header_t *hdr = oidc_cache_shm_base(context);
331
38
  hdr->nslots = (apr_uint32_t)size_max;
332
38
  hdr->nbuckets = nbuckets;
333
  /* round the slot stride up to the entry's 64-byte alignment (see oidc_cache_shm_segment_size) */
334
38
  hdr->entry_size = (apr_uint32_t)APR_ALIGN((apr_size_t)entry_size_max, 64);
335
38
  if (apr_generate_random_bytes((unsigned char *)hdr->hash_key, sizeof(hdr->hash_key)) != APR_SUCCESS) {
336
0
    oidc_serror(s, "apr_generate_random_bytes failed to seed the shared memory cache hash");
337
0
    return HTTP_INTERNAL_SERVER_ERROR;
338
0
  }
339
38
  hdr->last_pressure_warning = 0;
340
38
  _oidc_memset(oidc_cache_shm_buckets(hdr), 0, (apr_size_t)nbuckets * sizeof(apr_uint32_t));
341
19.0k
  for (apr_uint32_t i = 1; i <= hdr->nslots; i++) {
342
19.0k
    oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, i);
343
19.0k
    t->section_key[0] = '\0';
344
19.0k
    t->access = 0;
345
19.0k
    t->next = i < hdr->nslots ? i + 1 : 0;
346
19.0k
  }
347
38
  hdr->free_head = 1;
348
349
38
  if (oidc_cache_mutex_post_config(pool, s, context->mutex, "shm") == FALSE)
350
0
    return HTTP_INTERNAL_SERVER_ERROR;
351
38
  context->mutex_ready = TRUE;
352
353
38
  oidc_sdebug(s,
354
38
        "initialized shared memory with a cache size (# entries) of: %d, a max (single) entry size of: %d, "
355
38
        "and a segment size of: %" APR_SIZE_T_FMT,
356
38
        size_max, entry_size_max, segment_size);
357
358
38
  oidc_slog(s, APLOG_TRACE1, "create: %pp (shm=%pp,s=%pp, p=%d)", context, context ? context->shm : 0, s,
359
38
      context ? context->is_parent : -1);
360
361
38
  return OK;
362
38
}
363
364
/*
365
 * initialize the shared memory segment in a child process
366
 */
367
0
static int oidc_cache_shm_child_init(apr_pool_t *p, server_rec *s) {
368
0
  oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module);
369
0
  oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg;
370
371
0
  oidc_slog(s, APLOG_TRACE1, "init: %pp (shm=%pp,s=%pp, p=%d)", context, context ? context->shm : 0, s,
372
0
      context ? context->is_parent : -1);
373
374
0
  if (context->is_parent == FALSE)
375
0
    return APR_SUCCESS;
376
0
  context->is_parent = FALSE;
377
378
0
  return oidc_cache_mutex_child_init(p, s, context->mutex);
379
0
}
380
381
/*
382
 * assemble single key name based on section/key input
383
 */
384
29.4k
static char *oidc_cache_shm_get_key(request_rec *r, const char *section, const char *key) {
385
386
29.4k
  char *section_key = oidc_cache_section_key(r->pool, section, key);
387
388
  /* check that the passed in key is valid */
389
29.4k
  const apr_size_t section_key_len = _oidc_strlen(section_key);
390
29.4k
  if (section_key_len >= OIDC_CACHE_SHM_KEY_MAX) {
391
0
    oidc_error(r, "could not construct cache key since key size is too large (%d >= %d) (%s)",
392
0
         (int)section_key_len, OIDC_CACHE_SHM_KEY_MAX, section_key);
393
0
    return NULL;
394
0
  }
395
396
29.4k
  return section_key;
397
29.4k
}
398
399
static apr_uint32_t oidc_cache_shm_find(oidc_cache_shm_header_t *hdr, const apr_uint32_t *bucket,
400
29.3k
          const char *section_key, apr_uint32_t *prev) {
401
29.3k
  *prev = 0;
402
37.5k
  for (apr_uint32_t idx = *bucket; idx != 0; idx = oidc_cache_shm_slot(hdr, idx)->next) {
403
8.87k
    if (_oidc_strcmp(oidc_cache_shm_slot(hdr, idx)->section_key, section_key) == 0)
404
694
      return idx;
405
8.18k
    *prev = idx;
406
8.18k
  }
407
28.6k
  return 0;
408
29.3k
}
409
410
/*
411
 * get a value from the shared memory cache
412
 */
413
23.3k
static apr_byte_t oidc_cache_shm_get(request_rec *r, const char *section, const char *key, char **value) {
414
415
23.3k
  oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
416
23.3k
  const oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg;
417
418
23.3k
  const char *section_key = oidc_cache_shm_get_key(r, section, key);
419
23.3k
  if (section_key == NULL)
420
0
    return FALSE;
421
422
23.3k
  *value = NULL;
423
23.3k
  oidc_cache_shm_header_t *hdr = oidc_cache_shm_base(context);
424
23.3k
  const apr_uint64_t hash = oidc_cache_shm_hash(section_key, hdr->hash_key);
425
23.3k
  const apr_uint32_t bucket_idx = (apr_uint32_t)hash & (hdr->nbuckets - 1);
426
427
23.3k
  if (oidc_cache_mutex_lock(r->pool, r->server, context->mutex) == FALSE)
428
0
    return FALSE;
429
430
23.3k
  apr_uint32_t *bucket = &oidc_cache_shm_buckets(hdr)[bucket_idx];
431
23.3k
  const apr_time_t current_time = apr_time_now();
432
23.3k
  apr_uint32_t prev = 0;
433
23.3k
  apr_uint32_t idx = oidc_cache_shm_find(hdr, bucket, section_key, &prev);
434
23.3k
  if (idx != 0) {
435
49
    oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, idx);
436
49
    if (t->expires > current_time) {
437
49
      t->access = current_time;
438
      /* The copy must finish while the mutex prevents a concurrent replacement. */
439
49
      *value = apr_pstrdup(r->pool, t->value);
440
49
    } else {
441
0
      if (prev != 0)
442
0
        oidc_cache_shm_slot(hdr, prev)->next = t->next;
443
0
      else
444
0
        *bucket = t->next;
445
0
      oidc_cache_shm_slot_free(hdr, t, idx);
446
0
    }
447
49
  }
448
23.3k
  return oidc_cache_mutex_unlock(r->pool, r->server, context->mutex);
449
23.3k
}
450
451
/*
452
 * unlink the specified slot from the bucket chain it is on; must be called with the mutex held
453
 */
454
3.10k
static apr_byte_t oidc_cache_shm_unlink(oidc_cache_shm_header_t *hdr, apr_uint32_t idx) {
455
3.10k
  const oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, idx);
456
3.10k
  const apr_uint64_t hash = oidc_cache_shm_hash(t->section_key, hdr->hash_key);
457
3.10k
  apr_uint32_t *bucket = &oidc_cache_shm_buckets(hdr)[(apr_uint32_t)hash & (hdr->nbuckets - 1)];
458
3.10k
  apr_uint32_t prev = 0;
459
6.10k
  for (apr_uint32_t i = *bucket; i != 0; i = oidc_cache_shm_slot(hdr, i)->next) {
460
6.10k
    if (i == idx) {
461
3.10k
      if (prev != 0)
462
1.92k
        oidc_cache_shm_slot(hdr, prev)->next = t->next;
463
1.18k
      else
464
1.18k
        *bucket = t->next;
465
3.10k
      return TRUE;
466
3.10k
    }
467
2.99k
    prev = i;
468
2.99k
  }
469
0
  return FALSE;
470
3.10k
}
471
472
/* The cache mutex is held: reclaim an expired entry, otherwise use exact LRU. */
473
static apr_uint32_t oidc_cache_shm_evict(oidc_cache_shm_header_t *hdr, apr_time_t current_time,
474
3.10k
           apr_time_t *pressure_age) {
475
3.10k
  apr_uint32_t expired_victim = 0;
476
3.10k
  apr_uint32_t lru_victim = 0;
477
3.10k
  apr_time_t oldest = 0;
478
3.10k
  *pressure_age = -1;
479
1.55M
  for (apr_uint32_t idx = 1; idx <= hdr->nslots; idx++) {
480
1.55M
    const oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, idx);
481
1.55M
    if (t->section_key[0] == '\0')
482
0
      continue;
483
1.55M
    if ((expired_victim == 0) && (t->expires <= current_time))
484
0
      expired_victim = idx;
485
1.55M
    if ((lru_victim == 0) || (t->access < oldest)) {
486
11.4k
      lru_victim = idx;
487
11.4k
      oldest = t->access;
488
11.4k
    }
489
1.55M
  }
490
491
3.10k
  const apr_uint32_t victim = expired_victim != 0 ? expired_victim : lru_victim;
492
3.10k
  if (victim == 0)
493
0
    return 0;
494
495
3.10k
  if (expired_victim == 0) {
496
3.10k
    const apr_time_t access = oidc_cache_shm_slot(hdr, victim)->access;
497
3.10k
    const apr_time_t age = current_time >= access ? (current_time - access) / 1000000 : 0;
498
3.10k
    if ((age < 3600) &&
499
3.10k
        ((hdr->last_pressure_warning == 0) || (current_time < hdr->last_pressure_warning) ||
500
3.10k
         (current_time - hdr->last_pressure_warning >= OIDC_CACHE_SHM_PRESSURE_WARN_INTERVAL))) {
501
2
      hdr->last_pressure_warning = current_time;
502
2
      *pressure_age = age;
503
2
    }
504
3.10k
  }
505
506
3.10k
  if (oidc_cache_shm_unlink(hdr, victim) == FALSE)
507
0
    return 0;
508
509
3.10k
  return victim;
510
3.10k
}
511
512
static void oidc_cache_shm_entry_update(oidc_cache_shm_header_t *hdr, apr_uint32_t idx, const char *value,
513
4.92k
          apr_time_t expiry, apr_time_t access) {
514
4.92k
  oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, idx);
515
4.92k
  _oidc_strcpy(t->value, value);
516
4.92k
  t->expires = expiry;
517
4.92k
  t->access = access;
518
4.92k
}
519
520
static void oidc_cache_shm_entry_insert(oidc_cache_shm_header_t *hdr, apr_uint32_t *bucket, apr_uint32_t idx,
521
          const char *section_key, const char *value, apr_time_t expiry,
522
4.28k
          apr_time_t access) {
523
4.28k
  oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, idx);
524
4.28k
  t->next = *bucket;
525
4.28k
  *bucket = idx;
526
4.28k
  _oidc_strncpy(t->section_key, section_key, OIDC_CACHE_SHM_KEY_MAX - 1);
527
4.28k
  t->section_key[OIDC_CACHE_SHM_KEY_MAX - 1] = '\0';
528
4.28k
  oidc_cache_shm_entry_update(hdr, idx, value, expiry, access);
529
4.28k
}
530
531
4.92k
static void oidc_cache_shm_warn_pressure(request_rec *r, const oidc_cfg_t *cfg, apr_time_t pressure_age) {
532
4.92k
  if (pressure_age < 0)
533
4.92k
    return;
534
2
  oidc_warn(r,
535
2
      "dropping least-recently-used entry with age = %" APR_TIME_T_FMT
536
2
      "s, which is less than one hour; consider increasing the shared memory caching space "
537
2
      "(which is %d now) with the (global) " OIDCCacheShmMax " setting.",
538
2
      pressure_age, oidc_cfg_cache_shm_size_max_get(cfg));
539
2
}
540
541
/*
542
 * store a value in the shared memory cache
543
 */
544
static apr_byte_t oidc_cache_shm_set(request_rec *r, const char *section, const char *key, const char *value,
545
6.17k
             apr_time_t expiry) {
546
547
6.17k
  const oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
548
6.17k
  const oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg;
549
6.17k
  const int entry_size_max = oidc_cfg_cache_shm_entry_size_max_get(cfg);
550
551
6.17k
  const char *section_key = oidc_cache_shm_get_key(r, section, key);
552
6.17k
  if (section_key == NULL)
553
0
    return FALSE;
554
555
  /* Use signed subtraction so an undersized slot cannot wrap into a large value limit. */
556
6.17k
  const apr_ssize_t value_size_max = oidc_cache_shm_value_size_max(entry_size_max);
557
6.17k
  const apr_ssize_t value_size = value != NULL ? (apr_ssize_t)_oidc_strlen(value) : 0;
558
559
  /* check that the passed in value is valid; reject at ">=" rather than ">" so the NUL terminator
560
   * written by the _oidc_strcpy below always fits within the entry, independent of struct padding */
561
6.17k
  if ((value != NULL) && ((value_size_max <= 0) || (value_size >= value_size_max))) {
562
102
    oidc_error(r,
563
102
         "could not store value since value size is too large (%ld >= %ld); consider "
564
102
         "increasing " OIDCCacheShmEntrySizeMax "",
565
102
         (long)value_size, (long)value_size_max);
566
102
    return FALSE;
567
102
  }
568
569
6.07k
  oidc_cache_shm_header_t *hdr = oidc_cache_shm_base(context);
570
6.07k
  const apr_uint64_t hash = oidc_cache_shm_hash(section_key, hdr->hash_key);
571
6.07k
  const apr_uint32_t bucket_idx = (apr_uint32_t)hash & (hdr->nbuckets - 1);
572
6.07k
  apr_uint32_t *bucket = &oidc_cache_shm_buckets(hdr)[bucket_idx];
573
6.07k
  if (oidc_cache_mutex_lock(r->pool, r->server, context->mutex) == FALSE)
574
0
    return FALSE;
575
576
6.07k
  apr_uint32_t prev = 0;
577
6.07k
  apr_uint32_t idx = oidc_cache_shm_find(hdr, bucket, section_key, &prev);
578
6.07k
  if (value == NULL) {
579
1.14k
    if (idx != 0) {
580
0
      oidc_cache_shm_entry_t *t = oidc_cache_shm_slot(hdr, idx);
581
0
      if (prev != 0)
582
0
        oidc_cache_shm_slot(hdr, prev)->next = t->next;
583
0
      else
584
0
        *bucket = t->next;
585
0
      oidc_cache_shm_slot_free(hdr, t, idx);
586
0
    }
587
1.14k
    return oidc_cache_mutex_unlock(r->pool, r->server, context->mutex);
588
1.14k
  }
589
590
4.92k
  const apr_time_t current_time = apr_time_now();
591
4.92k
  apr_time_t pressure_age = -1;
592
4.92k
  if (idx != 0) {
593
645
    oidc_cache_shm_entry_update(hdr, idx, value, expiry, current_time);
594
4.28k
  } else {
595
4.28k
    if (hdr->free_head != 0) {
596
1.17k
      idx = hdr->free_head;
597
1.17k
      hdr->free_head = oidc_cache_shm_slot(hdr, idx)->next;
598
3.10k
    } else {
599
3.10k
      idx = oidc_cache_shm_evict(hdr, current_time, &pressure_age);
600
3.10k
    }
601
4.28k
    if (idx != 0)
602
4.28k
      oidc_cache_shm_entry_insert(hdr, bucket, idx, section_key, value, expiry, current_time);
603
4.28k
  }
604
605
4.92k
  const apr_byte_t unlocked = oidc_cache_mutex_unlock(r->pool, r->server, context->mutex);
606
4.92k
  if (idx == 0) {
607
0
    oidc_error(r, "could not obtain a cache slot");
608
0
    return FALSE;
609
0
  }
610
4.92k
  if (unlocked == FALSE)
611
0
    return FALSE;
612
4.92k
  oidc_cache_shm_warn_pressure(r, cfg, pressure_age);
613
4.92k
  return TRUE;
614
4.92k
}
615
616
0
static apr_status_t oidc_cache_shm_destroy_segment(apr_pool_t *pool, server_rec *s, oidc_cache_cfg_shm_t *context) {
617
0
  apr_byte_t locked = FALSE;
618
0
  if (context->mutex_ready == TRUE) {
619
0
    locked = oidc_cache_mutex_lock(pool, s, context->mutex);
620
0
    if (locked == FALSE)
621
0
      return APR_EGENERAL;
622
0
  }
623
624
0
  apr_status_t rv = apr_shm_destroy(context->shm);
625
0
  oidc_sdebug(s, "apr_shm_destroy returned: %d", rv);
626
0
  context->shm = NULL;
627
0
  if ((locked == TRUE) && (oidc_cache_mutex_unlock(pool, s, context->mutex) == FALSE))
628
0
    rv = APR_EGENERAL;
629
0
  return rv;
630
0
}
631
632
0
static apr_status_t oidc_cache_shm_destroy_mutex(server_rec *s, oidc_cache_cfg_shm_t *context) {
633
0
  if ((context->mutex != NULL) && (oidc_cache_mutex_destroy(s, context->mutex) != TRUE))
634
0
    return APR_EGENERAL;
635
0
  context->mutex = NULL;
636
0
  context->mutex_ready = FALSE;
637
0
  return APR_SUCCESS;
638
0
}
639
640
0
static int oidc_cache_shm_destroy(apr_pool_t *pool, server_rec *s) {
641
0
  oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module);
642
0
  oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg;
643
0
  apr_status_t rv = APR_SUCCESS;
644
645
0
  oidc_slog(s, APLOG_TRACE1, "destroy: %pp (shm=%pp,s=%pp, p=%d)", context, context ? context->shm : 0, s,
646
0
      context ? context->is_parent : -1);
647
648
0
  if (context == NULL)
649
0
    return rv;
650
0
  if ((context->is_parent == TRUE) && (context->shm != NULL))
651
0
    rv = oidc_cache_shm_destroy_segment(pool, s, context);
652
0
  if (oidc_cache_shm_destroy_mutex(s, context) != APR_SUCCESS)
653
0
    rv = APR_EGENERAL;
654
0
  return rv;
655
0
}
656
657
// clang-format off
658
659
oidc_cache_t oidc_cache_shm = {
660
  "shm",
661
  0,
662
  oidc_cache_shm_post_config,
663
  oidc_cache_shm_child_init,
664
  oidc_cache_shm_get,
665
  oidc_cache_shm_set,
666
  oidc_cache_shm_destroy
667
};
668
669
// clang-format on