The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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  */
23 RCSID("$Id: 218b339cfbcec097eb96ba8df4b9f5756f794283 $")
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  */
53 int 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  */
76 int 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  */
102 int 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  */
128 int 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  */
154 int 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  */
182 int 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  */
221 int 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  */
252 int 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, 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  */
329 int 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 
340 static 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, 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, proj_id, thing, expected_str, got_str);
363 
364  talloc_free(expected_str);
365  talloc_free(got_str);
366 
367  return false;
368 }
369 
370 static 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, 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, 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  */
421 int 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, proj_id, fr_syserror(errno));
433  return -1;
434  }
435 
436  /*
437  * Try and grab the existing semaphore
438  */
439 again:
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, 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, 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, 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, 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, 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:444
#define unlikely(_x)
Definition: build.h:378
talloc_free(reap)
char * fr_perm_gid_to_str(TALLOC_CTX *ctx, uid_t gid)
Print gid to a string.
Definition: perm.c:386
char * fr_perm_uid_to_str(TALLOC_CTX *ctx, uid_t uid)
Print uid to a string.
Definition: perm.c:366
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