/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 |