]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/clean-ipc.c
tree-wide: drop 'This file is part of systemd' blurb
[thirdparty/systemd.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 static int clean_posix_mq(uid_t uid, gid_t gid, bool rm) {
316 _cleanup_closedir_ DIR *dir = NULL;
317 struct dirent *de;
318 int ret = 0;
319
320 dir = opendir("/dev/mqueue");
321 if (!dir) {
322 if (errno == ENOENT)
323 return 0;
324
325 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
326 }
327
328 FOREACH_DIRENT_ALL(de, dir, goto fail) {
329 struct stat st;
330 char fn[1+strlen(de->d_name)+1];
331
332 if (dot_or_dot_dot(de->d_name))
333 continue;
334
335 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
336 if (errno == ENOENT)
337 continue;
338
339 ret = log_warning_errno(errno,
340 "Failed to stat() MQ segment %s: %m",
341 de->d_name);
342 continue;
343 }
344
345 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
346 continue;
347
348 if (!rm)
349 return 1;
350
351 fn[0] = '/';
352 strcpy(fn+1, de->d_name);
353
354 if (mq_unlink(fn) < 0) {
355 if (errno == ENOENT)
356 continue;
357
358 ret = log_warning_errno(errno,
359 "Failed to unlink POSIX message queue %s: %m",
360 fn);
361 } else {
362 log_debug("Removed POSIX message queue %s", fn);
363 if (ret == 0)
364 ret = 1;
365 }
366 }
367
368 return ret;
369
370 fail:
371 return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
372 }
373
374 int clean_ipc_internal(uid_t uid, gid_t gid, bool rm) {
375 int ret = 0, r;
376
377 /* If 'rm' is true, clean all IPC objects owned by either the specified UID or the specified GID. Return the
378 * last error encountered or == 0 if no matching IPC objects have been found or > 0 if matching IPC objects
379 * have been found and have been removed.
380 *
381 * If 'rm' is false, just search for IPC objects owned by either the specified UID or the specified GID. In
382 * this case we return < 0 on error, > 0 if we found a matching object, == 0 if we didn't.
383 *
384 * As special rule: if UID/GID is specified as root we'll silently not clean up things, and always claim that
385 * there are IPC objects for it. */
386
387 if (uid == 0) {
388 if (!rm)
389 return 1;
390
391 uid = UID_INVALID;
392 }
393 if (gid == 0) {
394 if (!rm)
395 return 1;
396
397 gid = GID_INVALID;
398 }
399
400 /* Anything to do? */
401 if (!uid_is_valid(uid) && !gid_is_valid(gid))
402 return 0;
403
404 r = clean_sysvipc_shm(uid, gid, rm);
405 if (r != 0) {
406 if (!rm)
407 return r;
408 if (ret == 0)
409 ret = r;
410 }
411
412 r = clean_sysvipc_sem(uid, gid, rm);
413 if (r != 0) {
414 if (!rm)
415 return r;
416 if (ret == 0)
417 ret = r;
418 }
419
420 r = clean_sysvipc_msg(uid, gid, rm);
421 if (r != 0) {
422 if (!rm)
423 return r;
424 if (ret == 0)
425 ret = r;
426 }
427
428 r = clean_posix_shm(uid, gid, rm);
429 if (r != 0) {
430 if (!rm)
431 return r;
432 if (ret == 0)
433 ret = r;
434 }
435
436 r = clean_posix_mq(uid, gid, rm);
437 if (r != 0) {
438 if (!rm)
439 return r;
440 if (ret == 0)
441 ret = r;
442 }
443
444 return ret;
445 }
446
447 int clean_ipc_by_uid(uid_t uid) {
448 return clean_ipc_internal(uid, GID_INVALID, true);
449 }
450
451 int clean_ipc_by_gid(gid_t gid) {
452 return clean_ipc_internal(UID_INVALID, gid, true);
453 }