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