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