]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/clean-ipc.c
core: add RemoveIPC= setting
[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 "formats-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) {
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 (shmctl(shmid, IPC_RMID, NULL) < 0) {
96
97 /* Ignore entries that are already deleted */
98 if (errno == EIDRM || errno == EINVAL)
99 continue;
100
101 ret = log_warning_errno(errno,
102 "Failed to remove SysV shared memory segment %i: %m",
103 shmid);
104 }
105 }
106
107 return ret;
108
109 fail:
110 return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
111 }
112
113 static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid) {
114 _cleanup_fclose_ FILE *f = NULL;
115 char line[LINE_MAX];
116 bool first = true;
117 int ret = 0;
118
119 f = fopen("/proc/sysvipc/sem", "re");
120 if (!f) {
121 if (errno == ENOENT)
122 return 0;
123
124 return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
125 }
126
127 FOREACH_LINE(line, f, goto fail) {
128 uid_t uid, cuid;
129 gid_t gid, cgid;
130 int semid;
131
132 if (first) {
133 first = false;
134 continue;
135 }
136
137 truncate_nl(line);
138
139 if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
140 &semid, &uid, &gid, &cuid, &cgid) != 5)
141 continue;
142
143 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
144 continue;
145
146 if (semctl(semid, 0, IPC_RMID) < 0) {
147
148 /* Ignore entries that are already deleted */
149 if (errno == EIDRM || errno == EINVAL)
150 continue;
151
152 ret = log_warning_errno(errno,
153 "Failed to remove SysV semaphores object %i: %m",
154 semid);
155 }
156 }
157
158 return ret;
159
160 fail:
161 return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m");
162 }
163
164 static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid) {
165 _cleanup_fclose_ FILE *f = NULL;
166 char line[LINE_MAX];
167 bool first = true;
168 int ret = 0;
169
170 f = fopen("/proc/sysvipc/msg", "re");
171 if (!f) {
172 if (errno == ENOENT)
173 return 0;
174
175 return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
176 }
177
178 FOREACH_LINE(line, f, goto fail) {
179 uid_t uid, cuid;
180 gid_t gid, cgid;
181 pid_t cpid, lpid;
182 int msgid;
183
184 if (first) {
185 first = false;
186 continue;
187 }
188
189 truncate_nl(line);
190
191 if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
192 &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
193 continue;
194
195 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
196 continue;
197
198 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
199
200 /* Ignore entries that are already deleted */
201 if (errno == EIDRM || errno == EINVAL)
202 continue;
203
204 ret = log_warning_errno(errno,
205 "Failed to remove SysV message queue %i: %m",
206 msgid);
207 }
208 }
209
210 return ret;
211
212 fail:
213 return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m");
214 }
215
216 static int clean_posix_shm_internal(DIR *dir, uid_t uid, gid_t gid) {
217 struct dirent *de;
218 int ret = 0, r;
219
220 assert(dir);
221
222 FOREACH_DIRENT_ALL(de, dir, goto fail) {
223 struct stat st;
224
225 if (STR_IN_SET(de->d_name, "..", "."))
226 continue;
227
228 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
229 if (errno == ENOENT)
230 continue;
231
232 ret = log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name);
233 continue;
234 }
235
236 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
237 continue;
238
239 if (S_ISDIR(st.st_mode)) {
240 _cleanup_closedir_ DIR *kid;
241
242 kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
243 if (!kid) {
244 if (errno != ENOENT)
245 ret = log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name);
246 } else {
247 r = clean_posix_shm_internal(kid, uid, gid);
248 if (r < 0)
249 ret = r;
250 }
251
252 if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
253
254 if (errno == ENOENT)
255 continue;
256
257 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name);
258 }
259 } else {
260
261 if (unlinkat(dirfd(dir), de->d_name, 0) < 0) {
262
263 if (errno == ENOENT)
264 continue;
265
266 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name);
267 }
268 }
269 }
270
271 return ret;
272
273 fail:
274 return log_warning_errno(errno, "Failed to read /dev/shm: %m");
275 }
276
277 static int clean_posix_shm(uid_t uid, gid_t gid) {
278 _cleanup_closedir_ DIR *dir = NULL;
279
280 dir = opendir("/dev/shm");
281 if (!dir) {
282 if (errno == ENOENT)
283 return 0;
284
285 return log_warning_errno(errno, "Failed to open /dev/shm: %m");
286 }
287
288 return clean_posix_shm_internal(dir, uid, gid);
289 }
290
291 static int clean_posix_mq(uid_t uid, gid_t gid) {
292 _cleanup_closedir_ DIR *dir = NULL;
293 struct dirent *de;
294 int ret = 0;
295
296 dir = opendir("/dev/mqueue");
297 if (!dir) {
298 if (errno == ENOENT)
299 return 0;
300
301 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
302 }
303
304 FOREACH_DIRENT_ALL(de, dir, goto fail) {
305 struct stat st;
306 char fn[1+strlen(de->d_name)+1];
307
308 if (STR_IN_SET(de->d_name, "..", "."))
309 continue;
310
311 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
312 if (errno == ENOENT)
313 continue;
314
315 ret = log_warning_errno(errno,
316 "Failed to stat() MQ segment %s: %m",
317 de->d_name);
318 continue;
319 }
320
321 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
322 continue;
323
324 fn[0] = '/';
325 strcpy(fn+1, de->d_name);
326
327 if (mq_unlink(fn) < 0) {
328 if (errno == ENOENT)
329 continue;
330
331 ret = log_warning_errno(errno,
332 "Failed to unlink POSIX message queue %s: %m",
333 fn);
334 }
335 }
336
337 return ret;
338
339 fail:
340 return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
341 }
342
343 int clean_ipc(uid_t uid, gid_t gid) {
344 int ret = 0, r;
345
346 /* Anything to do? */
347 if (!uid_is_valid(uid) && !gid_is_valid(gid))
348 return 0;
349
350 /* Refuse to clean IPC of the root user */
351 if (uid == 0 && gid == 0)
352 return 0;
353
354 r = clean_sysvipc_shm(uid, gid);
355 if (r < 0)
356 ret = r;
357
358 r = clean_sysvipc_sem(uid, gid);
359 if (r < 0)
360 ret = r;
361
362 r = clean_sysvipc_msg(uid, gid);
363 if (r < 0)
364 ret = r;
365
366 r = clean_posix_shm(uid, gid);
367 if (r < 0)
368 ret = r;
369
370 r = clean_posix_mq(uid, gid);
371 if (r < 0)
372 ret = r;
373
374 return ret;
375 }
376
377 int clean_ipc_by_uid(uid_t uid) {
378 return clean_ipc(uid, GID_INVALID);
379 }
380
381 int clean_ipc_by_gid(gid_t gid) {
382 return clean_ipc(UID_INVALID, gid);
383 }