chiark / gitweb /
Prep v228: Clean up the new src/basic/*-util-[hc] files:
[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 /// 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                 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
293         }
294
295         FOREACH_DIRENT(de, dir, goto fail) {
296                 struct stat st;
297                 char fn[1+strlen(de->d_name)+1];
298
299                 if (STR_IN_SET(de->d_name, "..", "."))
300                         continue;
301
302                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
303                         if (errno == ENOENT)
304                                 continue;
305
306                         ret = log_warning_errno(errno,
307                                                 "Failed to stat() MQ segment %s: %m",
308                                                 de->d_name);
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                         ret = log_warning_errno(errno,
323                                                 "Failed to unlink POSIX message queue %s: %m",
324                                                 fn);
325                 }
326         }
327
328         return ret;
329
330 fail:
331         return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
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 }