Branch data Line data Source code
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 : 0 : static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) {
30 : :
31 [ # # # # ]: 0 : if (uid_is_valid(delete_uid) && subject_uid == delete_uid)
32 : 0 : return true;
33 : :
34 [ # # # # ]: 0 : if (gid_is_valid(delete_gid) && subject_gid == delete_gid)
35 : 0 : return true;
36 : :
37 : 0 : return false;
38 : : }
39 : :
40 : 0 : static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) {
41 : 0 : _cleanup_fclose_ FILE *f = NULL;
42 : 0 : bool first = true;
43 : 0 : int ret = 0, r;
44 : :
45 : 0 : f = fopen("/proc/sysvipc/shm", "re");
46 [ # # ]: 0 : if (!f) {
47 [ # # ]: 0 : if (errno == ENOENT)
48 : 0 : return 0;
49 : :
50 [ # # ]: 0 : return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
51 : : }
52 : :
53 : 0 : for (;;) {
54 [ # # # # ]: 0 : _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 : 0 : r = read_line(f, LONG_LINE_MAX, &line);
62 [ # # ]: 0 : if (r < 0)
63 [ # # ]: 0 : return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
64 [ # # ]: 0 : if (r == 0)
65 : 0 : break;
66 : :
67 [ # # ]: 0 : if (first) {
68 : 0 : first = false;
69 : 0 : continue;
70 : : }
71 : :
72 [ # # ]: 0 : 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 : 0 : continue;
75 : :
76 [ # # ]: 0 : if (n_attached > 0)
77 : 0 : continue;
78 : :
79 [ # # ]: 0 : if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
80 : 0 : continue;
81 : :
82 [ # # ]: 0 : if (!rm)
83 : 0 : return 1;
84 : :
85 [ # # ]: 0 : if (shmctl(shmid, IPC_RMID, NULL) < 0) {
86 : :
87 : : /* Ignore entries that are already deleted */
88 [ # # # # ]: 0 : if (IN_SET(errno, EIDRM, EINVAL))
89 : 0 : continue;
90 : :
91 [ # # ]: 0 : ret = log_warning_errno(errno,
92 : : "Failed to remove SysV shared memory segment %i: %m",
93 : : shmid);
94 : : } else {
95 [ # # ]: 0 : log_debug("Removed SysV shared memory segment %i.", shmid);
96 [ # # ]: 0 : if (ret == 0)
97 : 0 : ret = 1;
98 : : }
99 : : }
100 : :
101 : 0 : return ret;
102 : : }
103 : :
104 : 0 : static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid, bool rm) {
105 : 0 : _cleanup_fclose_ FILE *f = NULL;
106 : 0 : bool first = true;
107 : 0 : int ret = 0, r;
108 : :
109 : 0 : f = fopen("/proc/sysvipc/sem", "re");
110 [ # # ]: 0 : if (!f) {
111 [ # # ]: 0 : if (errno == ENOENT)
112 : 0 : return 0;
113 : :
114 [ # # ]: 0 : return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
115 : : }
116 : :
117 : 0 : for (;;) {
118 [ # # # # ]: 0 : _cleanup_free_ char *line = NULL;
119 : : uid_t uid, cuid;
120 : : gid_t gid, cgid;
121 : : int semid;
122 : :
123 : 0 : r = read_line(f, LONG_LINE_MAX, &line);
124 [ # # ]: 0 : if (r < 0)
125 [ # # ]: 0 : return log_warning_errno(r, "Failed to read /proc/sysvipc/sem: %m");
126 [ # # ]: 0 : if (r == 0)
127 : 0 : break;
128 : :
129 [ # # ]: 0 : if (first) {
130 : 0 : first = false;
131 : 0 : continue;
132 : : }
133 : :
134 [ # # ]: 0 : if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
135 : : &semid, &uid, &gid, &cuid, &cgid) != 5)
136 : 0 : continue;
137 : :
138 [ # # ]: 0 : if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
139 : 0 : continue;
140 : :
141 [ # # ]: 0 : if (!rm)
142 : 0 : return 1;
143 : :
144 [ # # ]: 0 : if (semctl(semid, 0, IPC_RMID) < 0) {
145 : :
146 : : /* Ignore entries that are already deleted */
147 [ # # # # ]: 0 : if (IN_SET(errno, EIDRM, EINVAL))
148 : 0 : continue;
149 : :
150 [ # # ]: 0 : ret = log_warning_errno(errno,
151 : : "Failed to remove SysV semaphores object %i: %m",
152 : : semid);
153 : : } else {
154 [ # # ]: 0 : log_debug("Removed SysV semaphore %i.", semid);
155 [ # # ]: 0 : if (ret == 0)
156 : 0 : ret = 1;
157 : : }
158 : : }
159 : :
160 : 0 : return ret;
161 : : }
162 : :
163 : 0 : static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid, bool rm) {
164 : 0 : _cleanup_fclose_ FILE *f = NULL;
165 : 0 : bool first = true;
166 : 0 : int ret = 0, r;
167 : :
168 : 0 : f = fopen("/proc/sysvipc/msg", "re");
169 [ # # ]: 0 : if (!f) {
170 [ # # ]: 0 : if (errno == ENOENT)
171 : 0 : return 0;
172 : :
173 [ # # ]: 0 : return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
174 : : }
175 : :
176 : 0 : for (;;) {
177 [ # # # # ]: 0 : _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 : 0 : r = read_line(f, LONG_LINE_MAX, &line);
184 [ # # ]: 0 : if (r < 0)
185 [ # # ]: 0 : return log_warning_errno(r, "Failed to read /proc/sysvipc/msg: %m");
186 [ # # ]: 0 : if (r == 0)
187 : 0 : break;
188 : :
189 [ # # ]: 0 : if (first) {
190 : 0 : first = false;
191 : 0 : continue;
192 : : }
193 : :
194 [ # # ]: 0 : 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 : 0 : continue;
197 : :
198 [ # # ]: 0 : if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
199 : 0 : continue;
200 : :
201 [ # # ]: 0 : if (!rm)
202 : 0 : return 1;
203 : :
204 [ # # ]: 0 : if (msgctl(msgid, IPC_RMID, NULL) < 0) {
205 : :
206 : : /* Ignore entries that are already deleted */
207 [ # # # # ]: 0 : if (IN_SET(errno, EIDRM, EINVAL))
208 : 0 : continue;
209 : :
210 [ # # ]: 0 : ret = log_warning_errno(errno,
211 : : "Failed to remove SysV message queue %i: %m",
212 : : msgid);
213 : : } else {
214 [ # # ]: 0 : log_debug("Removed SysV message queue %i.", msgid);
215 [ # # ]: 0 : if (ret == 0)
216 : 0 : ret = 1;
217 : : }
218 : : }
219 : :
220 : 0 : return ret;
221 : : }
222 : :
223 : 0 : static int clean_posix_shm_internal(DIR *dir, uid_t uid, gid_t gid, bool rm) {
224 : : struct dirent *de;
225 : 0 : int ret = 0, r;
226 : :
227 [ # # ]: 0 : assert(dir);
228 : :
229 [ # # # # ]: 0 : FOREACH_DIRENT_ALL(de, dir, goto fail) {
230 : : struct stat st;
231 : :
232 [ # # ]: 0 : if (dot_or_dot_dot(de->d_name))
233 : 0 : continue;
234 : :
235 [ # # ]: 0 : if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
236 [ # # ]: 0 : if (errno == ENOENT)
237 : 0 : continue;
238 : :
239 [ # # ]: 0 : ret = log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name);
240 : 0 : continue;
241 : : }
242 : :
243 [ # # ]: 0 : if (S_ISDIR(st.st_mode)) {
244 [ # # # ]: 0 : _cleanup_closedir_ DIR *kid;
245 : :
246 : 0 : kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
247 [ # # ]: 0 : if (!kid) {
248 [ # # ]: 0 : if (errno != ENOENT)
249 [ # # ]: 0 : ret = log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name);
250 : : } else {
251 : 0 : r = clean_posix_shm_internal(kid, uid, gid, rm);
252 [ # # ]: 0 : if (r < 0)
253 : 0 : ret = r;
254 : : }
255 : :
256 [ # # ]: 0 : if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
257 : 0 : continue;
258 : :
259 [ # # ]: 0 : if (!rm)
260 : 0 : return 1;
261 : :
262 [ # # ]: 0 : if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
263 : :
264 [ # # ]: 0 : if (errno == ENOENT)
265 : 0 : continue;
266 : :
267 [ # # ]: 0 : ret = log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name);
268 : : } else {
269 [ # # ]: 0 : log_debug("Removed POSIX shared memory directory %s", de->d_name);
270 [ # # ]: 0 : if (ret == 0)
271 : 0 : ret = 1;
272 : : }
273 : : } else {
274 : :
275 [ # # ]: 0 : if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
276 : 0 : continue;
277 : :
278 [ # # ]: 0 : if (!rm)
279 : 0 : return 1;
280 : :
281 [ # # ]: 0 : if (unlinkat(dirfd(dir), de->d_name, 0) < 0) {
282 : :
283 [ # # ]: 0 : if (errno == ENOENT)
284 : 0 : continue;
285 : :
286 [ # # ]: 0 : ret = log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name);
287 : : } else {
288 [ # # ]: 0 : log_debug("Removed POSIX shared memory segment %s", de->d_name);
289 [ # # ]: 0 : if (ret == 0)
290 : 0 : ret = 1;
291 : : }
292 : : }
293 : : }
294 : :
295 : 0 : return ret;
296 : :
297 : 0 : fail:
298 [ # # ]: 0 : return log_warning_errno(errno, "Failed to read /dev/shm: %m");
299 : : }
300 : :
301 : 0 : static int clean_posix_shm(uid_t uid, gid_t gid, bool rm) {
302 : 0 : _cleanup_closedir_ DIR *dir = NULL;
303 : :
304 : 0 : dir = opendir("/dev/shm");
305 [ # # ]: 0 : if (!dir) {
306 [ # # ]: 0 : if (errno == ENOENT)
307 : 0 : return 0;
308 : :
309 [ # # ]: 0 : return log_warning_errno(errno, "Failed to open /dev/shm: %m");
310 : : }
311 : :
312 : 0 : return clean_posix_shm_internal(dir, uid, gid, rm);
313 : : }
314 : :
315 : 0 : static int clean_posix_mq(uid_t uid, gid_t gid, bool rm) {
316 : 0 : _cleanup_closedir_ DIR *dir = NULL;
317 : : struct dirent *de;
318 : 0 : int ret = 0;
319 : :
320 : 0 : dir = opendir("/dev/mqueue");
321 [ # # ]: 0 : if (!dir) {
322 [ # # ]: 0 : if (errno == ENOENT)
323 : 0 : return 0;
324 : :
325 [ # # ]: 0 : return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
326 : : }
327 : :
328 [ # # # # ]: 0 : FOREACH_DIRENT_ALL(de, dir, goto fail) {
329 : : struct stat st;
330 : 0 : char fn[1+strlen(de->d_name)+1];
331 : :
332 [ # # ]: 0 : if (dot_or_dot_dot(de->d_name))
333 : 0 : continue;
334 : :
335 [ # # ]: 0 : if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
336 [ # # ]: 0 : if (errno == ENOENT)
337 : 0 : continue;
338 : :
339 [ # # ]: 0 : ret = log_warning_errno(errno,
340 : : "Failed to stat() MQ segment %s: %m",
341 : : de->d_name);
342 : 0 : continue;
343 : : }
344 : :
345 [ # # ]: 0 : if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
346 : 0 : continue;
347 : :
348 [ # # ]: 0 : if (!rm)
349 : 0 : return 1;
350 : :
351 : 0 : fn[0] = '/';
352 : 0 : strcpy(fn+1, de->d_name);
353 : :
354 [ # # ]: 0 : if (mq_unlink(fn) < 0) {
355 [ # # ]: 0 : if (errno == ENOENT)
356 : 0 : continue;
357 : :
358 [ # # ]: 0 : ret = log_warning_errno(errno,
359 : : "Failed to unlink POSIX message queue %s: %m",
360 : : fn);
361 : : } else {
362 [ # # ]: 0 : log_debug("Removed POSIX message queue %s", fn);
363 [ # # ]: 0 : if (ret == 0)
364 : 0 : ret = 1;
365 : : }
366 : : }
367 : :
368 : 0 : return ret;
369 : :
370 : 0 : fail:
371 [ # # ]: 0 : return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
372 : : }
373 : :
374 : 0 : int clean_ipc_internal(uid_t uid, gid_t gid, bool rm) {
375 : 0 : 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 [ # # ]: 0 : if (uid == 0) {
388 [ # # ]: 0 : if (!rm)
389 : 0 : return 1;
390 : :
391 : 0 : uid = UID_INVALID;
392 : : }
393 [ # # ]: 0 : if (gid == 0) {
394 [ # # ]: 0 : if (!rm)
395 : 0 : return 1;
396 : :
397 : 0 : gid = GID_INVALID;
398 : : }
399 : :
400 : : /* Anything to do? */
401 [ # # # # ]: 0 : if (!uid_is_valid(uid) && !gid_is_valid(gid))
402 : 0 : return 0;
403 : :
404 : 0 : r = clean_sysvipc_shm(uid, gid, rm);
405 [ # # ]: 0 : if (r != 0) {
406 [ # # ]: 0 : if (!rm)
407 : 0 : return r;
408 [ # # ]: 0 : if (ret == 0)
409 : 0 : ret = r;
410 : : }
411 : :
412 : 0 : r = clean_sysvipc_sem(uid, gid, rm);
413 [ # # ]: 0 : if (r != 0) {
414 [ # # ]: 0 : if (!rm)
415 : 0 : return r;
416 [ # # ]: 0 : if (ret == 0)
417 : 0 : ret = r;
418 : : }
419 : :
420 : 0 : r = clean_sysvipc_msg(uid, gid, rm);
421 [ # # ]: 0 : if (r != 0) {
422 [ # # ]: 0 : if (!rm)
423 : 0 : return r;
424 [ # # ]: 0 : if (ret == 0)
425 : 0 : ret = r;
426 : : }
427 : :
428 : 0 : r = clean_posix_shm(uid, gid, rm);
429 [ # # ]: 0 : if (r != 0) {
430 [ # # ]: 0 : if (!rm)
431 : 0 : return r;
432 [ # # ]: 0 : if (ret == 0)
433 : 0 : ret = r;
434 : : }
435 : :
436 : 0 : r = clean_posix_mq(uid, gid, rm);
437 [ # # ]: 0 : if (r != 0) {
438 [ # # ]: 0 : if (!rm)
439 : 0 : return r;
440 [ # # ]: 0 : if (ret == 0)
441 : 0 : ret = r;
442 : : }
443 : :
444 : 0 : return ret;
445 : : }
446 : :
447 : 0 : int clean_ipc_by_uid(uid_t uid) {
448 : 0 : return clean_ipc_internal(uid, GID_INVALID, true);
449 : : }
450 : :
451 : 0 : int clean_ipc_by_gid(gid_t gid) {
452 : 0 : return clean_ipc_internal(UID_INVALID, gid, true);
453 : : }
|