1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <mqueue.h>
7 #include <stdbool.h>
8 #include <stdio.h>
9 #include <sys/ipc.h>
10 #include <sys/msg.h>
11 #include <sys/sem.h>
12 #include <sys/shm.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15
16 #include "clean-ipc.h"
17 #include "dirent-util.h"
18 #include "fd-util.h"
19 #include "fileio.h"
20 #include "format-util.h"
21 #include "log.h"
22 #include "macro.h"
23 #include "string-util.h"
24 #include "strv.h"
25 #include "user-util.h"
26
match_uid_gid(uid_t subject_uid,gid_t subject_gid,uid_t delete_uid,gid_t delete_gid)27 static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) {
28
29 if (uid_is_valid(delete_uid) && subject_uid == delete_uid)
30 return true;
31
32 if (gid_is_valid(delete_gid) && subject_gid == delete_gid)
33 return true;
34
35 return false;
36 }
37
clean_sysvipc_shm(uid_t delete_uid,gid_t delete_gid,bool rm)38 static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) {
39 _cleanup_fclose_ FILE *f = NULL;
40 bool first = true;
41 int ret = 0, r;
42
43 f = fopen("/proc/sysvipc/shm", "re");
44 if (!f) {
45 if (errno == ENOENT)
46 return 0;
47
48 return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
49 }
50
51 for (;;) {
52 _cleanup_free_ char *line = NULL;
53 unsigned n_attached;
54 pid_t cpid, lpid;
55 uid_t uid, cuid;
56 gid_t gid, cgid;
57 int shmid;
58
59 r = read_line(f, LONG_LINE_MAX, &line);
60 if (r < 0)
61 return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
62 if (r == 0)
63 break;
64
65 if (first) {
66 first = false;
67 continue;
68 }
69
70 if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
71 &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8)
72 continue;
73
74 if (n_attached > 0)
75 continue;
76
77 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
78 continue;
79
80 if (!rm)
81 return 1;
82
83 if (shmctl(shmid, IPC_RMID, NULL) < 0) {
84
85 /* Ignore entries that are already deleted */
86 if (IN_SET(errno, EIDRM, EINVAL))
87 continue;
88
89 ret = log_warning_errno(errno,
90 "Failed to remove SysV shared memory segment %i: %m",
91 shmid);
92 } else {
93 log_debug("Removed SysV shared memory segment %i.", shmid);
94 if (ret == 0)
95 ret = 1;
96 }
97 }
98
99 return ret;
100 }
101
clean_sysvipc_sem(uid_t delete_uid,gid_t delete_gid,bool rm)102 static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid, bool rm) {
103 _cleanup_fclose_ FILE *f = NULL;
104 bool first = true;
105 int ret = 0, r;
106
107 f = fopen("/proc/sysvipc/sem", "re");
108 if (!f) {
109 if (errno == ENOENT)
110 return 0;
111
112 return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
113 }
114
115 for (;;) {
116 _cleanup_free_ char *line = NULL;
117 uid_t uid, cuid;
118 gid_t gid, cgid;
119 int semid;
120
121 r = read_line(f, LONG_LINE_MAX, &line);
122 if (r < 0)
123 return log_warning_errno(r, "Failed to read /proc/sysvipc/sem: %m");
124 if (r == 0)
125 break;
126
127 if (first) {
128 first = false;
129 continue;
130 }
131
132 if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
133 &semid, &uid, &gid, &cuid, &cgid) != 5)
134 continue;
135
136 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
137 continue;
138
139 if (!rm)
140 return 1;
141
142 if (semctl(semid, 0, IPC_RMID) < 0) {
143
144 /* Ignore entries that are already deleted */
145 if (IN_SET(errno, EIDRM, EINVAL))
146 continue;
147
148 ret = log_warning_errno(errno,
149 "Failed to remove SysV semaphores object %i: %m",
150 semid);
151 } else {
152 log_debug("Removed SysV semaphore %i.", semid);
153 if (ret == 0)
154 ret = 1;
155 }
156 }
157
158 return ret;
159 }
160
clean_sysvipc_msg(uid_t delete_uid,gid_t delete_gid,bool rm)161 static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid, bool rm) {
162 _cleanup_fclose_ FILE *f = NULL;
163 bool first = true;
164 int ret = 0, r;
165
166 f = fopen("/proc/sysvipc/msg", "re");
167 if (!f) {
168 if (errno == ENOENT)
169 return 0;
170
171 return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
172 }
173
174 for (;;) {
175 _cleanup_free_ char *line = NULL;
176 uid_t uid, cuid;
177 gid_t gid, cgid;
178 pid_t cpid, lpid;
179 int msgid;
180
181 r = read_line(f, LONG_LINE_MAX, &line);
182 if (r < 0)
183 return log_warning_errno(r, "Failed to read /proc/sysvipc/msg: %m");
184 if (r == 0)
185 break;
186
187 if (first) {
188 first = false;
189 continue;
190 }
191
192 if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
193 &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
194 continue;
195
196 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
197 continue;
198
199 if (!rm)
200 return 1;
201
202 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
203
204 /* Ignore entries that are already deleted */
205 if (IN_SET(errno, EIDRM, EINVAL))
206 continue;
207
208 ret = log_warning_errno(errno,
209 "Failed to remove SysV message queue %i: %m",
210 msgid);
211 } else {
212 log_debug("Removed SysV message queue %i.", msgid);
213 if (ret == 0)
214 ret = 1;
215 }
216 }
217
218 return ret;
219 }
220
clean_posix_shm_internal(const char * dirname,DIR * dir,uid_t uid,gid_t gid,bool rm)221 static int clean_posix_shm_internal(const char *dirname, DIR *dir, uid_t uid, gid_t gid, bool rm) {
222 int ret = 0, r;
223
224 assert(dir);
225
226 FOREACH_DIRENT_ALL(de, dir, goto fail) {
227 struct stat st;
228
229 if (dot_or_dot_dot(de->d_name))
230 continue;
231
232 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
233 if (errno == ENOENT)
234 continue;
235
236 ret = log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s/%s: %m",
237 dirname, de->d_name);
238 continue;
239 }
240
241 if (S_ISDIR(st.st_mode)) {
242 _cleanup_closedir_ DIR *kid = NULL;
243
244 kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
245 if (!kid) {
246 if (errno != ENOENT)
247 ret = log_warning_errno(errno, "Failed to enter shared memory directory %s/%s: %m",
248 dirname, de->d_name);
249 } else {
250 r = clean_posix_shm_internal(de->d_name, kid, uid, gid, rm);
251 if (r < 0)
252 ret = r;
253 }
254
255 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
256 continue;
257
258 if (!rm)
259 return 1;
260
261 if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
262
263 if (errno == ENOENT)
264 continue;
265
266 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s/%s: %m",
267 dirname, 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
clean_posix_shm(uid_t uid,gid_t gid,bool rm)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("/dev/shm", dir, uid, gid, rm);
313 }
314
clean_posix_mq(uid_t uid,gid_t gid,bool rm)315 static int clean_posix_mq(uid_t uid, gid_t gid, bool rm) {
316 _cleanup_closedir_ DIR *dir = NULL;
317 int ret = 0;
318
319 dir = opendir("/dev/mqueue");
320 if (!dir) {
321 if (errno == ENOENT)
322 return 0;
323
324 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
325 }
326
327 FOREACH_DIRENT_ALL(de, dir, goto fail) {
328 struct stat st;
329 char fn[1+strlen(de->d_name)+1];
330
331 if (dot_or_dot_dot(de->d_name))
332 continue;
333
334 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
335 if (errno == ENOENT)
336 continue;
337
338 ret = log_warning_errno(errno,
339 "Failed to stat() MQ segment %s: %m",
340 de->d_name);
341 continue;
342 }
343
344 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
345 continue;
346
347 if (!rm)
348 return 1;
349
350 fn[0] = '/';
351 strcpy(fn+1, de->d_name);
352
353 if (mq_unlink(fn) < 0) {
354 if (errno == ENOENT)
355 continue;
356
357 ret = log_warning_errno(errno,
358 "Failed to unlink POSIX message queue %s: %m",
359 fn);
360 } else {
361 log_debug("Removed POSIX message queue %s", fn);
362 if (ret == 0)
363 ret = 1;
364 }
365 }
366
367 return ret;
368
369 fail:
370 return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
371 }
372
clean_ipc_internal(uid_t uid,gid_t gid,bool rm)373 int clean_ipc_internal(uid_t uid, gid_t gid, bool rm) {
374 int ret = 0, r;
375
376 /* If 'rm' is true, clean all IPC objects owned by either the specified UID or the specified GID. Return the
377 * last error encountered or == 0 if no matching IPC objects have been found or > 0 if matching IPC objects
378 * have been found and have been removed.
379 *
380 * If 'rm' is false, just search for IPC objects owned by either the specified UID or the specified GID. In
381 * this case we return < 0 on error, > 0 if we found a matching object, == 0 if we didn't.
382 *
383 * As special rule: if UID/GID is specified as root we'll silently not clean up things, and always claim that
384 * there are IPC objects for it. */
385
386 if (uid == 0) {
387 if (!rm)
388 return 1;
389
390 uid = UID_INVALID;
391 }
392 if (gid == 0) {
393 if (!rm)
394 return 1;
395
396 gid = GID_INVALID;
397 }
398
399 /* Anything to do? */
400 if (!uid_is_valid(uid) && !gid_is_valid(gid))
401 return 0;
402
403 r = clean_sysvipc_shm(uid, gid, rm);
404 if (r != 0) {
405 if (!rm)
406 return r;
407 if (ret == 0)
408 ret = r;
409 }
410
411 r = clean_sysvipc_sem(uid, gid, rm);
412 if (r != 0) {
413 if (!rm)
414 return r;
415 if (ret == 0)
416 ret = r;
417 }
418
419 r = clean_sysvipc_msg(uid, gid, rm);
420 if (r != 0) {
421 if (!rm)
422 return r;
423 if (ret == 0)
424 ret = r;
425 }
426
427 r = clean_posix_shm(uid, gid, rm);
428 if (r != 0) {
429 if (!rm)
430 return r;
431 if (ret == 0)
432 ret = r;
433 }
434
435 r = clean_posix_mq(uid, gid, rm);
436 if (r != 0) {
437 if (!rm)
438 return r;
439 if (ret == 0)
440 ret = r;
441 }
442
443 return ret;
444 }
445
clean_ipc_by_uid(uid_t uid)446 int clean_ipc_by_uid(uid_t uid) {
447 return clean_ipc_internal(uid, GID_INVALID, true);
448 }
449
clean_ipc_by_gid(gid_t gid)450 int clean_ipc_by_gid(gid_t gid) {
451 return clean_ipc_internal(UID_INVALID, gid, true);
452 }
453