]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/clean-ipc.c
tree-wide: drop license boilerplate
[thirdparty/systemd.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 static int clean_posix_mq(uid_t uid, gid_t gid, bool rm) {
318 _cleanup_closedir_ DIR *dir = NULL;
319 struct dirent *de;
320 int ret = 0;
321
322 dir = opendir("/dev/mqueue");
323 if (!dir) {
324 if (errno == ENOENT)
325 return 0;
326
327 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
328 }
329
330 FOREACH_DIRENT_ALL(de, dir, goto fail) {
331 struct stat st;
332 char fn[1+strlen(de->d_name)+1];
333
334 if (dot_or_dot_dot(de->d_name))
335 continue;
336
337 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
338 if (errno == ENOENT)
339 continue;
340
341 ret = log_warning_errno(errno,
342 "Failed to stat() MQ segment %s: %m",
343 de->d_name);
344 continue;
345 }
346
347 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
348 continue;
349
350 if (!rm)
351 return 1;
352
353 fn[0] = '/';
354 strcpy(fn+1, de->d_name);
355
356 if (mq_unlink(fn) < 0) {
357 if (errno == ENOENT)
358 continue;
359
360 ret = log_warning_errno(errno,
361 "Failed to unlink POSIX message queue %s: %m",
362 fn);
363 } else {
364 log_debug("Removed POSIX message queue %s", fn);
365 if (ret == 0)
366 ret = 1;
367 }
368 }
369
370 return ret;
371
372 fail:
373 return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
374 }
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 r = clean_sysvipc_sem(uid, gid, rm);
415 if (r != 0) {
416 if (!rm)
417 return r;
418 if (ret == 0)
419 ret = r;
420 }
421
422 r = clean_sysvipc_msg(uid, gid, rm);
423 if (r != 0) {
424 if (!rm)
425 return r;
426 if (ret == 0)
427 ret = r;
428 }
429
430 r = clean_posix_shm(uid, gid, rm);
431 if (r != 0) {
432 if (!rm)
433 return r;
434 if (ret == 0)
435 ret = r;
436 }
437
438 r = clean_posix_mq(uid, gid, rm);
439 if (r != 0) {
440 if (!rm)
441 return r;
442 if (ret == 0)
443 ret = r;
444 }
445
446 return ret;
447 }
448
449 int clean_ipc_by_uid(uid_t uid) {
450 return clean_ipc_internal(uid, GID_INVALID, true);
451 }
452
453 int clean_ipc_by_gid(gid_t gid) {
454 return clean_ipc_internal(UID_INVALID, gid, true);
455 }