1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <mqueue.h>
7 #include <stdbool.h>
8 #include <stdio.h>
9 #include <sys/ipc.h>
10 #include <sys/msg.h>
11 #include <sys/sem.h>
12 #include <sys/shm.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 
16 #include "clean-ipc.h"
17 #include "dirent-util.h"
18 #include "fd-util.h"
19 #include "fileio.h"
20 #include "format-util.h"
21 #include "log.h"
22 #include "macro.h"
23 #include "string-util.h"
24 #include "strv.h"
25 #include "user-util.h"
26 
match_uid_gid(uid_t subject_uid,gid_t subject_gid,uid_t delete_uid,gid_t delete_gid)27 static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) {
28 
29         if (uid_is_valid(delete_uid) && subject_uid == delete_uid)
30                 return true;
31 
32         if (gid_is_valid(delete_gid) && subject_gid == delete_gid)
33                 return true;
34 
35         return false;
36 }
37 
clean_sysvipc_shm(uid_t delete_uid,gid_t delete_gid,bool rm)38 static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) {
39         _cleanup_fclose_ FILE *f = NULL;
40         bool first = true;
41         int ret = 0, r;
42 
43         f = fopen("/proc/sysvipc/shm", "re");
44         if (!f) {
45                 if (errno == ENOENT)
46                         return 0;
47 
48                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
49         }
50 
51         for (;;) {
52                 _cleanup_free_ char *line = NULL;
53                 unsigned n_attached;
54                 pid_t cpid, lpid;
55                 uid_t uid, cuid;
56                 gid_t gid, cgid;
57                 int shmid;
58 
59                 r = read_line(f, LONG_LINE_MAX, &line);
60                 if (r < 0)
61                         return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
62                 if (r == 0)
63                         break;
64 
65                 if (first) {
66                         first = false;
67                         continue;
68                 }
69 
70                 if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
71                            &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8)
72                         continue;
73 
74                 if (n_attached > 0)
75                         continue;
76 
77                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
78                         continue;
79 
80                 if (!rm)
81                         return 1;
82 
83                 if (shmctl(shmid, IPC_RMID, NULL) < 0) {
84 
85                         /* Ignore entries that are already deleted */
86                         if (IN_SET(errno, EIDRM, EINVAL))
87                                 continue;
88 
89                         ret = log_warning_errno(errno,
90                                                 "Failed to remove SysV shared memory segment %i: %m",
91                                                 shmid);
92                 } else {
93                         log_debug("Removed SysV shared memory segment %i.", shmid);
94                         if (ret == 0)
95                                 ret = 1;
96                 }
97         }
98 
99         return ret;
100 }
101 
clean_sysvipc_sem(uid_t delete_uid,gid_t delete_gid,bool rm)102 static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid, bool rm) {
103         _cleanup_fclose_ FILE *f = NULL;
104         bool first = true;
105         int ret = 0, r;
106 
107         f = fopen("/proc/sysvipc/sem", "re");
108         if (!f) {
109                 if (errno == ENOENT)
110                         return 0;
111 
112                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
113         }
114 
115         for (;;) {
116                 _cleanup_free_ char *line = NULL;
117                 uid_t uid, cuid;
118                 gid_t gid, cgid;
119                 int semid;
120 
121                 r = read_line(f, LONG_LINE_MAX, &line);
122                 if (r < 0)
123                         return log_warning_errno(r, "Failed to read /proc/sysvipc/sem: %m");
124                 if (r == 0)
125                         break;
126 
127                 if (first) {
128                         first = false;
129                         continue;
130                 }
131 
132                 if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
133                            &semid, &uid, &gid, &cuid, &cgid) != 5)
134                         continue;
135 
136                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
137                         continue;
138 
139                 if (!rm)
140                         return 1;
141 
142                 if (semctl(semid, 0, IPC_RMID) < 0) {
143 
144                         /* Ignore entries that are already deleted */
145                         if (IN_SET(errno, EIDRM, EINVAL))
146                                 continue;
147 
148                         ret = log_warning_errno(errno,
149                                                 "Failed to remove SysV semaphores object %i: %m",
150                                                 semid);
151                 } else {
152                         log_debug("Removed SysV semaphore %i.", semid);
153                         if (ret == 0)
154                                 ret = 1;
155                 }
156         }
157 
158         return ret;
159 }
160 
clean_sysvipc_msg(uid_t delete_uid,gid_t delete_gid,bool rm)161 static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid, bool rm) {
162         _cleanup_fclose_ FILE *f = NULL;
163         bool first = true;
164         int ret = 0, r;
165 
166         f = fopen("/proc/sysvipc/msg", "re");
167         if (!f) {
168                 if (errno == ENOENT)
169                         return 0;
170 
171                 return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
172         }
173 
174         for (;;) {
175                 _cleanup_free_ char *line = NULL;
176                 uid_t uid, cuid;
177                 gid_t gid, cgid;
178                 pid_t cpid, lpid;
179                 int msgid;
180 
181                 r = read_line(f, LONG_LINE_MAX, &line);
182                 if (r < 0)
183                         return log_warning_errno(r, "Failed to read /proc/sysvipc/msg: %m");
184                 if (r == 0)
185                         break;
186 
187                 if (first) {
188                         first = false;
189                         continue;
190                 }
191 
192                 if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
193                            &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
194                         continue;
195 
196                 if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
197                         continue;
198 
199                 if (!rm)
200                         return 1;
201 
202                 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
203 
204                         /* Ignore entries that are already deleted */
205                         if (IN_SET(errno, EIDRM, EINVAL))
206                                 continue;
207 
208                         ret = log_warning_errno(errno,
209                                                 "Failed to remove SysV message queue %i: %m",
210                                                 msgid);
211                 } else {
212                         log_debug("Removed SysV message queue %i.", msgid);
213                         if (ret == 0)
214                                 ret = 1;
215                 }
216         }
217 
218         return ret;
219 }
220 
clean_posix_shm_internal(const char * dirname,DIR * dir,uid_t uid,gid_t gid,bool rm)221 static int clean_posix_shm_internal(const char *dirname, DIR *dir, uid_t uid, gid_t gid, bool rm) {
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/%s: %m",
237                                                 dirname, de->d_name);
238                         continue;
239                 }
240 
241                 if (S_ISDIR(st.st_mode)) {
242                         _cleanup_closedir_ DIR *kid = NULL;
243 
244                         kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
245                         if (!kid) {
246                                 if (errno != ENOENT)
247                                         ret = log_warning_errno(errno, "Failed to enter shared memory directory %s/%s: %m",
248                                                                 dirname, de->d_name);
249                         } else {
250                                 r = clean_posix_shm_internal(de->d_name, kid, uid, gid, rm);
251                                 if (r < 0)
252                                         ret = r;
253                         }
254 
255                         if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
256                                 continue;
257 
258                         if (!rm)
259                                 return 1;
260 
261                         if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
262 
263                                 if (errno == ENOENT)
264                                         continue;
265 
266                                 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s/%s: %m",
267                                                         dirname, de->d_name);
268                         } else {
269                                 log_debug("Removed POSIX shared memory directory %s", de->d_name);
270                                 if (ret == 0)
271                                         ret = 1;
272                         }
273                 } else {
274 
275                         if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
276                                 continue;
277 
278                         if (!rm)
279                                 return 1;
280 
281                         if (unlinkat(dirfd(dir), de->d_name, 0) < 0) {
282 
283                                 if (errno == ENOENT)
284                                         continue;
285 
286                                 ret = log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name);
287                         } else {
288                                 log_debug("Removed POSIX shared memory segment %s", de->d_name);
289                                 if (ret == 0)
290                                         ret = 1;
291                         }
292                 }
293         }
294 
295         return ret;
296 
297 fail:
298         return log_warning_errno(errno, "Failed to read /dev/shm: %m");
299 }
300 
clean_posix_shm(uid_t uid,gid_t gid,bool rm)301 static int clean_posix_shm(uid_t uid, gid_t gid, bool rm) {
302         _cleanup_closedir_ DIR *dir = NULL;
303 
304         dir = opendir("/dev/shm");
305         if (!dir) {
306                 if (errno == ENOENT)
307                         return 0;
308 
309                 return log_warning_errno(errno, "Failed to open /dev/shm: %m");
310         }
311 
312         return clean_posix_shm_internal("/dev/shm", dir, uid, gid, rm);
313 }
314 
clean_posix_mq(uid_t uid,gid_t gid,bool rm)315 static int clean_posix_mq(uid_t uid, gid_t gid, bool rm) {
316         _cleanup_closedir_ DIR *dir = NULL;
317         int ret = 0;
318 
319         dir = opendir("/dev/mqueue");
320         if (!dir) {
321                 if (errno == ENOENT)
322                         return 0;
323 
324                 return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
325         }
326 
327         FOREACH_DIRENT_ALL(de, dir, goto fail) {
328                 struct stat st;
329                 char fn[1+strlen(de->d_name)+1];
330 
331                 if (dot_or_dot_dot(de->d_name))
332                         continue;
333 
334                 if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
335                         if (errno == ENOENT)
336                                 continue;
337 
338                         ret = log_warning_errno(errno,
339                                                 "Failed to stat() MQ segment %s: %m",
340                                                 de->d_name);
341                         continue;
342                 }
343 
344                 if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
345                         continue;
346 
347                 if (!rm)
348                         return 1;
349 
350                 fn[0] = '/';
351                 strcpy(fn+1, de->d_name);
352 
353                 if (mq_unlink(fn) < 0) {
354                         if (errno == ENOENT)
355                                 continue;
356 
357                         ret = log_warning_errno(errno,
358                                                 "Failed to unlink POSIX message queue %s: %m",
359                                                 fn);
360                 } else {
361                         log_debug("Removed POSIX message queue %s", fn);
362                         if (ret == 0)
363                                 ret = 1;
364                 }
365         }
366 
367         return ret;
368 
369 fail:
370         return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
371 }
372 
clean_ipc_internal(uid_t uid,gid_t gid,bool rm)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         r = clean_sysvipc_sem(uid, gid, rm);
412         if (r != 0) {
413                 if (!rm)
414                         return r;
415                 if (ret == 0)
416                         ret = r;
417         }
418 
419         r = clean_sysvipc_msg(uid, gid, rm);
420         if (r != 0) {
421                 if (!rm)
422                         return r;
423                 if (ret == 0)
424                         ret = r;
425         }
426 
427         r = clean_posix_shm(uid, gid, rm);
428         if (r != 0) {
429                 if (!rm)
430                         return r;
431                 if (ret == 0)
432                         ret = r;
433         }
434 
435         r = clean_posix_mq(uid, gid, rm);
436         if (r != 0) {
437                 if (!rm)
438                         return r;
439                 if (ret == 0)
440                         ret = r;
441         }
442 
443         return ret;
444 }
445 
clean_ipc_by_uid(uid_t uid)446 int clean_ipc_by_uid(uid_t uid) {
447         return clean_ipc_internal(uid, GID_INVALID, true);
448 }
449 
clean_ipc_by_gid(gid_t gid)450 int clean_ipc_by_gid(gid_t gid) {
451         return clean_ipc_internal(UID_INVALID, gid, true);
452 }
453