xref: /freebsd/crypto/krb5/src/lib/krb5/rcache/t_rcfile2.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/rcache/t_rcfile2.c - rcache file version 2 tests */
3 /*
4  * Copyright (C) 2019 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /*
34  * Usage:
35  *
36  *   t_rcfile2 <filename> expiry <nreps>
37  *     store <nreps> records spaced far enough apart that all records appear
38  *     expired; verify that the file size doesn't increase beyond one table.
39  *
40  *   t_rcfile2 <filename> concurrent <nprocesses> <nreps>
41  *     spawn <nprocesses> subprocesses, each of which stores <nreps> unique
42  *     tags.  As each process completes, the master process tests that the
43  *     records stored by the subprocess appears as replays.
44  *
45  *   t_rcfile2 <filename> race <nprocesses> <nreps>
46  *     spawn <nprocesses> subprocesses, each of which tries to store the same
47  *     tag and reports success or failure.  The master process verifies that
48  *     exactly one subprocess succeeds.  Repeat <reps> times.
49  */
50 
51 #include "rc_file2.c"
52 #include <sys/wait.h>
53 #include <sys/time.h>
54 
55 krb5_context ctx;
56 
57 static krb5_error_code
test_store(const char * filename,uint8_t * tag,krb5_timestamp timestamp,const uint32_t clockskew)58 test_store(const char *filename, uint8_t *tag, krb5_timestamp timestamp,
59            const uint32_t clockskew)
60 {
61     krb5_data tag_data = make_data(tag, TAG_LEN);
62 
63     ctx->clockskew = clockskew;
64     (void)krb5_set_debugging_time(ctx, timestamp, 0);
65     return file2_store(ctx, (void *)filename, &tag_data);
66 }
67 
68 /* Store a sequence of unique tags, with timestamps far enough apart that all
69  * previous records appear expired.  Verify that we only use one table. */
70 static void
expiry_test(const char * filename,int reps)71 expiry_test(const char *filename, int reps)
72 {
73     krb5_error_code ret;
74     struct stat statbuf;
75     uint8_t tag[TAG_LEN] = { 0 }, seed[K5_HASH_SEED_LEN] = { 0 }, data[4];
76     uint32_t timestamp;
77     const uint32_t clockskew = 5, start = 1000;
78     uint64_t hashval;
79     int i, st;
80 
81     assert((uint32_t)reps < (UINT32_MAX - start) / clockskew / 2);
82     for (i = 0, timestamp = start; i < reps; i++, timestamp += clockskew * 2) {
83         store_32_be(i, data);
84         hashval = k5_siphash24(data, 4, seed);
85         store_64_be(hashval, tag);
86 
87         ret = test_store(filename, tag, timestamp, clockskew);
88         assert(ret == 0);
89 
90         /* Since we increment timestamp enough to expire every record between
91          * each call, we should never create a second hash table. */
92         st = stat(filename, &statbuf);
93         assert(st == 0);
94         assert(statbuf.st_size <= (FIRST_TABLE_RECORDS + 1) * RECORD_LEN);
95     }
96 }
97 
98 /* Store a sequence of unique tags with the same timestamp.  Exit with failure
99  * if any store operation doesn't succeed or fail as given by expect_fail. */
100 static void
store_records(const char * filename,int id,int reps,int expect_fail)101 store_records(const char *filename, int id, int reps, int expect_fail)
102 {
103     krb5_error_code ret;
104     uint8_t tag[TAG_LEN] = { 0 };
105     int i;
106 
107     store_32_be(id, tag);
108     for (i = 0; i < reps; i++) {
109         store_32_be(i, tag + 4);
110         ret = test_store(filename, tag, 1000, 100);
111         if (ret != (expect_fail ? KRB5KRB_AP_ERR_REPEAT : 0)) {
112             fprintf(stderr, "store %d %d %sfail\n", id, i,
113                     expect_fail ? "didn't " : "");
114             _exit(1);
115         }
116     }
117 }
118 
119 /* Spawn multiple child processes, each storing a sequence of unique tags.
120  * After each process completes, verify that its tags appear as replays. */
121 static void
concurrency_test(const char * filename,int nchildren,int reps)122 concurrency_test(const char *filename, int nchildren, int reps)
123 {
124     pid_t *pids, pid;
125     int i, nprocs, status;
126 
127     pids = calloc(nchildren, sizeof(*pids));
128     assert(pids != NULL);
129     for (i = 0; i < nchildren; i++) {
130         pids[i] = fork();
131         assert(pids[i] != -1);
132         if (pids[i] == 0) {
133             store_records(filename, i, reps, 0);
134             _exit(0);
135         }
136     }
137     for (nprocs = nchildren; nprocs > 0; nprocs--) {
138         pid = wait(&status);
139         assert(pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0);
140         for (i = 0; i < nchildren; i++) {
141             if (pids[i] == pid)
142                 store_records(filename, i, reps, 1);
143         }
144     }
145     free(pids);
146 }
147 
148 /* Spawn multiple child processes, all trying to store the same tag.  Verify
149  * that only one of the processes succeeded.  Repeat reps times. */
150 static void
race_test(const char * filename,int nchildren,int reps)151 race_test(const char *filename, int nchildren, int reps)
152 {
153     int i, j, status, nsuccess;
154     uint8_t tag[TAG_LEN] = { 0 };
155     pid_t pid;
156 
157     for (i = 0; i < reps; i++) {
158         store_32_be(i, tag);
159         for (j = 0; j < nchildren; j++) {
160             pid = fork();
161             assert(pid != -1);
162             if (pid == 0)
163                 _exit(test_store(filename, tag, 1000, 100) != 0);
164         }
165 
166         nsuccess = 0;
167         for (j = 0; j < nchildren; j++) {
168             pid = wait(&status);
169             assert(pid != -1);
170             if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
171                 nsuccess++;
172         }
173         assert(nsuccess == 1);
174     }
175 }
176 
177 int
main(int argc,char ** argv)178 main(int argc, char **argv)
179 {
180     const char *filename, *cmd;
181 
182     argv++;
183     assert(*argv != NULL);
184 
185     if (krb5_init_context(&ctx) != 0)
186         abort();
187 
188     assert(*argv != NULL);
189     filename = *argv++;
190     unlink(filename);
191 
192     assert(*argv != NULL);
193     cmd = *argv++;
194     if (strcmp(cmd, "expiry") == 0) {
195         assert(argv[0] != NULL);
196         expiry_test(filename, atoi(argv[0]));
197     } else if (strcmp(cmd, "concurrent") == 0) {
198         assert(argv[0] != NULL && argv[1] != NULL);
199         concurrency_test(filename, atoi(argv[0]), atoi(argv[1]));
200     } else if (strcmp(cmd, "race") == 0) {
201         assert(argv[0] != NULL && argv[1] != NULL);
202         race_test(filename, atoi(argv[0]), atoi(argv[1]));
203     } else {
204         abort();
205     }
206 
207     krb5_free_context(ctx);
208     return 0;
209 }
210