1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2025 Bill Sommerfeld
14 */
15
16 /*
17 * Demonstration of idmap collisions due to missing locking and a
18 * fencepost error in get_next_eph_uid. On a 24-core Zen 4 system
19 * (EPYC 8224P) with a batchsize of 1000 and 20 threads, I see a
20 * couple dups per run. Dups ending in 001 are from the fencepost error.
21 *
22 * Build with: gcc -O2 -o idmaptest idmaptest.c -lidmap
23 */
24 #include <err.h>
25 #include <idmap.h>
26 #include <pthread.h>
27 #include <stdio.h>
28 #include <stdbool.h>
29 #include <time.h>
30
31 struct batch {
32 idmap_rid_t base;
33 int count;
34 uid_t *idbuf;
35 idmap_stat *statbuf;
36 idmap_stat *statbuf2;
37 };
38
39 static volatile int go;
40 static char sid[32];
41
42 static pthread_mutex_t m;
43 static int ready_count;
44 static bool test_gid;
45
46 void *
get_idmap_batch(void * arg)47 get_idmap_batch(void *arg)
48 {
49 idmap_rid_t rid;
50 int i;
51 struct batch *b = arg;
52
53 (void) pthread_mutex_lock(&m);
54 ready_count += 1;
55 (void) pthread_mutex_unlock(&m);
56
57 /* spinwait until all threads are created */
58 while (!go)
59 ;
60
61 for (i = 0; i < b->count; i++) {
62 idmap_get_handle_t *h;
63 rid = b->base + i;
64 (void) idmap_get_create(&h);
65 if (test_gid) {
66 (void) idmap_get_gidbysid(h, sid, rid,
67 IDMAP_REQ_FLG_USE_CACHE,
68 &b->idbuf[i], &b->statbuf[i]);
69 } else {
70 (void) idmap_get_uidbysid(h, sid, rid,
71 IDMAP_REQ_FLG_USE_CACHE,
72 &b->idbuf[i], &b->statbuf[i]);
73 }
74 b->statbuf2[i] = idmap_get_mappings(h);
75 idmap_get_destroy(h);
76 }
77 return (NULL);
78 }
79
80 #define NTHREAD 20
81 #define BATCHSIZE 1000
82 #define RIDBASE 2000
83
84 int
cmpugid(const void * a,const void * b)85 cmpugid(const void *a, const void *b)
86 {
87 uid_t x = *(const uid_t *)a;
88 uid_t y = *(const uid_t *)b;
89 if (x > y)
90 return (1);
91 if (x < y)
92 return (-1);
93 return (0);
94 }
95
96 static const struct timespec usec100 = { 0, 100000 };
97
98 bool
test_idmap()99 test_idmap()
100 {
101 int i, j, err;
102 time_t now;
103 bool fail = false;
104 const char *whatsit = test_gid ? "gid" : "uid";
105
106 pthread_t thread[NTHREAD];
107 struct batch b[NTHREAD];
108 uid_t idbuf[NTHREAD*BATCHSIZE];
109
110 go = 0;
111 ready_count = 0;
112
113 (void) time(&now);
114 (void) snprintf(sid, sizeof (sid), "S-1-5-21-44444444-%ld", now);
115
116 printf("testing for dup %s\n", whatsit);
117
118 printf("base sid: %s\n", sid);
119
120 for (i = 0; i < NTHREAD; i++) {
121 b[i].base = RIDBASE + i * BATCHSIZE;
122 b[i].count = BATCHSIZE;
123 b[i].idbuf = &idbuf[i * BATCHSIZE];
124 b[i].statbuf = calloc(BATCHSIZE, sizeof (idmap_stat));
125 b[i].statbuf2 = calloc(BATCHSIZE, sizeof (idmap_stat));
126 }
127 for (i = 0; i < NTHREAD; i++) {
128 err = pthread_create(&thread[i], NULL, get_idmap_batch, &b[i]);
129 if (err) {
130 errc(EXIT_FAILURE, err,
131 "Failed to create thread %d", i);
132 }
133 }
134
135 for (;;) {
136 int n;
137 (void) pthread_mutex_lock(&m);
138 n = ready_count;
139 (void) pthread_mutex_unlock(&m);
140 if (n == NTHREAD) {
141 go = 1;
142 break;
143 }
144 (void) nanosleep(&usec100, NULL);
145 }
146
147 go = 1;
148 for (i = 0; i < NTHREAD; i++) {
149 int err = pthread_join(thread[i], NULL);
150 if (err != 0) {
151 printf("thread %d error %d\n", i, err);
152 fail = true;
153 }
154 }
155 for (i = 0; i < NTHREAD; i++) {
156 for (j = 0; j < BATCHSIZE; j++) {
157 if (b[i].statbuf[j]) {
158 printf("fail 1: %d,%d => %d\n",
159 i, j, b[i].statbuf[j]);
160 fail = true;
161 }
162 if (b[i].statbuf2[j]) {
163 printf("fail 2: %d,%d => %d\n",
164 i, j, b[i].statbuf2[j]);
165 fail = true;
166 }
167 }
168 }
169 qsort(idbuf, NTHREAD*BATCHSIZE, sizeof (uid_t), cmpugid);
170 for (i = 1; i < NTHREAD*BATCHSIZE; i++) {
171 if (idbuf[i] == idbuf[i-1]) {
172 printf("dup %s %x\n", whatsit, idbuf[i]);
173 fail = true;
174 }
175 }
176 return (fail);
177 }
178
179 bool
idmapd_running()180 idmapd_running()
181 {
182 bool running = false;
183 idmap_get_handle_t *h;
184 idmap_stat status;
185 idmap_stat s = idmap_get_create(&h);
186 idmap_rid_t rid;
187 char *domain;
188
189 if (s != 0) {
190 fprintf(stderr, "Can't create idmap handle: %s\n",
191 idmap_stat2string(s));
192 return (false);
193 }
194
195 s = idmap_get_sidbyuid(h, 0,
196 IDMAP_REQ_FLG_USE_CACHE, &domain, &rid, &status);
197 if (s != IDMAP_SUCCESS) {
198 fprintf(stderr, "Can't create queue map request: %s\n",
199 idmap_stat2string(s));
200 } else if ((s = idmap_get_mappings(h)) != 0) {
201 fprintf(stderr, "idmap_get_mappings failed: %s\n",
202 idmap_stat2string(s));
203 } else if (status != IDMAP_SUCCESS) {
204 fprintf(stderr, "mapping of 0 failed: %s\n",
205 idmap_stat2string(status));
206 } else {
207 running = true;
208 }
209 idmap_get_destroy(h);
210 return (running);
211 }
212
213 int
main(int argc,char ** argv)214 main(int argc, char **argv)
215 {
216 bool fail = false;
217
218 if (!idmapd_running()) {
219 fprintf(stderr, "Is idmapd running?\n");
220 exit(4); /* signal a "SKIP" to the testing framework */
221 }
222 fprintf(stderr, "idmapd is running\n");
223
224 (void) pthread_mutex_init(&m, NULL);
225
226 test_gid = false;
227 if (test_idmap())
228 fail = true;
229
230 test_gid = true;
231 if (test_idmap())
232 fail = true;
233
234 if (fail) {
235 fprintf(stderr, "FAIL: test failed\n");
236 exit(EXIT_FAILURE);
237 }
238 fprintf(stderr, "PASS: duplicate ids not detected\n");
239 exit(EXIT_SUCCESS);
240 }
241