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