chiark / gitweb /
dynamic-user: don't use a UID that currently owns IPC objects (#6962)
[elogind.git] / src / shared / clean-ipc.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2014 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <dirent.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <mqueue.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <sys/ipc.h>
29 #include <sys/msg.h>
30 #include <sys/sem.h>
31 #include <sys/shm.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34
35 #include "clean-ipc.h"
36 #include "dirent-util.h"
37 #include "fd-util.h"
38 #include "fileio.h"
39 #include "format-util.h"
40 #include "log.h"
41 #include "macro.h"
42 #include "string-util.h"
43 #include "strv.h"
44 #include "user-util.h"
45
46 static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) {
47
48         if (uid_is_valid(delete_uid) && subject_uid == delete_uid)
49                 return true;
50
51         if (gid_is_valid(delete_gid) && subject_gid == delete_gid)
52                 return true;
53
54         return false;
55 }
56
57 static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) {
58         _cleanup_fclose_ FILE *f = NULL;
59         char line[LINE_MAX];
60         bool first = true;
61         int ret = 0;
62
63         f = fopen("/proc/sysvipc/shm", "re");
64         if (!f) {
65                 if (errno == ENOENT)
66                         return 0;
67
68                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
69         }
70
71         FOREACH_LINE(line, f, goto fail) {
72                 unsigned n_attached;
73                 pid_t cpid, lpid;
74                 uid_t uid, cuid;
75                 gid_t gid, cgid;
76                 int shmid;
77
78                 if (first) {
79                         first = false;
80                         continue;
81                 }
82
83                 truncate_nl(line);
84
85                 if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
86                            &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8)
87                         continue;
88
89                 if (n_attached > 0)
90                         continue;
91
92                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
93                         continue;
94
95                 if (!rm)
96                         return 1;
97
98                 if (shmctl(shmid, IPC_RMID, NULL) < 0) {
99
100                         /* Ignore entries that are already deleted */
101                         if (IN_SET(errno, EIDRM, EINVAL))
102                                 continue;
103
104                         ret = log_warning_errno(errno,
105                                                 "Failed to remove SysV shared memory segment %i: %m",
106                                                 shmid);
107                 } else {
108                         log_debug("Removed SysV shared memory segment %i.", shmid);
109                         if (ret == 0)
110                                 ret = 1;
111                 }
112         }
113
114         return ret;
115
116 fail:
117         return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
118 }
119
120 static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid, bool rm) {
121         _cleanup_fclose_ FILE *f = NULL;
122         char line[LINE_MAX];
123         bool first = true;
124         int ret = 0;
125
126         f = fopen("/proc/sysvipc/sem", "re");
127         if (!f) {
128                 if (errno == ENOENT)
129                         return 0;
130
131                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
132         }
133
134         FOREACH_LINE(line, f, goto fail) {
135                 uid_t uid, cuid;
136                 gid_t gid, cgid;
137                 int semid;
138
139                 if (first) {
140                         first = false;
141                         continue;
142                 }
143
144                 truncate_nl(line);
145
146                 if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
147                            &semid, &uid, &gid, &cuid, &cgid) != 5)
148                         continue;
149
150                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
151                         continue;
152
153                 if (!rm)
154                         return 1;
155
156                 if (semctl(semid, 0, IPC_RMID) < 0) {
157
158                         /* Ignore entries that are already deleted */
159                         if (IN_SET(errno, EIDRM, EINVAL))
160                                 continue;
161
162                         ret = log_warning_errno(errno,
163                                                 "Failed to remove SysV semaphores object %i: %m",
164                                                 semid);
165                 } else {
166                         log_debug("Removed SysV semaphore %i.", semid);
167                         if (ret == 0)
168                                 ret = 1;
169                 }
170         }
171
172         return ret;
173
174 fail:
175         return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m");
176 }
177
178 static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid, bool rm) {
179         _cleanup_fclose_ FILE *f = NULL;
180         char line[LINE_MAX];
181         bool first = true;
182         int ret = 0;
183
184         f = fopen("/proc/sysvipc/msg", "re");
185         if (!f) {
186                 if (errno == ENOENT)
187                         return 0;
188
189                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
190         }
191
192         FOREACH_LINE(line, f, goto fail) {
193                 uid_t uid, cuid;
194                 gid_t gid, cgid;
195                 pid_t cpid, lpid;
196                 int msgid;
197
198                 if (first) {
199                         first = false;
200                         continue;
201                 }
202
203                 truncate_nl(line);
204
205                 if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
206                            &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
207                         continue;
208
209                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
210                         continue;
211
212                 if (!rm)
213                         return 1;
214
215                 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
216
217                         /* Ignore entries that are already deleted */
218                         if (IN_SET(errno, EIDRM, EINVAL))
219                                 continue;
220
221                         ret = log_warning_errno(errno,
222                                                 "Failed to remove SysV message queue %i: %m",
223                                                 msgid);
224                 } else {
225                         log_debug("Removed SysV message queue %i.", msgid);
226                         if (ret == 0)
227                                 ret = 1;
228                 }
229         }
230
231         return ret;
232
233 fail:
234         return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m");
235 }
236
237 static int clean_posix_shm_internal(DIR *dir, uid_t uid, gid_t gid, bool rm) {
238         struct dirent *de;
239         int ret = 0, r;
240
241         assert(dir);
242
243         FOREACH_DIRENT_ALL(de, dir, goto fail) {
244                 struct stat st;
245
246                 if (dot_or_dot_dot(de->d_name))
247                         continue;
248
249                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
250                         if (errno == ENOENT)
251                                 continue;
252
253                         ret = log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name);
254                         continue;
255                 }
256
257                 if (S_ISDIR(st.st_mode)) {
258                         _cleanup_closedir_ DIR *kid;
259
260                         kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
261                         if (!kid) {
262                                 if (errno != ENOENT)
263                                         ret = log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name);
264                         } else {
265                                 r = clean_posix_shm_internal(kid, uid, gid, rm);
266                                 if (r < 0)
267                                         ret = r;
268                         }
269
270                         if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
271                                 continue;
272
273                         if (!rm)
274                                 return 1;
275
276                         if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
277
278                                 if (errno == ENOENT)
279                                         continue;
280
281                                 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name);
282                         } else {
283                                 log_debug("Removed POSIX shared memory directory %s", de->d_name);
284                                 if (ret == 0)
285                                         ret = 1;
286                         }
287                 } else {
288
289                         if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
290                                 continue;
291
292                         if (!rm)
293                                 return 1;
294
295                         if (unlinkat(dirfd(dir), de->d_name, 0) < 0) {
296
297                                 if (errno == ENOENT)
298                                         continue;
299
300                                 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name);
301                         } else {
302                                 log_debug("Removed POSIX shared memory segment %s", de->d_name);
303                                 if (ret == 0)
304                                         ret = 1;
305                         }
306                 }
307         }
308
309         return ret;
310
311 fail:
312         return log_warning_errno(errno, "Failed to read /dev/shm: %m");
313 }
314
315 static int clean_posix_shm(uid_t uid, gid_t gid, bool rm) {
316         _cleanup_closedir_ DIR *dir = NULL;
317
318         dir = opendir("/dev/shm");
319         if (!dir) {
320                 if (errno == ENOENT)
321                         return 0;
322
323                 return log_warning_errno(errno, "Failed to open /dev/shm: %m");
324         }
325
326         return clean_posix_shm_internal(dir, uid, gid, rm);
327 }
328
329 #if 0 /// UNNEEDED by elogind
330 static int clean_posix_mq(uid_t uid, gid_t gid, bool rm) {
331         _cleanup_closedir_ DIR *dir = NULL;
332         struct dirent *de;
333         int ret = 0;
334
335         dir = opendir("/dev/mqueue");
336         if (!dir) {
337                 if (errno == ENOENT)
338                         return 0;
339
340                 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
341         }
342
343         FOREACH_DIRENT_ALL(de, dir, goto fail) {
344                 struct stat st;
345                 char fn[1+strlen(de->d_name)+1];
346
347                 if (dot_or_dot_dot(de->d_name))
348                         continue;
349
350                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
351                         if (errno == ENOENT)
352                                 continue;
353
354                         ret = log_warning_errno(errno,
355                                                 "Failed to stat() MQ segment %s: %m",
356                                                 de->d_name);
357                         continue;
358                 }
359
360                 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
361                         continue;
362
363                 if (!rm)
364                         return 1;
365
366                 fn[0] = '/';
367                 strcpy(fn+1, de->d_name);
368
369                 if (mq_unlink(fn) < 0) {
370                         if (errno == ENOENT)
371                                 continue;
372
373                         ret = log_warning_errno(errno,
374                                                 "Failed to unlink POSIX message queue %s: %m",
375                                                 fn);
376                 } else {
377                         log_debug("Removed POSIX message queue %s", fn);
378                         if (ret == 0)
379                                 ret = 1;
380                 }
381         }
382
383         return ret;
384
385 fail:
386         return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
387 }
388 #endif // 0
389
390 int clean_ipc_internal(uid_t uid, gid_t gid, bool rm) {
391         int ret = 0, r;
392
393         /* If 'rm' is true, clean all IPC objects owned by either the specified UID or the specified GID. Return the
394          * last error encountered or == 0 if no matching IPC objects have been found or > 0 if matching IPC objects
395          * have been found and have been removed.
396          *
397          * If 'rm' is false, just search for IPC objects owned by either the specified UID or the specified GID. In
398          * this case we return < 0 on error, > 0 if we found a matching object, == 0 if we didn't.
399          *
400          * As special rule: if UID/GID is specified as root we'll silently not clean up things, and always claim that
401          * there are IPC objects for it. */
402
403         if (uid == 0) {
404                 if (!rm)
405                         return 1;
406
407                 uid = UID_INVALID;
408         }
409         if (gid == 0) {
410                 if (!rm)
411                         return 1;
412
413                 gid = GID_INVALID;
414         }
415
416         /* Anything to do? */
417         if (!uid_is_valid(uid) && !gid_is_valid(gid))
418                 return 0;
419
420         r = clean_sysvipc_shm(uid, gid, rm);
421         if (r != 0) {
422                 if (!rm)
423                         return r;
424                 if (ret == 0)
425                         ret = r;
426         }
427
428 #if 0 /// elogind does not use mq_open anywhere
429 #endif // 0
430         r = clean_sysvipc_sem(uid, gid, rm);
431         if (r != 0) {
432                 if (!rm)
433                         return r;
434                 if (ret == 0)
435                         ret = r;
436         }
437
438         r = clean_sysvipc_msg(uid, gid, rm);
439         if (r != 0) {
440                 if (!rm)
441                         return r;
442                 if (ret == 0)
443                         ret = r;
444         }
445
446         r = clean_posix_shm(uid, gid, rm);
447         if (r != 0) {
448                 if (!rm)
449                         return r;
450                 if (ret == 0)
451                         ret = r;
452         }
453
454         r = clean_posix_mq(uid, gid, rm);
455         if (r != 0) {
456                 if (!rm)
457                         return r;
458                 if (ret == 0)
459                         ret = r;
460         }
461
462         return ret;
463 }
464
465 int clean_ipc_by_uid(uid_t uid) {
466         return clean_ipc_internal(uid, GID_INVALID, true);
467 }
468
469 #if 0 /// UNNEEDED by elogind
470 int clean_ipc_by_gid(gid_t gid) {
471         return clean_ipc_internal(UID_INVALID, gid, true);
472 }
473 #endif // 0