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