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 : }
|