The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
sem.c
Go to the documentation of this file.
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 */
23RCSID("$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#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 */
53int fr_sem_pid(pid_t *pid, int sem_id)
54{
55 int ret;
56
57 ret = semctl(sem_id, 0, GETPID);
58 if (ret < 0) {
59 fr_strerror_printf("Failed getting semaphore PID: %s", fr_syserror(errno));
60 return -1;
61 }
62
63 *pid = (pid_t)ret;
64
65 return 0;
66}
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 */
76int fr_sem_uid(uid_t *uid, int sem_id)
77{
78 int ret;
79 struct semid_ds info;
80
81 ret = semctl(sem_id, 0, IPC_STAT, &info);
82 if (ret < 0) {
83 *uid = 0;
84
85 fr_strerror_printf("Failed getting semaphore UID: %s", fr_syserror(errno));
86 return -1;
87 }
88
89 *uid = info.sem_perm.uid;
90
91 return 0;
92}
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 */
102int fr_sem_gid(uid_t *gid, int sem_id)
103{
104 int ret;
105 struct semid_ds info;
106
107 ret = semctl(sem_id, 0, IPC_STAT, &info);
108 if (ret < 0) {
109 *gid = 0;
110
111 fr_strerror_printf("Failed getting semaphore GID: %s", fr_syserror(errno));
112 return -1;
113 }
114
115 *gid = info.sem_perm.gid;
116
117 return 0;
118}
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 */
128int fr_sem_cuid(uid_t *uid, int sem_id)
129{
130 int ret;
131 struct semid_ds info;
132
133 ret = semctl(sem_id, 0, IPC_STAT, &info);
134 if (ret < 0) {
135 *uid = 0;
136
137 fr_strerror_printf("Failed getting semaphore CUID: %s", fr_syserror(errno));
138 return -1;
139 }
140
141 *uid = info.sem_perm.cuid;
142
143 return 0;
144}
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 */
154int fr_sem_cgid(uid_t *gid, int sem_id)
155{
156 int ret;
157 struct semid_ds info;
158
159 ret = semctl(sem_id, 0, IPC_STAT, &info);
160 if (ret < 0) {
161 *gid = 0;
162
163 fr_strerror_printf("Failed getting semaphore CGID: %s", fr_syserror(errno));
164 return -1;
165 }
166
167 *gid = info.sem_perm.cgid;
168
169 return 0;
170}
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 */
182int fr_sem_post(int sem_id, char const *file, bool undo_on_exit)
183{
184 unsigned int num;
185
186 errno = 0;
187 num = semctl(sem_id, 0, GETVAL);
188 if (errno != 0) {
189 fr_strerror_printf("Failed getting value from semaphore bound to \"%s\" - %s", file,
190 fr_syserror(errno));
191 return 01;
192 }
193 if (num == 0) return 1;
194
195 {
196 struct sembuf sop = {
197 .sem_num = 0,
198 .sem_op = -1,
199 .sem_flg = undo_on_exit * SEM_UNDO
200 };
201
202 if (semop(sem_id, &sop, 1) < 0) {
203 fr_strerror_printf("Failed posting semaphore bound to \"%s\" - %s", file,
204 fr_syserror(errno));
205 return -1;
206 }
207 }
208
209 return 0;
210}
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 */
221int fr_sem_take(int sem_id, char const *file, bool undo_on_exit)
222{
223 struct sembuf sop = {
224 .sem_num = 0,
225 .sem_op = 1,
226 .sem_flg = undo_on_exit * SEM_UNDO
227 };
228
229 if (semop(sem_id, &sop, 1) < 0) {
230 fr_strerror_printf("Failed waiting on semaphore bound to \"%s\" - %s", file,
231 fr_syserror(errno));
232 return -1;
233 }
234
235 return 0;
236}
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 */
252int fr_sem_wait(int sem_id, char const *file, bool undo_on_exit, bool nonblock)
253{
254 struct sembuf sops[2];
255 short flags_nonblock;
256 short flags_undo;
257
258 flags_nonblock = nonblock * IPC_NOWAIT;
259 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 sops[0].sem_num = 0;
267 sops[0].sem_op = 0;
268 sops[0].sem_flg = flags_nonblock;
269
270 sops[1].sem_num = 0;
271 sops[1].sem_op = 1;
272 sops[1].sem_flg = flags_nonblock | flags_undo;
273
274 if (semop(sem_id, sops, 2) < 0) {
275 pid_t sem_pid;
276 uid_t uid;
277 gid_t gid;
278 int semop_err = errno;
279 char *uid_str;
280 char *gid_str;
281 int ret;
282 bool dead = false;
283
284 if (semop_err == EAGAIN) return 1;
285
286 if ((fr_sem_pid(&sem_pid, sem_id) < 0) ||
287 (fr_sem_uid(&uid, sem_id) < 0) ||
288 (fr_sem_gid(&gid, sem_id) < 0)) {
289 simple_error:
290 fr_strerror_printf("Failed waiting on semaphore bound to \"%s\" - %s", file,
291 fr_syserror(semop_err));
292 goto done;
293 }
294
295 ret = kill(sem_pid, 0);
296 if ((ret < 0) && (errno == ESRCH)) dead = true;
297
298 uid_str = fr_perm_uid_to_str(NULL, uid);
299 if (unlikely(!uid_str)) goto simple_error;
300
301 gid_str = fr_perm_gid_to_str(NULL, gid);
302 if (unlikely(!uid_str)) {
303 talloc_free(uid_str);
304 goto simple_error;
305 }
306
307 fr_strerror_printf("Failed waiting on semaphore bound to \"%s\" - %s. Semaphore "
308 "owned by %s:%s PID %u%s", file, fr_syserror(semop_err),
309 uid_str, gid_str, (unsigned int) sem_pid, dead ? " (dead)" : "");
310
311 talloc_free(uid_str);
312 talloc_free(gid_str);
313
314 done:
315 return (semop_err == EACCES ? -1 : -2);
316 }
317
318 return 0;
319}
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 */
329int fr_sem_close(int sem_id, char const *file)
330{
331 if (semctl(sem_id, 0, IPC_RMID) < 0) {
332 fr_strerror_printf("Removing semaphore on \"%s\" failed: %s",
333 file ? file : "unknown", fr_syserror(errno));
334 return -1;
335 }
336
337 return 0;
338}
339
340static bool sem_check_uid(char const *file, int proj_id,
341 char const *thing, uid_t expected, uid_t got)
342{
343 char *expected_str, *got_str;
344
345 if (expected == got) return true;
346
347 expected_str = fr_perm_uid_to_str(NULL, expected);
348 if (unlikely(!expected_str)) {
349 simple_error:
350 fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect",
351 file, (unsigned int) proj_id, thing);
352 return false;
353 }
354
355 got_str = fr_perm_uid_to_str(NULL, got);
356 if (unlikely(!got_str)) {
357 talloc_free(expected_str);
358
359 goto simple_error;
360 }
361 fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect. Expected \"%s\", got \"%s\"",
362 file, (unsigned int) proj_id, thing, expected_str, got_str);
363
364 talloc_free(expected_str);
365 talloc_free(got_str);
366
367 return false;
368}
369
370static bool sem_check_gid(char const *file, int proj_id,
371 char const *thing, gid_t expected, gid_t got)
372{
373 char *expected_str, *got_str;
374
375 if (expected == got) return true;
376
377 expected_str = fr_perm_gid_to_str(NULL, expected);
378 if (unlikely(!expected_str)) {
379 simple_error:
380 fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect",
381 file, (unsigned int) proj_id, thing);
382 return false;
383 }
384
385 got_str = fr_perm_gid_to_str(NULL, got);
386 if (unlikely(!got_str)) {
387 talloc_free(expected_str);
388
389 goto simple_error;
390 }
391 fr_strerror_printf("Semaphore on \"%s\" ID 0x%x - %s is incorrect. Expected \"%s\", got \"%s\"",
392 file, (unsigned int) proj_id, thing, expected_str, got_str);
393
394 talloc_free(expected_str);
395 talloc_free(got_str);
396
397 return false;
398}
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 */
421int fr_sem_get(char const *file, int proj_id, uid_t uid, gid_t gid, bool check_perm, bool must_exist)
422{
423 key_t sem_key;
424 int sem_id;
425 bool seen_eexist = false;
426
427 if (proj_id == 0) proj_id = DEFAULT_PROJ_ID;
428
429 sem_key = ftok(file, proj_id);
430 if (sem_key < 0) {
431 fr_strerror_printf("Failed associating semaphore with \"%s\" ID 0x%x: %s",
432 file, (unsigned int) proj_id, fr_syserror(errno));
433 return -1;
434 }
435
436 /*
437 * Try and grab the existing semaphore
438 */
439again:
440 sem_id = semget(sem_key, 0, 0);
441 if (sem_id < 0) {
442 unsigned short mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
443
444 if (errno != ENOENT) { /* Semaphore existed but we ran into an error */
445 fr_strerror_printf("Failed getting semaphore on \"%s\" ID 0x%x: %s",
446 file, (unsigned int) proj_id, fr_syserror(errno));
447 return -2;
448 }
449
450 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 sem_id = semget(sem_key, 1, IPC_CREAT | IPC_EXCL | mode);
457 if (sem_id < 0) {
458 if (errno == EEXIST) { /* Can get this with racing processes */
459 if (!seen_eexist) {
460 seen_eexist = true;
461 goto again;
462 }
463 }
464
465 fr_strerror_printf("Failed creating semaphore on \"%s\" ID 0x%x: %s",
466 file, (unsigned int) proj_id, fr_syserror(errno));
467 return -3;
468 }
469
470 /*
471 * Set a specific uid/gid on the semaphore
472 */
473 {
474 struct semid_ds info = {
475 .sem_perm = {
476 .uid = uid,
477 .gid = gid,
478 .mode = mode
479 }
480 };
481
482 if (semctl(sem_id, 0, IPC_SET, &info) < 0) {
483 fr_strerror_printf("Failed setting permissions for semaphore on \"%s\" ID 0x%x: %s",
484 file, (unsigned int) proj_id, fr_syserror(errno));
485 fr_sem_close(sem_id, file);
486 return -3;
487 }
488 }
489
490 /*
491 * Ensure that we, or a process with the same UID/GID
492 * as ourselves, own the semaphore.
493 */
494 } else if (check_perm) {
495 int ret;
496 struct semid_ds info;
497
498 ret = semctl(sem_id, 0, IPC_STAT, &info);
499 if (ret < 0) {
500 fr_strerror_printf("Failed getting semaphore permissions on \"%s\" ID 0x%x: %s",
501 file, (unsigned int) proj_id, fr_syserror(errno));
502 return -2;
503 }
504
505 if (info.sem_perm.mode & S_IWOTH) {
506 fr_strerror_printf("Semaphore on \"%s\" ID 0x%x is world writable (insecure)",
507 file, (unsigned int) proj_id);
508 return -2;
509 }
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 if (!sem_check_uid(file, proj_id, "UID", uid, info.sem_perm.uid) ||
517 ((info.sem_perm.cuid != 0) && !sem_check_uid(file, proj_id, "CUID", uid, info.sem_perm.cuid)) ||
518 !sem_check_gid(file, proj_id, "GID", gid, info.sem_perm.gid) ||
519 ((info.sem_perm.cgid != 0) && !sem_check_gid(file, proj_id, "CGID", gid, info.sem_perm.cgid))) {
520 return -1;
521 }
522 }
523
524 return sem_id;
525}
526#endif /* __EMSCRIPTEN__ */
int const char * file
Definition acutest.h:702
#define RCSID(id)
Definition build.h:483
#define unlikely(_x)
Definition build.h:381
talloc_free(reap)
char * fr_perm_uid_to_str(TALLOC_CTX *ctx, uid_t uid)
Print uid to a string.
Definition perm.c:494
char * fr_perm_gid_to_str(TALLOC_CTX *ctx, uid_t gid)
Print gid to a string.
Definition perm.c:514
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:102
int fr_sem_cgid(uid_t *gid, int sem_id)
Return the GID that created the semaphore.
Definition sem.c:154
int fr_sem_post(int sem_id, char const *file, bool undo_on_exit)
Decrement the semaphore by 1.
Definition sem.c:182
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:252
int fr_sem_close(int sem_id, char const *file)
Remove the semaphore, this helps with permissions issues.
Definition sem.c:329
#define DEFAULT_PROJ_ID
Definition sem.c:43
static bool sem_check_uid(char const *file, int proj_id, char const *thing, uid_t expected, uid_t got)
Definition sem.c:340
int fr_sem_cuid(uid_t *uid, int sem_id)
Return the UID that created the semaphore.
Definition sem.c:128
int fr_sem_uid(uid_t *uid, int sem_id)
Return the UID that last operated on the semaphore.
Definition sem.c:76
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:53
static bool sem_check_gid(char const *file, int proj_id, char const *thing, gid_t expected, gid_t got)
Definition sem.c:370
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:421
int fr_sem_take(int sem_id, char const *file, bool undo_on_exit)
Increment the semaphore by 1.
Definition sem.c:221
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