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