chiark / gitweb /
844dc9db96a27bf63b29ccc3497f53ba3220f783
[elogind.git] / src / shared / clean-ipc.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   Copyright 2014 Lennart Poettering
4 ***/
5
6 #include <dirent.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <limits.h>
10 #include <mqueue.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <sys/ipc.h>
15 #include <sys/msg.h>
16 #include <sys/sem.h>
17 #include <sys/shm.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20
21 #include "clean-ipc.h"
22 #include "dirent-util.h"
23 #include "fd-util.h"
24 #include "fileio.h"
25 #include "format-util.h"
26 #include "log.h"
27 #include "macro.h"
28 #include "string-util.h"
29 #include "strv.h"
30 #include "user-util.h"
31
32 static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) {
33
34         if (uid_is_valid(delete_uid) && subject_uid == delete_uid)
35                 return true;
36
37         if (gid_is_valid(delete_gid) && subject_gid == delete_gid)
38                 return true;
39
40         return false;
41 }
42
43 static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) {
44         _cleanup_fclose_ FILE *f = NULL;
45         char line[LINE_MAX];
46         bool first = true;
47         int ret = 0;
48
49         f = fopen("/proc/sysvipc/shm", "re");
50         if (!f) {
51                 if (errno == ENOENT)
52                         return 0;
53
54                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
55         }
56
57         FOREACH_LINE(line, f, goto fail) {
58                 unsigned n_attached;
59                 pid_t cpid, lpid;
60                 uid_t uid, cuid;
61                 gid_t gid, cgid;
62                 int shmid;
63
64                 if (first) {
65                         first = false;
66                         continue;
67                 }
68
69                 truncate_nl(line);
70
71                 if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
72                            &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8)
73                         continue;
74
75                 if (n_attached > 0)
76                         continue;
77
78                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
79                         continue;
80
81                 if (!rm)
82                         return 1;
83
84                 if (shmctl(shmid, IPC_RMID, NULL) < 0) {
85
86                         /* Ignore entries that are already deleted */
87                         if (IN_SET(errno, EIDRM, EINVAL))
88                                 continue;
89
90                         ret = log_warning_errno(errno,
91                                                 "Failed to remove SysV shared memory segment %i: %m",
92                                                 shmid);
93                 } else {
94                         log_debug("Removed SysV shared memory segment %i.", shmid);
95                         if (ret == 0)
96                                 ret = 1;
97                 }
98         }
99
100         return ret;
101
102 fail:
103         return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
104 }
105
106 static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid, bool rm) {
107         _cleanup_fclose_ FILE *f = NULL;
108         char line[LINE_MAX];
109         bool first = true;
110         int ret = 0;
111
112         f = fopen("/proc/sysvipc/sem", "re");
113         if (!f) {
114                 if (errno == ENOENT)
115                         return 0;
116
117                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
118         }
119
120         FOREACH_LINE(line, f, goto fail) {
121                 uid_t uid, cuid;
122                 gid_t gid, cgid;
123                 int semid;
124
125                 if (first) {
126                         first = false;
127                         continue;
128                 }
129
130                 truncate_nl(line);
131
132                 if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
133                            &semid, &uid, &gid, &cuid, &cgid) != 5)
134                         continue;
135
136                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
137                         continue;
138
139                 if (!rm)
140                         return 1;
141
142                 if (semctl(semid, 0, IPC_RMID) < 0) {
143
144                         /* Ignore entries that are already deleted */
145                         if (IN_SET(errno, EIDRM, EINVAL))
146                                 continue;
147
148                         ret = log_warning_errno(errno,
149                                                 "Failed to remove SysV semaphores object %i: %m",
150                                                 semid);
151                 } else {
152                         log_debug("Removed SysV semaphore %i.", semid);
153                         if (ret == 0)
154                                 ret = 1;
155                 }
156         }
157
158         return ret;
159
160 fail:
161         return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m");
162 }
163
164 static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid, bool rm) {
165         _cleanup_fclose_ FILE *f = NULL;
166         char line[LINE_MAX];
167         bool first = true;
168         int ret = 0;
169
170         f = fopen("/proc/sysvipc/msg", "re");
171         if (!f) {
172                 if (errno == ENOENT)
173                         return 0;
174
175                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
176         }
177
178         FOREACH_LINE(line, f, goto fail) {
179                 uid_t uid, cuid;
180                 gid_t gid, cgid;
181                 pid_t cpid, lpid;
182                 int msgid;
183
184                 if (first) {
185                         first = false;
186                         continue;
187                 }
188
189                 truncate_nl(line);
190
191                 if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
192                            &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
193                         continue;
194
195                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
196                         continue;
197
198                 if (!rm)
199                         return 1;
200
201                 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
202
203                         /* Ignore entries that are already deleted */
204                         if (IN_SET(errno, EIDRM, EINVAL))
205                                 continue;
206
207                         ret = log_warning_errno(errno,
208                                                 "Failed to remove SysV message queue %i: %m",
209                                                 msgid);
210                 } else {
211                         log_debug("Removed SysV message queue %i.", msgid);
212                         if (ret == 0)
213                                 ret = 1;
214                 }
215         }
216
217         return ret;
218
219 fail:
220         return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m");
221 }
222
223 static int clean_posix_shm_internal(DIR *dir, uid_t uid, gid_t gid, bool rm) {
224         struct dirent *de;
225         int ret = 0, r;
226
227         assert(dir);
228
229         FOREACH_DIRENT_ALL(de, dir, goto fail) {
230                 struct stat st;
231
232                 if (dot_or_dot_dot(de->d_name))
233                         continue;
234
235                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
236                         if (errno == ENOENT)
237                                 continue;
238
239                         ret = log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name);
240                         continue;
241                 }
242
243                 if (S_ISDIR(st.st_mode)) {
244                         _cleanup_closedir_ DIR *kid;
245
246                         kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
247                         if (!kid) {
248                                 if (errno != ENOENT)
249                                         ret = log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name);
250                         } else {
251                                 r = clean_posix_shm_internal(kid, uid, gid, rm);
252                                 if (r < 0)
253                                         ret = r;
254                         }
255
256                         if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
257                                 continue;
258
259                         if (!rm)
260                                 return 1;
261
262                         if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
263
264                                 if (errno == ENOENT)
265                                         continue;
266
267                                 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name);
268                         } else {
269                                 log_debug("Removed POSIX shared memory directory %s", de->d_name);
270                                 if (ret == 0)
271                                         ret = 1;
272                         }
273                 } else {
274
275                         if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
276                                 continue;
277
278                         if (!rm)
279                                 return 1;
280
281                         if (unlinkat(dirfd(dir), de->d_name, 0) < 0) {
282
283                                 if (errno == ENOENT)
284                                         continue;
285
286                                 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name);
287                         } else {
288                                 log_debug("Removed POSIX shared memory segment %s", de->d_name);
289                                 if (ret == 0)
290                                         ret = 1;
291                         }
292                 }
293         }
294
295         return ret;
296
297 fail:
298         return log_warning_errno(errno, "Failed to read /dev/shm: %m");
299 }
300
301 static int clean_posix_shm(uid_t uid, gid_t gid, bool rm) {
302         _cleanup_closedir_ DIR *dir = NULL;
303
304         dir = opendir("/dev/shm");
305         if (!dir) {
306                 if (errno == ENOENT)
307                         return 0;
308
309                 return log_warning_errno(errno, "Failed to open /dev/shm: %m");
310         }
311
312         return clean_posix_shm_internal(dir, uid, gid, rm);
313 }
314
315 #if 0 /// UNNEEDED by elogind
316 static int clean_posix_mq(uid_t uid, gid_t gid, bool rm) {
317         _cleanup_closedir_ DIR *dir = NULL;
318         struct dirent *de;
319         int ret = 0;
320
321         dir = opendir("/dev/mqueue");
322         if (!dir) {
323                 if (errno == ENOENT)
324                         return 0;
325
326                 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
327         }
328
329         FOREACH_DIRENT_ALL(de, dir, goto fail) {
330                 struct stat st;
331                 char fn[1+strlen(de->d_name)+1];
332
333                 if (dot_or_dot_dot(de->d_name))
334                         continue;
335
336                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
337                         if (errno == ENOENT)
338                                 continue;
339
340                         ret = log_warning_errno(errno,
341                                                 "Failed to stat() MQ segment %s: %m",
342                                                 de->d_name);
343                         continue;
344                 }
345
346                 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
347                         continue;
348
349                 if (!rm)
350                         return 1;
351
352                 fn[0] = '/';
353                 strcpy(fn+1, de->d_name);
354
355                 if (mq_unlink(fn) < 0) {
356                         if (errno == ENOENT)
357                                 continue;
358
359                         ret = log_warning_errno(errno,
360                                                 "Failed to unlink POSIX message queue %s: %m",
361                                                 fn);
362                 } else {
363                         log_debug("Removed POSIX message queue %s", fn);
364                         if (ret == 0)
365                                 ret = 1;
366                 }
367         }
368
369         return ret;
370
371 fail:
372         return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
373 }
374 #endif // 0
375
376 int clean_ipc_internal(uid_t uid, gid_t gid, bool rm) {
377         int ret = 0, r;
378
379         /* If 'rm' is true, clean all IPC objects owned by either the specified UID or the specified GID. Return the
380          * last error encountered or == 0 if no matching IPC objects have been found or > 0 if matching IPC objects
381          * have been found and have been removed.
382          *
383          * If 'rm' is false, just search for IPC objects owned by either the specified UID or the specified GID. In
384          * this case we return < 0 on error, > 0 if we found a matching object, == 0 if we didn't.
385          *
386          * As special rule: if UID/GID is specified as root we'll silently not clean up things, and always claim that
387          * there are IPC objects for it. */
388
389         if (uid == 0) {
390                 if (!rm)
391                         return 1;
392
393                 uid = UID_INVALID;
394         }
395         if (gid == 0) {
396                 if (!rm)
397                         return 1;
398
399                 gid = GID_INVALID;
400         }
401
402         /* Anything to do? */
403         if (!uid_is_valid(uid) && !gid_is_valid(gid))
404                 return 0;
405
406         r = clean_sysvipc_shm(uid, gid, rm);
407         if (r != 0) {
408                 if (!rm)
409                         return r;
410                 if (ret == 0)
411                         ret = r;
412         }
413
414 #if 0 /// elogind does not use mq_open anywhere
415 #endif // 0
416         r = clean_sysvipc_sem(uid, gid, rm);
417         if (r != 0) {
418                 if (!rm)
419                         return r;
420                 if (ret == 0)
421                         ret = r;
422         }
423
424         r = clean_sysvipc_msg(uid, gid, rm);
425         if (r != 0) {
426                 if (!rm)
427                         return r;
428                 if (ret == 0)
429                         ret = r;
430         }
431
432         r = clean_posix_shm(uid, gid, rm);
433         if (r != 0) {
434                 if (!rm)
435                         return r;
436                 if (ret == 0)
437                         ret = r;
438         }
439
440 #if 0 /// Nothing in elogind uses mqueues
441         r = clean_posix_mq(uid, gid, rm);
442         if (r != 0) {
443                 if (!rm)
444                         return r;
445                 if (ret == 0)
446                         ret = r;
447         }
448 #endif // 0
449
450         return ret;
451 }
452
453 int clean_ipc_by_uid(uid_t uid) {
454         return clean_ipc_internal(uid, GID_INVALID, true);
455 }
456
457 #if 0 /// UNNEEDED by elogind
458 int clean_ipc_by_gid(gid_t gid) {
459         return clean_ipc_internal(UID_INVALID, gid, true);
460 }
461 #endif // 0