]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/clean-ipc.c
shared/varlink: add missing terminator in json strings
[thirdparty/systemd.git] / src / shared / clean-ipc.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <dirent.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <limits.h>
7 #include <mqueue.h>
8 #include <stdbool.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <sys/ipc.h>
12 #include <sys/msg.h>
13 #include <sys/sem.h>
14 #include <sys/shm.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17
18 #include "clean-ipc.h"
19 #include "dirent-util.h"
20 #include "fd-util.h"
21 #include "fileio.h"
22 #include "format-util.h"
23 #include "log.h"
24 #include "macro.h"
25 #include "string-util.h"
26 #include "strv.h"
27 #include "user-util.h"
28
29 static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) {
30
31 if (uid_is_valid(delete_uid) && subject_uid == delete_uid)
32 return true;
33
34 if (gid_is_valid(delete_gid) && subject_gid == delete_gid)
35 return true;
36
37 return false;
38 }
39
40 static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) {
41 _cleanup_fclose_ FILE *f = NULL;
42 bool first = true;
43 int ret = 0, r;
44
45 f = fopen("/proc/sysvipc/shm", "re");
46 if (!f) {
47 if (errno == ENOENT)
48 return 0;
49
50 return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
51 }
52
53 for (;;) {
54 _cleanup_free_ char *line = NULL;
55 unsigned n_attached;
56 pid_t cpid, lpid;
57 uid_t uid, cuid;
58 gid_t gid, cgid;
59 int shmid;
60
61 r = read_line(f, LONG_LINE_MAX, &line);
62 if (r < 0)
63 return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
64 if (r == 0)
65 break;
66
67 if (first) {
68 first = false;
69 continue;
70 }
71
72 if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
73 &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8)
74 continue;
75
76 if (n_attached > 0)
77 continue;
78
79 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
80 continue;
81
82 if (!rm)
83 return 1;
84
85 if (shmctl(shmid, IPC_RMID, NULL) < 0) {
86
87 /* Ignore entries that are already deleted */
88 if (IN_SET(errno, EIDRM, EINVAL))
89 continue;
90
91 ret = log_warning_errno(errno,
92 "Failed to remove SysV shared memory segment %i: %m",
93 shmid);
94 } else {
95 log_debug("Removed SysV shared memory segment %i.", shmid);
96 if (ret == 0)
97 ret = 1;
98 }
99 }
100
101 return ret;
102 }
103
104 static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid, bool rm) {
105 _cleanup_fclose_ FILE *f = NULL;
106 bool first = true;
107 int ret = 0, r;
108
109 f = fopen("/proc/sysvipc/sem", "re");
110 if (!f) {
111 if (errno == ENOENT)
112 return 0;
113
114 return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
115 }
116
117 for (;;) {
118 _cleanup_free_ char *line = NULL;
119 uid_t uid, cuid;
120 gid_t gid, cgid;
121 int semid;
122
123 r = read_line(f, LONG_LINE_MAX, &line);
124 if (r < 0)
125 return log_warning_errno(r, "Failed to read /proc/sysvipc/sem: %m");
126 if (r == 0)
127 break;
128
129 if (first) {
130 first = false;
131 continue;
132 }
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
163 static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid, bool rm) {
164 _cleanup_fclose_ FILE *f = NULL;
165 bool first = true;
166 int ret = 0, r;
167
168 f = fopen("/proc/sysvipc/msg", "re");
169 if (!f) {
170 if (errno == ENOENT)
171 return 0;
172
173 return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
174 }
175
176 for (;;) {
177 _cleanup_free_ char *line = NULL;
178 uid_t uid, cuid;
179 gid_t gid, cgid;
180 pid_t cpid, lpid;
181 int msgid;
182
183 r = read_line(f, LONG_LINE_MAX, &line);
184 if (r < 0)
185 return log_warning_errno(r, "Failed to read /proc/sysvipc/msg: %m");
186 if (r == 0)
187 break;
188
189 if (first) {
190 first = false;
191 continue;
192 }
193
194 if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
195 &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
196 continue;
197
198 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
199 continue;
200
201 if (!rm)
202 return 1;
203
204 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
205
206 /* Ignore entries that are already deleted */
207 if (IN_SET(errno, EIDRM, EINVAL))
208 continue;
209
210 ret = log_warning_errno(errno,
211 "Failed to remove SysV message queue %i: %m",
212 msgid);
213 } else {
214 log_debug("Removed SysV message queue %i.", msgid);
215 if (ret == 0)
216 ret = 1;
217 }
218 }
219
220 return ret;
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 }