Coverage Report

Created: 2026-05-24 06:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/krb5/src/kdc/replay.c
Line
Count
Source
1
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
/* kdc/replay.c - Replay lookaside cache for the KDC, to avoid extra work */
3
/*
4
 * Copyright 1991 by the Massachusetts Institute of Technology.
5
 * All Rights Reserved.
6
 *
7
 * Export of this software from the United States of America may
8
 *   require a specific license from the United States Government.
9
 *   It is the responsibility of any person or organization contemplating
10
 *   export to obtain such a license before exporting.
11
 *
12
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13
 * distribute this software and its documentation for any purpose and
14
 * without fee is hereby granted, provided that the above copyright
15
 * notice appear in all copies and that both that copyright notice and
16
 * this permission notice appear in supporting documentation, and that
17
 * the name of M.I.T. not be used in advertising or publicity pertaining
18
 * to distribution of the software without specific, written prior
19
 * permission.  Furthermore if you modify this software you must label
20
 * your software as modified software and not distribute it in such a
21
 * fashion that it might be confused with the original M.I.T. software.
22
 * M.I.T. makes no representations about the suitability of
23
 * this software for any purpose.  It is provided "as is" without express
24
 * or implied warranty.
25
 */
26
27
#include "k5-int.h"
28
#include "k5-queue.h"
29
#include "k5-hashtab.h"
30
#include "kdc_util.h"
31
#include "extern.h"
32
33
#ifndef NOCACHE
34
35
struct entry {
36
    K5_TAILQ_ENTRY(entry) links;
37
    int num_hits;
38
    krb5_timestamp timein;
39
    krb5_data req_packet;
40
    krb5_data reply_packet;
41
};
42
43
#ifndef LOOKASIDE_MAX_SIZE
44
0
#define LOOKASIDE_MAX_SIZE (10 * 1024 * 1024)
45
#endif
46
47
K5_LIST_HEAD(entry_list, entry);
48
K5_TAILQ_HEAD(entry_queue, entry);
49
50
static struct k5_hashtab *hash_table;
51
static struct entry_queue expiration_queue;
52
53
static int hits = 0;
54
static int calls = 0;
55
static int max_hits_per_entry = 0;
56
static int num_entries = 0;
57
static size_t total_size = 0;
58
59
0
#define STALE_TIME      (2*60)            /* two minutes */
60
0
#define STALE(ptr, now) (ts_after(now, ts_incr((ptr)->timein, STALE_TIME)))
61
62
/* Return the rough memory footprint of an entry containing req and rep. */
63
static size_t
64
entry_size(const krb5_data *req, const krb5_data *rep)
65
62
{
66
62
    return sizeof(struct entry) + req->length +
67
62
        ((rep == NULL) ? 0 : rep->length);
68
62
}
69
70
/* Insert an entry into the cache. */
71
static struct entry *
72
insert_entry(krb5_context context, krb5_data *req, krb5_data *rep,
73
             krb5_timestamp time)
74
31
{
75
31
    krb5_error_code ret;
76
31
    struct entry *entry;
77
31
    size_t esize = entry_size(req, rep);
78
79
31
    entry = calloc(1, sizeof(*entry));
80
31
    if (entry == NULL)
81
0
        goto error;
82
31
    entry->timein = time;
83
84
31
    ret = krb5int_copy_data_contents(context, req, &entry->req_packet);
85
31
    if (ret)
86
0
        goto error;
87
88
31
    if (rep != NULL) {
89
31
        ret = krb5int_copy_data_contents(context, rep, &entry->reply_packet);
90
31
        if (ret)
91
0
            goto error;
92
31
    }
93
94
31
    ret = k5_hashtab_add(hash_table, entry->req_packet.data,
95
31
                         entry->req_packet.length, entry);
96
31
    if (ret)
97
0
        goto error;
98
31
    K5_TAILQ_INSERT_TAIL(&expiration_queue, entry, links);
99
31
    num_entries++;
100
31
    total_size += esize;
101
102
31
    return entry;
103
104
0
error:
105
0
    if (entry != NULL) {
106
0
        krb5_free_data_contents(context, &entry->req_packet);
107
0
        krb5_free_data_contents(context, &entry->reply_packet);
108
0
        free(entry);
109
0
    }
110
0
    return NULL;
111
31
}
112
113
114
/* Remove entry from its hash bucket and the expiration queue, and free it. */
115
static void
116
discard_entry(krb5_context context, struct entry *entry)
117
31
{
118
31
    total_size -= entry_size(&entry->req_packet, &entry->reply_packet);
119
31
    num_entries--;
120
31
    k5_hashtab_remove(hash_table, entry->req_packet.data,
121
31
                      entry->req_packet.length);
122
31
    K5_TAILQ_REMOVE(&expiration_queue, entry, links);
123
31
    krb5_free_data_contents(context, &entry->req_packet);
124
31
    krb5_free_data_contents(context, &entry->reply_packet);
125
31
    free(entry);
126
31
}
127
128
/* Initialize the lookaside cache structures and randomize the hash seed. */
129
krb5_error_code
130
kdc_init_lookaside(krb5_context context)
131
31
{
132
31
    krb5_error_code ret;
133
31
    uint8_t seed[K5_HASH_SEED_LEN];
134
31
    krb5_data d = make_data(seed, sizeof(seed));
135
136
31
    ret = krb5_c_random_make_octets(context, &d);
137
31
    if (ret)
138
0
        return ret;
139
31
    ret = k5_hashtab_create(seed, 8192, &hash_table);
140
31
    if (ret)
141
0
        return ret;
142
31
    K5_TAILQ_INIT(&expiration_queue);
143
31
    return 0;
144
31
}
145
146
/* Remove the lookaside cache entry for a packet. */
147
void
148
kdc_remove_lookaside(krb5_context kcontext, krb5_data *req_packet)
149
0
{
150
0
    struct entry *e;
151
152
0
    e = k5_hashtab_get(hash_table, req_packet->data, req_packet->length);
153
0
    if (e != NULL)
154
0
        discard_entry(kcontext, e);
155
0
}
156
157
/*
158
 * Return true and fill in reply_packet_out if req_packet is in the lookaside
159
 * cache; otherwise return false.
160
 *
161
 * If the request was inserted with a NULL reply_packet to indicate that a
162
 * request is still being processed, then return TRUE with reply_packet_out set
163
 * to NULL.
164
 */
165
krb5_boolean
166
kdc_check_lookaside(krb5_context kcontext, krb5_data *req_packet,
167
                    krb5_data **reply_packet_out)
168
0
{
169
0
    struct entry *e;
170
171
0
    *reply_packet_out = NULL;
172
0
    calls++;
173
174
0
    e = k5_hashtab_get(hash_table, req_packet->data, req_packet->length);
175
0
    if (e == NULL)
176
0
        return FALSE;
177
178
0
    e->num_hits++;
179
0
    hits++;
180
181
    /* Leave *reply_packet_out as NULL for an in-progress entry. */
182
0
    if (e->reply_packet.length == 0)
183
0
        return TRUE;
184
185
0
    return (krb5_copy_data(kcontext, &e->reply_packet,
186
0
                           reply_packet_out) == 0);
187
0
}
188
189
/*
190
 * Insert a request and reply into the lookaside cache.  Assumes it's not
191
 * already there, and can fail silently on memory exhaustion.  Also discard old
192
 * entries in the cache.
193
 *
194
 * The reply_packet may be NULL to indicate a request that is still processing.
195
 */
196
void
197
kdc_insert_lookaside(krb5_context kcontext, krb5_data *req_packet,
198
                     krb5_data *reply_packet)
199
0
{
200
0
    struct entry *e, *next;
201
0
    krb5_timestamp timenow;
202
0
    size_t esize = entry_size(req_packet, reply_packet);
203
204
0
    if (krb5_timeofday(kcontext, &timenow))
205
0
        return;
206
207
    /* Purge stale entries and limit the total size of the entries. */
208
0
    K5_TAILQ_FOREACH_SAFE(e, &expiration_queue, links, next) {
209
0
        if (!STALE(e, timenow) && total_size + esize <= LOOKASIDE_MAX_SIZE)
210
0
            break;
211
0
        max_hits_per_entry = max(max_hits_per_entry, e->num_hits);
212
0
        discard_entry(kcontext, e);
213
0
    }
214
215
0
    insert_entry(kcontext, req_packet, reply_packet, timenow);
216
0
    return;
217
0
}
218
219
/* Free all entries in the lookaside cache. */
220
void
221
kdc_free_lookaside(krb5_context kcontext)
222
31
{
223
31
    struct entry *e, *next;
224
225
31
    K5_TAILQ_FOREACH_SAFE(e, &expiration_queue, links, next) {
226
0
        discard_entry(kcontext, e);
227
0
    }
228
31
    k5_hashtab_free(hash_table);
229
31
}
230
231
#endif /* NOCACHE */