chiark / gitweb /
Merge pull request #3 from elogind/dev_v226-r1
[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                         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 /// UNNEEDED by elogind
281 #if 0
282 static int clean_posix_mq(uid_t uid) {
283         _cleanup_closedir_ DIR *dir = NULL;
284         struct dirent *de;
285         int ret = 0;
286
287         dir = opendir("/dev/mqueue");
288         if (!dir) {
289                 if (errno == ENOENT)
290                         return 0;
291
292                 log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
293                 return -errno;
294         }
295
296         FOREACH_DIRENT(de, dir, goto fail) {
297                 struct stat st;
298                 char fn[1+strlen(de->d_name)+1];
299
300                 if (STR_IN_SET(de->d_name, "..", "."))
301                         continue;
302
303                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
304                         if (errno == ENOENT)
305                                 continue;
306
307                         log_warning_errno(errno, "Failed to stat() MQ segment %s: %m", de->d_name);
308                         ret = -errno;
309                         continue;
310                 }
311
312                 if (st.st_uid != uid)
313                         continue;
314
315                 fn[0] = '/';
316                 strcpy(fn+1, de->d_name);
317
318                 if (mq_unlink(fn) < 0) {
319                         if (errno == ENOENT)
320                                 continue;
321
322                         log_warning_errno(errno, "Failed to unlink POSIX message queue %s: %m", fn);
323                         ret = -errno;
324                 }
325         }
326
327         return ret;
328
329 fail:
330         log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
331         return -errno;
332 }
333 #endif // 0
334
335 int clean_ipc(uid_t uid) {
336         int ret = 0, r;
337
338         /* Refuse to clean IPC of the root and system users */
339         if (uid <= SYSTEM_UID_MAX)
340                 return 0;
341
342         r = clean_sysvipc_shm(uid);
343         if (r < 0)
344                 ret = r;
345
346         r = clean_sysvipc_sem(uid);
347         if (r < 0)
348                 ret = r;
349
350         r = clean_sysvipc_msg(uid);
351         if (r < 0)
352                 ret = r;
353
354         r = clean_posix_shm(uid);
355         if (r < 0)
356                 ret = r;
357
358 /// elogind does not use mq_open anywhere
359 #if 0
360         r = clean_posix_mq(uid);
361         if (r < 0)
362                 ret = r;
363 #endif // 0
364
365         return ret;
366 }