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 * 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 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 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 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 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