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