/src/freeradius-server/src/lib/util/sem.c
Line | Count | Source |
1 | | /* |
2 | | * This program is is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or (at |
5 | | * your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** Implementation of named semaphores that release on exit |
18 | | * |
19 | | * @file src/lib/util/sem.c |
20 | | * |
21 | | * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
22 | | */ |
23 | | RCSID("$Id: 89fd1a4d1737162d8fcbb18cb4b6085ccec99461 $") |
24 | | |
25 | | /* |
26 | | * Semaphore functions missing in musl/emscripten. |
27 | | * |
28 | | * This isn't really needed for browser functionality anyway |
29 | | */ |
30 | | #ifndef __EMSCRIPTEN__ |
31 | | #include <sys/ipc.h> |
32 | | #include <sys/sem.h> |
33 | | #include <sys/stat.h> |
34 | | |
35 | | #include <stdbool.h> |
36 | | #include <signal.h> |
37 | | |
38 | | #include <freeradius-devel/util/perm.h> |
39 | | #include <freeradius-devel/util/sem.h> |
40 | | #include <freeradius-devel/util/strerror.h> |
41 | | #include <freeradius-devel/util/syserror.h> |
42 | | |
43 | 0 | #define DEFAULT_PROJ_ID ((int)'f') /* Only 8 bits are used */ |
44 | | |
45 | | /** Return the PID of the process that last operated on the semaphore |
46 | | * |
47 | | * @param[out] pid that last modified the semaphore. |
48 | | * @param[in] sem_id semaphore ID. |
49 | | * @return |
50 | | * - 0 on success. |
51 | | * - -1 on failure. |
52 | | */ |
53 | | int fr_sem_pid(pid_t *pid, int sem_id) |
54 | 0 | { |
55 | 0 | int ret; |
56 | |
|
57 | 0 | ret = semctl(sem_id, 0, GETPID); |
58 | 0 | if (ret < 0) { |
59 | 0 | fr_strerror_printf("Failed getting semaphore PID: %s", fr_syserror(errno)); |
60 | 0 | return -1; |
61 | 0 | } |
62 | | |
63 | 0 | *pid = (pid_t)ret; |
64 | |
|
65 | 0 | return 0; |
66 | 0 | } |
67 | | |
68 | | /** Return the UID that last operated on the semaphore |
69 | | * |
70 | | * @param[out] uid the last modified the semaphore. |
71 | | * @param[in] sem_id semaphore ID. |
72 | | * @return |
73 | | * - 0 on success. |
74 | | * - -1 on failure. |
75 | | */ |
76 | | int fr_sem_uid(uid_t *uid, int sem_id) |
77 | 0 | { |
78 | 0 | int ret; |
79 | 0 | struct semid_ds info; |
80 | |
|
81 | 0 | ret = semctl(sem_id, 0, IPC_STAT, &info); |
82 | 0 | if (ret < 0) { |
83 | 0 | *uid = 0; |
84 | |
|
85 | 0 | fr_strerror_printf("Failed getting semaphore UID: %s", fr_syserror(errno)); |
86 | 0 | return -1; |
87 | 0 | } |
88 | | |
89 | 0 | *uid = info.sem_perm.uid; |
90 | |
|
91 | 0 | return 0; |
92 | 0 | } |
93 | | |
94 | | /** Return the GID that last operated on the semaphore |
95 | | * |
96 | | * @param[out] gid the last modified the semaphore. |
97 | | * @param[in] sem_id semaphore ID. |
98 | | * @return |
99 | | * - 0 on success. |
100 | | * - -1 on failure. |
101 | | */ |
102 | | int fr_sem_gid(uid_t *gid, int sem_id) |
103 | 0 | { |
104 | 0 | int ret; |
105 | 0 | struct semid_ds info; |
106 | |
|
107 | 0 | ret = semctl(sem_id, 0, IPC_STAT, &info); |
108 | 0 | if (ret < 0) { |
109 | 0 | *gid = 0; |
110 | |
|
111 | 0 | fr_strerror_printf("Failed getting semaphore GID: %s", fr_syserror(errno)); |
112 | 0 | return -1; |
113 | 0 | } |
114 | | |
115 | 0 | *gid = info.sem_perm.gid; |
116 | |
|
117 | 0 | return 0; |
118 | 0 | } |
119 | | |
120 | | /** Return the UID that created the semaphore |
121 | | * |
122 | | * @param[out] uid the last modified the semaphore. |
123 | | * @param[in] sem_id semaphore ID. |
124 | | * @return |
125 | | * - 0 on success. |
126 | | * - -1 on failure. |
127 | | */ |
128 | | int fr_sem_cuid(uid_t *uid, int sem_id) |
129 | 0 | { |
130 | 0 | int ret; |
131 | 0 | struct semid_ds info; |
132 | |
|
133 | 0 | ret = semctl(sem_id, 0, IPC_STAT, &info); |
134 | 0 | if (ret < 0) { |
135 | 0 | *uid = 0; |
136 | |
|
137 | 0 | fr_strerror_printf("Failed getting semaphore CUID: %s", fr_syserror(errno)); |
138 | 0 | return -1; |
139 | 0 | } |
140 | | |
141 | 0 | *uid = info.sem_perm.cuid; |
142 | |
|
143 | 0 | return 0; |
144 | 0 | } |
145 | | |
146 | | /** Return the GID that created the semaphore |
147 | | * |
148 | | * @param[out] gid the last modified the semaphore. |
149 | | * @param[in] sem_id semaphore ID. |
150 | | * @return |
151 | | * - 0 on success. |
152 | | * - -1 on failure. |
153 | | */ |
154 | | int fr_sem_cgid(uid_t *gid, int sem_id) |
155 | 0 | { |
156 | 0 | int ret; |
157 | 0 | struct semid_ds info; |
158 | |
|
159 | 0 | ret = semctl(sem_id, 0, IPC_STAT, &info); |
160 | 0 | if (ret < 0) { |
161 | 0 | *gid = 0; |
162 | |
|
163 | 0 | fr_strerror_printf("Failed getting semaphore CGID: %s", fr_syserror(errno)); |
164 | 0 | return -1; |
165 | 0 | } |
166 | | |
167 | 0 | *gid = info.sem_perm.cgid; |
168 | |
|
169 | 0 | return 0; |
170 | 0 | } |
171 | | |
172 | | /** Decrement the semaphore by 1 |
173 | | * |
174 | | * @param[in] sem_id to take. |
175 | | * @param[in] file to use in error messages. |
176 | | * @param[in] undo_on_exit decrement the semaphore on exit. |
177 | | * @return |
178 | | * - 1 already at 0. |
179 | | * - 0 on success. |
180 | | * - -1 on failure. |
181 | | */ |
182 | | int fr_sem_post(int sem_id, char const *file, bool undo_on_exit) |
183 | 0 | { |
184 | 0 | unsigned int num; |
185 | |
|
186 | 0 | errno = 0; |
187 | 0 | num = semctl(sem_id, 0, GETVAL); |
188 | 0 | if (errno != 0) { |
189 | 0 | fr_strerror_printf("Failed getting value from semaphore bound to \"%s\" - %s", file, |
190 | 0 | fr_syserror(errno)); |
191 | 0 | return 01; |
192 | 0 | } |
193 | 0 | if (num == 0) return 1; |
194 | | |
195 | 0 | { |
196 | 0 | struct sembuf sop = { |
197 | 0 | .sem_num = 0, |
198 | 0 | .sem_op = -1, |
199 | 0 | .sem_flg = undo_on_exit * SEM_UNDO |
200 | 0 | }; |
201 | |
|
202 | 0 | if (semop(sem_id, &sop, 1) < 0) { |
203 | 0 | fr_strerror_printf("Failed posting semaphore bound to \"%s\" - %s", file, |
204 | 0 | fr_syserror(errno)); |
205 | 0 | return -1; |
206 | 0 | } |
207 | 0 | } |
208 | | |
209 | 0 | return 0; |
210 | 0 | } |
211 | | |
212 | | /** Increment the semaphore by 1 |
213 | | * |
214 | | * @param[in] sem_id to take. |
215 | | * @param[in] file to use in error messages. |
216 | | * @param[in] undo_on_exit decrement the semaphore on exit. |
217 | | * @return |
218 | | * - -1 on failure. |
219 | | * - 0 on success. |
220 | | */ |
221 | | int fr_sem_take(int sem_id, char const *file, bool undo_on_exit) |
222 | 0 | { |
223 | 0 | struct sembuf sop = { |
224 | 0 | .sem_num = 0, |
225 | 0 | .sem_op = 1, |
226 | 0 | .sem_flg = undo_on_exit * SEM_UNDO |
227 | 0 | }; |
228 | |
|
229 | 0 | if (semop(sem_id, &sop, 1) < 0) { |
230 | 0 | fr_strerror_printf("Failed waiting on semaphore bound to \"%s\" - %s", file, |
231 | 0 | fr_syserror(errno)); |
232 | 0 | return -1; |
233 | 0 | } |
234 | | |
235 | 0 | return 0; |
236 | 0 | } |
237 | | |
238 | | /** Wait for a semaphore to reach 0, then increment it by 1 |
239 | | * |
240 | | * @param[in] sem_id to operate on. |
241 | | * @param[in] file to use in error messages. |
242 | | * @param[in] undo_on_exit If true, semaphore will be decremented if |
243 | | * this process exits. |
244 | | * @param[in] nonblock If true, don't wait and return 1 if the |
245 | | * semaphore is not at 0. |
246 | | * @return |
247 | | * - 1 would have blocked waiting for semaphore. |
248 | | * - 0 incremented the semaphore. |
249 | | * - -1 permissions error (EACCES). |
250 | | * - -2 another error occurred. |
251 | | */ |
252 | | int fr_sem_wait(int sem_id, char const *file, bool undo_on_exit, bool nonblock) |
253 | 0 | { |
254 | 0 | struct sembuf sops[2]; |
255 | 0 | short flags_nonblock; |
256 | 0 | short flags_undo; |
257 | |
|
258 | 0 | flags_nonblock = nonblock * IPC_NOWAIT; |
259 | 0 | flags_undo = undo_on_exit * SEM_UNDO; |
260 | | |
261 | | /* |
262 | | * The semop operation below only completes |
263 | | * successfully if the semaphore is at 0 |
264 | | * which prevents races. |
265 | | */ |
266 | 0 | sops[0].sem_num = 0; |
267 | 0 | sops[0].sem_op = 0; |
268 | 0 | sops[0].sem_flg = flags_nonblock; |
269 | |
|
270 | 0 | sops[1].sem_num = 0; |
271 | 0 | sops[1].sem_op = 1; |
272 | 0 | sops[1].sem_flg = flags_nonblock | flags_undo; |
273 | |
|
274 | 0 | if (semop(sem_id, sops, 2) < 0) { |
275 | 0 | pid_t sem_pid; |
276 | 0 | uid_t uid; |
277 | 0 | gid_t gid; |
278 | 0 | int semop_err = errno; |
279 | 0 | char *uid_str; |
280 | 0 | char *gid_str; |
281 | 0 | int ret; |
282 | 0 | bool dead = false; |
283 | |
|
284 | 0 | if (semop_err == EAGAIN) return 1; |
285 | | |
286 | 0 | if ((fr_sem_pid(&sem_pid, sem_id) < 0) || |
287 | 0 | (fr_sem_uid(&uid, sem_id) < 0) || |
288 | 0 | (fr_sem_gid(&gid, sem_id) < 0)) { |
289 | 0 | simple_error: |
290 | 0 | fr_strerror_printf("Failed waiting on semaphore bound to \"%s\" - %s", file, |
291 | 0 | fr_syserror(semop_err)); |
292 | 0 | goto done; |
293 | 0 | } |
294 | | |
295 | 0 | ret = kill(sem_pid, 0); |
296 | 0 | if ((ret < 0) && (errno == ESRCH)) dead = true; |
297 | |
|
298 | 0 | uid_str = fr_perm_uid_to_str(NULL, uid); |
299 | 0 | if (unlikely(!uid_str)) goto simple_error; |
300 | | |
301 | 0 | gid_str = fr_perm_gid_to_str(NULL, gid); |
302 | 0 | if (unlikely(!uid_str)) { |
303 | 0 | talloc_free(uid_str); |
304 | 0 | goto simple_error; |
305 | 0 | } |
306 | | |
307 | 0 | fr_strerror_printf("Failed waiting on semaphore bound to \"%s\" - %s. Semaphore " |
308 | 0 | "owned by %s:%s PID %u%s", file, fr_syserror(semop_err), |
309 | 0 | uid_str, gid_str, (unsigned int) sem_pid, dead ? " (dead)" : ""); |
310 | |
|
311 | 0 | talloc_free(uid_str); |
312 | 0 | talloc_free(gid_str); |
313 | |
|
314 | 0 | done: |
315 | 0 | return (semop_err == EACCES ? -1 : -2); |
316 | 0 | } |
317 | | |
318 | 0 | return 0; |
319 | 0 | } |
320 | | |
321 | | /** Remove the semaphore, this helps with permissions issues |
322 | | * |
323 | | * @param[in] sem_id to close. |
324 | | * @param[in] file to use in error messages. |
325 | | * @return |
326 | | * - 0 on success. |
327 | | * - -1 on failure. |
328 | | */ |
329 | | int fr_sem_close(int sem_id, char const *file) |
330 | 0 | { |
331 | 0 | if (semctl(sem_id, 0, IPC_RMID) < 0) { |
332 | 0 | fr_strerror_printf("Removing semaphore on \"%s\" failed: %s", |
333 | 0 | file ? file : "unknown", fr_syserror(errno)); |
334 | 0 | return -1; |
335 | 0 | } |
336 | | |
337 | 0 | return 0; |
338 | 0 | } |
339 | | |
340 | | static bool sem_check_uid(char const *file, int proj_id, |
341 | | char const *thing, uid_t expected, uid_t got) |
342 | 0 | { |
343 | 0 | char *expected_str, *got_str; |
344 | |
|
345 | 0 | if (expected == got) return true; |
346 | | |
347 | 0 | expected_str = fr_perm_uid_to_str(NULL, expected); |
348 | 0 | if (unlikely(!expected_str)) { |
349 | 0 | simple_error: |
350 | 0 | fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect", |
351 | 0 | file, (unsigned int) proj_id, thing); |
352 | 0 | return false; |
353 | 0 | } |
354 | | |
355 | 0 | got_str = fr_perm_uid_to_str(NULL, got); |
356 | 0 | if (unlikely(!got_str)) { |
357 | 0 | talloc_free(expected_str); |
358 | |
|
359 | 0 | goto simple_error; |
360 | 0 | } |
361 | 0 | fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect. Expected \"%s\", got \"%s\"", |
362 | 0 | file, (unsigned int) proj_id, thing, expected_str, got_str); |
363 | |
|
364 | 0 | talloc_free(expected_str); |
365 | 0 | talloc_free(got_str); |
366 | |
|
367 | 0 | return false; |
368 | 0 | } |
369 | | |
370 | | static bool sem_check_gid(char const *file, int proj_id, |
371 | | char const *thing, gid_t expected, gid_t got) |
372 | 0 | { |
373 | 0 | char *expected_str, *got_str; |
374 | |
|
375 | 0 | if (expected == got) return true; |
376 | | |
377 | 0 | expected_str = fr_perm_gid_to_str(NULL, expected); |
378 | 0 | if (unlikely(!expected_str)) { |
379 | 0 | simple_error: |
380 | 0 | fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect", |
381 | 0 | file, (unsigned int) proj_id, thing); |
382 | 0 | return false; |
383 | 0 | } |
384 | | |
385 | 0 | got_str = fr_perm_gid_to_str(NULL, got); |
386 | 0 | if (unlikely(!got_str)) { |
387 | 0 | talloc_free(expected_str); |
388 | |
|
389 | 0 | goto simple_error; |
390 | 0 | } |
391 | 0 | fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect. Expected \"%s\", got \"%s\"", |
392 | 0 | file, (unsigned int) proj_id, thing, expected_str, got_str); |
393 | |
|
394 | 0 | talloc_free(expected_str); |
395 | 0 | talloc_free(got_str); |
396 | |
|
397 | 0 | return false; |
398 | 0 | } |
399 | | |
400 | | /** Returns a semid for the semaphore associated with the file |
401 | | * |
402 | | * @param[in] file to get or create semaphore from. |
403 | | * @param[in] proj_id if 0 will default to '0xf4ee4a31'. |
404 | | * @param[in] uid that should own the semaphore. |
405 | | * @param[in] gid that should own the semaphore. |
406 | | * @param[in] check_perm Verify the semaphore is owned by |
407 | | * the specified uid/gid, and that it |
408 | | * was created by the specified uid/gid |
409 | | * or root. |
410 | | * Also verify that it is not world |
411 | | * writable. |
412 | | * @param[in] must_exist semaphore must already exist. |
413 | | * @return |
414 | | * - >= 0 the semaphore id. |
415 | | * - -1 the file specified does not exist, or there is |
416 | | * a permissions error. |
417 | | * - -2 failed getting semaphore. |
418 | | * - -3 failed creating semaphore. |
419 | | * - -4 must_exist was true, and the semaphore does not exist. |
420 | | */ |
421 | | int fr_sem_get(char const *file, int proj_id, uid_t uid, gid_t gid, bool check_perm, bool must_exist) |
422 | 0 | { |
423 | 0 | key_t sem_key; |
424 | 0 | int sem_id; |
425 | 0 | bool seen_eexist = false; |
426 | |
|
427 | 0 | if (proj_id == 0) proj_id = DEFAULT_PROJ_ID; |
428 | |
|
429 | 0 | sem_key = ftok(file, proj_id); |
430 | 0 | if (sem_key < 0) { |
431 | 0 | fr_strerror_printf("Failed associating semaphore with \"%s\" ID 0x%x: %s", |
432 | 0 | file, (unsigned int) proj_id, fr_syserror(errno)); |
433 | 0 | return -1; |
434 | 0 | } |
435 | | |
436 | | /* |
437 | | * Try and grab the existing semaphore |
438 | | */ |
439 | 0 | again: |
440 | 0 | sem_id = semget(sem_key, 0, 0); |
441 | 0 | if (sem_id < 0) { |
442 | 0 | unsigned short mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; |
443 | |
|
444 | 0 | if (errno != ENOENT) { /* Semaphore existed but we ran into an error */ |
445 | 0 | fr_strerror_printf("Failed getting semaphore on \"%s\" ID 0x%x: %s", |
446 | 0 | file, (unsigned int) proj_id, fr_syserror(errno)); |
447 | 0 | return -2; |
448 | 0 | } |
449 | | |
450 | 0 | if (must_exist) return -4; |
451 | | |
452 | | /* |
453 | | * Create one semaphore, only if it doesn't |
454 | | * already exist, with u+rw,g+rw,o+r |
455 | | */ |
456 | 0 | sem_id = semget(sem_key, 1, IPC_CREAT | IPC_EXCL | mode); |
457 | 0 | if (sem_id < 0) { |
458 | 0 | if (errno == EEXIST) { /* Can get this with racing processes */ |
459 | 0 | if (!seen_eexist) { |
460 | 0 | seen_eexist = true; |
461 | 0 | goto again; |
462 | 0 | } |
463 | 0 | } |
464 | | |
465 | 0 | fr_strerror_printf("Failed creating semaphore on \"%s\" ID 0x%x: %s", |
466 | 0 | file, (unsigned int) proj_id, fr_syserror(errno)); |
467 | 0 | return -3; |
468 | 0 | } |
469 | | |
470 | | /* |
471 | | * Set a specific uid/gid on the semaphore |
472 | | */ |
473 | 0 | { |
474 | 0 | struct semid_ds info = { |
475 | 0 | .sem_perm = { |
476 | 0 | .uid = uid, |
477 | 0 | .gid = gid, |
478 | 0 | .mode = mode |
479 | 0 | } |
480 | 0 | }; |
481 | |
|
482 | 0 | if (semctl(sem_id, 0, IPC_SET, &info) < 0) { |
483 | 0 | fr_strerror_printf("Failed setting permissions for semaphore on \"%s\" ID 0x%x: %s", |
484 | 0 | file, (unsigned int) proj_id, fr_syserror(errno)); |
485 | 0 | fr_sem_close(sem_id, file); |
486 | 0 | return -3; |
487 | 0 | } |
488 | 0 | } |
489 | | |
490 | | /* |
491 | | * Ensure that we, or a process with the same UID/GID |
492 | | * as ourselves, own the semaphore. |
493 | | */ |
494 | 0 | } else if (check_perm) { |
495 | 0 | int ret; |
496 | 0 | struct semid_ds info; |
497 | |
|
498 | 0 | ret = semctl(sem_id, 0, IPC_STAT, &info); |
499 | 0 | if (ret < 0) { |
500 | 0 | fr_strerror_printf("Failed getting semaphore permissions on \"%s\" ID 0x%x: %s", |
501 | 0 | file, (unsigned int) proj_id, fr_syserror(errno)); |
502 | 0 | return -2; |
503 | 0 | } |
504 | | |
505 | 0 | if (info.sem_perm.mode & S_IWOTH) { |
506 | 0 | fr_strerror_printf("Semaphore on \"%s\" ID 0x%x is world writable (insecure)", |
507 | 0 | file, (unsigned int) proj_id); |
508 | 0 | return -2; |
509 | 0 | } |
510 | | |
511 | | /* |
512 | | * IPC_SET allows the cuid/cgid of the semaphore |
513 | | * to modify its permissions, so we need to check |
514 | | * that they're either root or the user we want. |
515 | | */ |
516 | 0 | if (!sem_check_uid(file, proj_id, "UID", uid, info.sem_perm.uid) || |
517 | 0 | ((info.sem_perm.cuid != 0) && !sem_check_uid(file, proj_id, "CUID", uid, info.sem_perm.cuid)) || |
518 | 0 | !sem_check_gid(file, proj_id, "GID", gid, info.sem_perm.gid) || |
519 | 0 | ((info.sem_perm.cgid != 0) && !sem_check_gid(file, proj_id, "CGID", gid, info.sem_perm.cgid))) { |
520 | 0 | return -1; |
521 | 0 | } |
522 | 0 | } |
523 | | |
524 | 0 | return sem_id; |
525 | 0 | } |
526 | | #endif /* __EMSCRIPTEN__ */ |