Coverage Report

Created: 2026-02-26 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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__ */