source: mod_gnutls/src/gnutls_cache.c @ d8d6b1e

debian/masterproxy-ticket
Last change on this file since d8d6b1e was d8d6b1e, checked in by Fiona Klute <fiona.klute@…>, 3 years ago

Reallocate buffer to data size after fetching from socache

There is no way to know the size of an socache object before fetching,
so the provided buffer must be large. To avoid wasting memory shrink
the buffer to the actual size of the object after fetching.

  • Property mode set to 100644
File size: 14.2 KB
Line 
1/*
2 *  Copyright 2004-2005 Paul Querna
3 *  Copyright 2008 Nikos Mavrogiannopoulos
4 *  Copyright 2011 Dash Shendy
5 *  Copyright 2015-2018 Fiona Klute
6 *
7 *  Licensed under the Apache License, Version 2.0 (the "License");
8 *  you may not use this file except in compliance with the License.
9 *  You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 *  Unless required by applicable law or agreed to in writing, software
14 *  distributed under the License is distributed on an "AS IS" BASIS,
15 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 *  See the License for the specific language governing permissions and
17 *  limitations under the License.
18 */
19
20/**
21 * @file gnutls_cache.c
22 *
23 * This file contains the cache implementation used for session
24 * caching and OCSP stapling. The `socache_*_session` functions
25 * implement the GnuTLS session cache API using the configured cache,
26 * using mgs_cache_store() and mgs_cache_fetch() as appropriate (see
27 * gnutls_cache.h).
28 */
29
30#include "gnutls_cache.h"
31#include "mod_gnutls.h"
32#include "gnutls_config.h"
33
34#include <ap_socache.h>
35#include <apr_escape.h>
36#include <util_mutex.h>
37
38/** Default session cache timeout */
39#define MGS_DEFAULT_CACHE_TIMEOUT 300
40
41/** Maximum length of the hex string representation of a GnuTLS
42 * session ID: two characters per byte, plus one more for `\0` */
43#if GNUTLS_VERSION_NUMBER >= 0x030400
44#define GNUTLS_SESSION_ID_STRING_LEN ((GNUTLS_MAX_SESSION_ID_SIZE * 2) + 1)
45#else
46#define GNUTLS_SESSION_ID_STRING_LEN ((GNUTLS_MAX_SESSION_ID * 2) + 1)
47#endif
48
49#ifdef APLOG_USE_MODULE
50APLOG_USE_MODULE(gnutls);
51#endif
52
53/**
54 * Turn a GnuTLS session ID into the key format we use for
55 * caches. Name the Session ID as `server:port.SessionID` to disallow
56 * resuming sessions on different servers.
57 *
58 * @return `0` on success, `-1` on failure
59 */
60static int mgs_session_id2dbm(conn_rec *c, unsigned char *id, int idlen,
61                              gnutls_datum_t *dbmkey)
62{
63    char sz[GNUTLS_SESSION_ID_STRING_LEN];
64    apr_status_t rv = apr_escape_hex(sz, id, idlen, 0, NULL);
65    if (rv != APR_SUCCESS)
66        return -1;
67
68    char *newkey = apr_psprintf(c->pool, "%s:%d.%s",
69                                c->base_server->server_hostname,
70                                c->base_server->port, sz);
71    dbmkey->size = strlen(newkey);
72    /* signedness does not matter for arbitrary bits */
73    dbmkey->data = (unsigned char*) newkey;
74    return 0;
75}
76
77/** The OPENSSL_TIME_FORMAT macro and mgs_time2sz() serve to print
78 * time in a format compatible with OpenSSL's `ASN1_TIME_print()`
79 * function. */
80#define OPENSSL_TIME_FORMAT "%b %d %k:%M:%S %Y %Z"
81
82char *mgs_time2sz(time_t in_time, char *str, int strsize)
83{
84    apr_time_exp_t vtm;
85    apr_size_t ret_size;
86    apr_time_t t;
87
88
89    apr_time_ansi_put(&t, in_time);
90    apr_time_exp_gmt(&vtm, t);
91    apr_strftime(str, &ret_size, strsize - 1, OPENSSL_TIME_FORMAT, &vtm);
92
93    return str;
94}
95
96
97
98int mgs_cache_store(mgs_cache_t cache, server_rec *server,
99                    gnutls_datum_t key, gnutls_datum_t data,
100                    apr_time_t expiry)
101{
102    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
103        ap_get_module_config(server->module_config, &gnutls_module);
104
105    apr_pool_t *spool;
106    apr_pool_create(&spool, NULL);
107
108    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
109        apr_global_mutex_lock(cache->mutex);
110    apr_status_t rv = cache->prov->store(cache->socache, server,
111                                         key.data, key.size,
112                                         expiry,
113                                         data.data, data.size,
114                                         spool);
115    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
116        apr_global_mutex_unlock(cache->mutex);
117
118    if (rv != APR_SUCCESS)
119    {
120        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, server,
121                     "error storing in cache '%s:%s'",
122                     cache->prov->name, sc->cache->config);
123        apr_pool_destroy(spool);
124        return -1;
125    }
126
127    ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
128                 "stored %u bytes of data (%u byte key) in cache '%s:%s'",
129                 data.size, key.size,
130                 cache->prov->name, sc->cache->config);
131    apr_pool_destroy(spool);
132    return 0;
133}
134
135
136
137/**
138 * Store function for the GnuTLS session cache, see
139 * gnutls_db_set_store_function().
140 *
141 * @param baton mgs_handle_t for the connection, as set via
142 * gnutls_db_set_ptr()
143 *
144 * @param key object key to store
145 *
146 * @param data the object to store
147 *
148 * @return `0` in case of success, `-1` in case of failure
149 */
150static int socache_store_session(void *baton, gnutls_datum_t key,
151                                 gnutls_datum_t data)
152{
153    mgs_handle_t *ctxt = baton;
154    gnutls_datum_t dbmkey;
155
156    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
157        return -1;
158
159    apr_time_t expiry = apr_time_now() + ctxt->sc->cache_timeout;
160
161    return mgs_cache_store(ctxt->sc->cache, ctxt->c->base_server,
162                           dbmkey, data, expiry);
163}
164
165
166
167/** 8K is the maximum size accepted when receiving OCSP responses,
168 * sessions cache entries should be much smaller. The buffer is
169 * reallocated to actual size after fetching, so memory waste is
170 * minimal and temporary. */
171#define SOCACHE_FETCH_BUF_SIZE (8 * 1024)
172
173gnutls_datum_t mgs_cache_fetch(mgs_cache_t cache, server_rec *server,
174                               gnutls_datum_t key, apr_pool_t *pool)
175{
176    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
177        ap_get_module_config(server->module_config, &gnutls_module);
178
179    gnutls_datum_t data = {NULL, 0};
180    data.data = gnutls_malloc(SOCACHE_FETCH_BUF_SIZE);
181    if (data.data == NULL)
182        return data;
183    data.size = SOCACHE_FETCH_BUF_SIZE;
184
185    apr_pool_t *spool;
186    apr_pool_create(&spool, pool);
187
188    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
189        apr_global_mutex_lock(cache->mutex);
190    apr_status_t rv = cache->prov->retrieve(cache->socache, server,
191                                            key.data, key.size,
192                                            data.data, &data.size,
193                                            spool);
194    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
195        apr_global_mutex_unlock(cache->mutex);
196
197    if (rv != APR_SUCCESS)
198    {
199        /* APR_NOTFOUND means there's no such object. */
200        if (rv == APR_NOTFOUND)
201            ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
202                         "requested entry not found in cache '%s:%s'.",
203                         cache->prov->name, sc->cache->config);
204        else
205            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, server,
206                         "error fetching from cache '%s:%s'",
207                         cache->prov->name, sc->cache->config);
208        /* free unused buffer */
209        gnutls_free(data.data);
210        data.data = NULL;
211        data.size = 0;
212    }
213    else
214    {
215        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
216                     "fetched %u bytes from cache '%s:%s'",
217                     data.size, cache->prov->name, sc->cache->config);
218
219        /* Realloc buffer to data.size. Data size must be less than or
220         * equal to the initial buffer size, so this REALLY should not
221         * fail. */
222        data.data = gnutls_realloc(data.data, data.size);
223        if (__builtin_expect(data.data == NULL, 0))
224        {
225            ap_log_error(APLOG_MARK, APLOG_CRIT, APR_ENOMEM, server,
226                         "%s: Could not realloc fetch buffer to data size!",
227                         __func__);
228            data.size = 0;
229        }
230    }
231    apr_pool_destroy(spool);
232
233    return data;
234}
235
236
237
238/**
239 * Fetch function for the GnuTLS session cache, see
240 * gnutls_db_set_retrieve_function().
241 *
242 * *Warning*: The `data` element of the returned `gnutls_datum_t` is
243 * allocated using `gnutls_malloc()` for compatibility with the GnuTLS
244 * session caching API, and must be released using `gnutls_free()`.
245 *
246 * @param baton mgs_handle_t for the connection, as set via
247 * gnutls_db_set_ptr()
248 *
249 * @param key object key to fetch
250 *
251 * @return the requested cache entry, or `{NULL, 0}`
252 */
253static gnutls_datum_t socache_fetch_session(void *baton, gnutls_datum_t key)
254{
255    gnutls_datum_t data = {NULL, 0};
256    gnutls_datum_t dbmkey;
257    mgs_handle_t *ctxt = baton;
258
259    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
260        return data;
261
262    return mgs_cache_fetch(ctxt->sc->cache, ctxt->c->base_server,
263                           dbmkey, ctxt->c->pool);
264}
265
266
267
268/**
269 * Remove function for the GnuTLS session cache, see
270 * gnutls_db_set_remove_function().
271 *
272 * @param baton mgs_handle_t for the connection, as set via
273 * gnutls_db_set_ptr()
274 *
275 * @param key object key to remove
276 *
277 * @return `0` in case of success, `-1` in case of failure
278 */
279static int socache_delete_session(void *baton, gnutls_datum_t key)
280{
281    gnutls_datum_t tmpkey;
282    mgs_handle_t *ctxt = baton;
283
284    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &tmpkey) < 0)
285        return -1;
286
287    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
288        apr_global_mutex_lock(ctxt->sc->cache->mutex);
289    apr_status_t rv = ctxt->sc->cache->prov->remove(ctxt->sc->cache->socache,
290                                                    ctxt->c->base_server,
291                                                    key.data, key.size,
292                                                    ctxt->c->pool);
293    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
294        apr_global_mutex_unlock(ctxt->sc->cache->mutex);
295
296    if (rv != APR_SUCCESS) {
297        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv,
298                     ctxt->c->base_server,
299                     "error deleting from cache '%s:%s'",
300                     ctxt->sc->cache->prov->name, ctxt->sc->cache->config);
301        return -1;
302    }
303    return 0;
304}
305
306
307
308static apr_status_t cleanup_socache(void *data)
309{
310    server_rec *s = data;
311    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
312        ap_get_module_config(s->module_config, &gnutls_module);
313    ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
314                 "Cleaning up socache '%s:%s'",
315                 sc->cache->prov->name, sc->cache->config);
316    sc->cache->prov->destroy(sc->cache->socache, s);
317    return APR_SUCCESS;
318}
319
320
321
322int mgs_cache_post_config(apr_pool_t *pconf, apr_pool_t *ptemp,
323                          server_rec *s, mgs_srvconf_rec *sc)
324{
325    apr_status_t rv = APR_SUCCESS;
326    /* GnuTLSCache was never explicitly set or is disabled: */
327    if (sc->cache_enable == GNUTLS_ENABLED_UNSET
328        || sc->cache_enable == GNUTLS_ENABLED_FALSE)
329    {
330        sc->cache_enable = GNUTLS_ENABLED_FALSE;
331        /* Cache disabled, done. */
332        return APR_SUCCESS;
333    }
334    /* if GnuTLSCacheTimeout was never explicitly set: */
335    if (sc->cache_timeout == MGS_TIMEOUT_UNSET)
336        sc->cache_timeout = apr_time_from_sec(MGS_DEFAULT_CACHE_TIMEOUT);
337
338    /* initialize cache structure and mutex if needed */
339    if (sc->cache == NULL)
340    {
341        sc->cache = apr_pcalloc(pconf, sizeof(struct mgs_cache));
342        rv = ap_global_mutex_create(&sc->cache->mutex, NULL,
343                                    MGS_CACHE_MUTEX_NAME,
344                                    NULL, s, pconf, 0);
345        if (rv != APR_SUCCESS)
346            return rv;
347    }
348
349    /* Find the right socache provider */
350    sc->cache->prov = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
351                                         sc->cache_type,
352                                         AP_SOCACHE_PROVIDER_VERSION);
353    if (sc->cache->prov)
354    {
355        /* Create and configure the cache instance. */
356        sc->cache->config = sc->cache_config;
357        const char *err = sc->cache->prov->create(&sc->cache->socache,
358                                                  sc->cache->config,
359                                                  ptemp, pconf);
360        if (err != NULL)
361        {
362            ap_log_error(APLOG_MARK, APLOG_EMERG, APR_EGENERAL, s,
363                         "Creating cache '%s:%s' failed: %s",
364                         sc->cache_type, sc->cache->config, err);
365            return HTTP_INSUFFICIENT_STORAGE;
366        }
367        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
368                     "%s: Socache '%s' created.", __func__, sc->cache_type);
369
370        // TODO: provide hints
371        rv = sc->cache->prov->init(sc->cache->socache,
372                                   "mod_gnutls-session", NULL, s, pconf);
373        if (rv != APR_SUCCESS)
374        {
375            ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
376                         "Initializing cache '%s:%s' failed!",
377                         sc->cache_type, sc->cache->config);
378            return HTTP_INSUFFICIENT_STORAGE;
379        }
380        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
381                     "%s: socache '%s:%s' created.", __func__,
382                     sc->cache_type, sc->cache->config);
383    }
384    else
385    {
386        ap_log_error(APLOG_MARK, APLOG_EMERG, APR_EGENERAL, s,
387                     "Could not find socache provider '%s', please make sure "
388                     "that the provider name is valid and the "
389                     "appropriate mod_socache submodule is loaded.",
390                     sc->cache_type);
391        return HTTP_NOT_FOUND;
392    }
393
394    apr_pool_pre_cleanup_register(pconf, s, cleanup_socache);
395
396    return APR_SUCCESS;
397}
398
399int mgs_cache_child_init(apr_pool_t * p,
400                         server_rec * s,
401                         mgs_srvconf_rec * sc)
402{
403    /* reinit cache mutex */
404    const char *lockfile = apr_global_mutex_lockfile(sc->cache->mutex);
405    apr_status_t rv = apr_global_mutex_child_init(&sc->cache->mutex,
406                                                  lockfile, p);
407    if (rv != APR_SUCCESS)
408        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
409                     "Failed to reinit mutex '%s'", MGS_CACHE_MUTEX_NAME);
410
411    return 0;
412}
413
414int mgs_cache_session_init(mgs_handle_t * ctxt)
415{
416    if (ctxt->sc->cache_enable)
417    {
418        gnutls_db_set_retrieve_function(ctxt->session,
419                                        socache_fetch_session);
420        gnutls_db_set_remove_function(ctxt->session,
421                                      socache_delete_session);
422        gnutls_db_set_store_function(ctxt->session,
423                                     socache_store_session);
424        gnutls_db_set_ptr(ctxt->session, ctxt);
425    }
426    return 0;
427}
Note: See TracBrowser for help on using the repository browser.