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