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