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