chiark / gitweb /
virt: when detecting containers and /run/systemd/container cannot be read, check...
[elogind.git] / src / shared / clean-ipc.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/ipc.h>
23 #include <sys/shm.h>
24 #include <sys/sem.h>
25 #include <sys/msg.h>
26 #include <sys/stat.h>
27 #include <sys/mman.h>
28 #include <fcntl.h>
29 #include <dirent.h>
30 #include <mqueue.h>
31
32 #include "util.h"
33 #include "strv.h"
34 #include "clean-ipc.h"
35
36 static int clean_sysvipc_shm(uid_t delete_uid) {
37         _cleanup_fclose_ FILE *f = NULL;
38         char line[LINE_MAX];
39         bool first = true;
40         int ret = 0;
41
42         f = fopen("/proc/sysvipc/shm", "re");
43         if (!f) {
44                 if (errno == ENOENT)
45                         return 0;
46
47                 log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
48                 return -errno;
49         }
50
51         FOREACH_LINE(line, f, goto fail) {
52                 unsigned n_attached;
53                 pid_t cpid, lpid;
54                 uid_t uid, cuid;
55                 gid_t gid, cgid;
56                 int shmid;
57
58                 if (first) {
59                         first = false;
60                         continue;
61                 }
62
63                 truncate_nl(line);
64
65                 if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
66                            &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8)
67                         continue;
68
69                 if (n_attached > 0)
70                         continue;
71
72                 if (uid != delete_uid)
73                         continue;
74
75                 if (shmctl(shmid, IPC_RMID, NULL) < 0) {
76
77                         /* Ignore entries that are already deleted */
78                         if (errno == EIDRM || errno == EINVAL)
79                                 continue;
80
81                         log_warning_errno(errno, "Failed to remove SysV shared memory segment %i: %m", shmid);
82                         ret = -errno;
83                 }
84         }
85
86         return ret;
87
88 fail:
89         log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
90         return -errno;
91 }
92
93 static int clean_sysvipc_sem(uid_t delete_uid) {
94         _cleanup_fclose_ FILE *f = NULL;
95         char line[LINE_MAX];
96         bool first = true;
97         int ret = 0;
98
99         f = fopen("/proc/sysvipc/sem", "re");
100         if (!f) {
101                 if (errno == ENOENT)
102                         return 0;
103
104                 log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
105                 return -errno;
106         }
107
108         FOREACH_LINE(line, f, goto fail) {
109                 uid_t uid, cuid;
110                 gid_t gid, cgid;
111                 int semid;
112
113                 if (first) {
114                         first = false;
115                         continue;
116                 }
117
118                 truncate_nl(line);
119
120                 if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
121                            &semid, &uid, &gid, &cuid, &cgid) != 5)
122                         continue;
123
124                 if (uid != delete_uid)
125                         continue;
126
127                 if (semctl(semid, 0, IPC_RMID) < 0) {
128
129                         /* Ignore entries that are already deleted */
130                         if (errno == EIDRM || errno == EINVAL)
131                                 continue;
132
133                         log_warning_errno(errno, "Failed to remove SysV semaphores object %i: %m", semid);
134                         ret = -errno;
135                 }
136         }
137
138         return ret;
139
140 fail:
141         log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m");
142         return -errno;
143 }
144
145 static int clean_sysvipc_msg(uid_t delete_uid) {
146         _cleanup_fclose_ FILE *f = NULL;
147         char line[LINE_MAX];
148         bool first = true;
149         int ret = 0;
150
151         f = fopen("/proc/sysvipc/msg", "re");
152         if (!f) {
153                 if (errno == ENOENT)
154                         return 0;
155
156                 log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
157                 return -errno;
158         }
159
160         FOREACH_LINE(line, f, goto fail) {
161                 uid_t uid, cuid;
162                 gid_t gid, cgid;
163                 pid_t cpid, lpid;
164                 int msgid;
165
166                 if (first) {
167                         first = false;
168                         continue;
169                 }
170
171                 truncate_nl(line);
172
173                 if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
174                            &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
175                         continue;
176
177                 if (uid != delete_uid)
178                         continue;
179
180                 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
181
182                         /* Ignore entries that are already deleted */
183                         if (errno == EIDRM || errno == EINVAL)
184                                 continue;
185
186                         log_warning_errno(errno, "Failed to remove SysV message queue %i: %m", msgid);
187                         ret = -errno;
188                 }
189         }
190
191         return ret;
192
193 fail:
194         log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m");
195         return -errno;
196 }
197
198 static int clean_posix_shm_internal(DIR *dir, uid_t uid) {
199         struct dirent *de;
200         int ret = 0, r;
201
202         assert(dir);
203
204         FOREACH_DIRENT(de, dir, goto fail) {
205                 struct stat st;
206
207                 if (STR_IN_SET(de->d_name, "..", "."))
208                         continue;
209
210                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
211                         if (errno == ENOENT)
212                                 continue;
213
214                         log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name);
215                         ret = -errno;
216                         continue;
217                 }
218
219                 if (st.st_uid != uid)
220                         continue;
221
222                 if (S_ISDIR(st.st_mode)) {
223                         _cleanup_closedir_ DIR *kid;
224
225                         kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
226                         if (!kid) {
227                                 if (errno != ENOENT) {
228                                         log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name);
229                                         ret = -errno;
230                                 }
231                         } else {
232                                 r = clean_posix_shm_internal(kid, uid);
233                                 if (r < 0)
234                                         ret = r;
235                         }
236
237                         if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
238
239                                 if (errno == ENOENT)
240                                         continue;
241
242                                 log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name);
243                                 ret = -errno;
244                         }
245                 } else {
246
247                         if (unlinkat(dirfd(dir), de->d_name, 0) < 0) {
248
249                                 if (errno == ENOENT)
250                                         continue;
251
252                                 log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name);
253                                 ret = -errno;
254                         }
255                 }
256         }
257
258         return ret;
259
260 fail:
261         log_warning_errno(errno, "Failed to read /dev/shm: %m");
262         return -errno;
263 }
264
265 static int clean_posix_shm(uid_t uid) {
266         _cleanup_closedir_ DIR *dir = NULL;
267
268         dir = opendir("/dev/shm");
269         if (!dir) {
270                 if (errno == ENOENT)
271                         return 0;
272
273                 log_warning_errno(errno, "Failed to open /dev/shm: %m");
274                 return -errno;
275         }
276
277         return clean_posix_shm_internal(dir, uid);
278 }
279
280 static int clean_posix_mq(uid_t uid) {
281         _cleanup_closedir_ DIR *dir = NULL;
282         struct dirent *de;
283         int ret = 0;
284
285         dir = opendir("/dev/mqueue");
286         if (!dir) {
287                 if (errno == ENOENT)
288                         return 0;
289
290                 log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
291                 return -errno;
292         }
293
294         FOREACH_DIRENT(de, dir, goto fail) {
295                 struct stat st;
296                 char fn[1+strlen(de->d_name)+1];
297
298                 if (STR_IN_SET(de->d_name, "..", "."))
299                         continue;
300
301                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
302                         if (errno == ENOENT)
303                                 continue;
304
305                         log_warning_errno(errno, "Failed to stat() MQ segment %s: %m", de->d_name);
306                         ret = -errno;
307                         continue;
308                 }
309
310                 if (st.st_uid != uid)
311                         continue;
312
313                 fn[0] = '/';
314                 strcpy(fn+1, de->d_name);
315
316                 if (mq_unlink(fn) < 0) {
317                         if (errno == ENOENT)
318                                 continue;
319
320                         log_warning_errno(errno, "Failed to unlink POSIX message queue %s: %m", fn);
321                         ret = -errno;
322                 }
323         }
324
325         return ret;
326
327 fail:
328         log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
329         return -errno;
330 }
331
332 int clean_ipc(uid_t uid) {
333         int ret = 0, r;
334
335         /* Refuse to clean IPC of the root and system users */
336         if (uid <= SYSTEM_UID_MAX)
337                 return 0;
338
339         r = clean_sysvipc_shm(uid);
340         if (r < 0)
341                 ret = r;
342
343         r = clean_sysvipc_sem(uid);
344         if (r < 0)
345                 ret = r;
346
347         r = clean_sysvipc_msg(uid);
348         if (r < 0)
349                 ret = r;
350
351         r = clean_posix_shm(uid);
352         if (r < 0)
353                 ret = r;
354
355         r = clean_posix_mq(uid);
356         if (r < 0)
357                 ret = r;
358
359         return ret;
360 }