From: Lennart Poettering Date: Fri, 14 Mar 2014 00:38:19 +0000 (+0100) Subject: logind: automatically remove SysV + POSIX IPC objects when the users owning them... X-Git-Tag: v212~183 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=66cdd0f2d0670b054bd27dad16fcb5838b11dde3 logind: automatically remove SysV + POSIX IPC objects when the users owning them fully log out --- diff --git a/.gitignore b/.gitignore index 2e17c72ab..74792b3a1 100644 --- a/.gitignore +++ b/.gitignore @@ -148,6 +148,7 @@ /test-id128 /test-inhibit /test-install +/test-ipcrm /test-job-type /test-journal /test-journal-enum diff --git a/Makefile.am b/Makefile.am index 8dcbaca3d..311ff09d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -795,7 +795,9 @@ libsystemd_shared_la_SOURCES = \ src/shared/bus-label.h \ src/shared/gpt.h \ src/shared/generator.h \ - src/shared/generator.c + src/shared/generator.c \ + src/shared/clean-ipc.h \ + src/shared/clean-ipc.c nodist_libsystemd_shared_la_SOURCES = \ src/shared/errno-from-name.h \ @@ -1176,7 +1178,8 @@ manual_tests += \ test-cgroup \ test-install \ test-watchdog \ - test-log + test-log \ + test-ipcrm tests += \ test-job-type \ @@ -1392,6 +1395,13 @@ test_log_SOURCES = \ test_log_LDADD = \ libsystemd-core.la +test_ipcrm_SOURCES = \ + src/test/test-ipcrm.c + +test_ipcrm_LDADD = \ + libsystemd-shared.la \ + -lrt + test_ellipsize_SOURCES = \ src/test/test-ellipsize.c diff --git a/man/logind.conf.xml b/man/logind.conf.xml index ce8f5097c..99140917a 100644 --- a/man/logind.conf.xml +++ b/man/logind.conf.xml @@ -317,6 +317,26 @@ to. + + RemoveIPC= + + Controls whether + System V and POSIX IPC objects + belonging to the user shall be removed + when she or he fully logs out. Takes a + boolean argument. If enabled the user + may not consume IPC resources after + the last of his sessions + terminated. This covers System V + semaphores, shared memory and message + queues, as well as POSIX shared memory + and message queues. Note that IPC + objects of the root user are excluded + from the effect of this + setting. Defaults to + on. + + diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf index d870f88cd..006f7286c 100644 --- a/src/login/logind-gperf.gperf +++ b/src/login/logind-gperf.gperf @@ -31,3 +31,4 @@ Login.LidSwitchIgnoreInhibited, config_parse_bool, 0, offsetof(Manag Login.IdleAction, config_parse_handle_action, 0, offsetof(Manager, idle_action) Login.IdleActionSec, config_parse_sec, 0, offsetof(Manager, idle_action_usec) Login.RuntimeDirectorySize, config_parse_tmpfs_size, 0, offsetof(Manager, runtime_dir_size) +Login.RemoveIPC, config_parse_bool, 0, offsetof(Manager, remove_ipc) diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 9bbe87957..961cbcb27 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -35,6 +35,7 @@ #include "bus-util.h" #include "bus-error.h" #include "conf-parser.h" +#include "clean-ipc.h" #include "logind-user.h" User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name) { @@ -572,6 +573,13 @@ int user_finalize(User *u) { if (k < 0) r = k; + /* Clean SysV + POSIX IPC objects */ + if (u->manager->remove_ipc) { + k = clean_ipc(u->uid); + if (k < 0) + r = k; + } + unlink(u->state_file); user_add_to_gc_queue(u); diff --git a/src/login/logind.c b/src/login/logind.c index 2d734ff26..8ba8a9155 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -49,6 +49,7 @@ Manager *manager_new(void) { m->n_autovts = 6; m->reserve_vt = 6; + m->remove_ipc = true; m->inhibit_delay_max = 5 * USEC_PER_SEC; m->handle_power_key = HANDLE_POWEROFF; m->handle_suspend_key = HANDLE_SUSPEND; diff --git a/src/login/logind.conf b/src/login/logind.conf index ddc23b8ac..79f96ec05 100644 --- a/src/login/logind.conf +++ b/src/login/logind.conf @@ -25,3 +25,4 @@ #IdleAction=ignore #IdleActionSec=30min #RuntimeDirectorySize=10% +#RemoveIPC=yes diff --git a/src/login/logind.h b/src/login/logind.h index 533554d4b..31353eff0 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -120,6 +120,8 @@ struct Manager { bool hibernate_key_ignore_inhibited; bool lid_switch_ignore_inhibited; + bool remove_ipc; + Hashmap *polkit_registry; sd_event_source *lid_switch_ignore_event_source; diff --git a/src/shared/clean-ipc.c b/src/shared/clean-ipc.c new file mode 100644 index 000000000..ddd42cc2b --- /dev/null +++ b/src/shared/clean-ipc.c @@ -0,0 +1,360 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "strv.h" +#include "clean-ipc.h" + +static int clean_sysvipc_shm(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/shm", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open /proc/sysvipc/shm: %m"); + return -errno; + } + + FOREACH_LINE(line, f, goto fail) { + unsigned n_attached; + pid_t cpid, lpid; + uid_t uid, cuid; + gid_t gid, cgid; + int shmid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8) + continue; + + if (n_attached > 0) + continue; + + if (uid != delete_uid) + continue; + + if (shmctl(shmid, IPC_RMID, NULL) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + log_warning("Failed to remove SysV shared memory segment %i: %m", shmid); + ret = -errno; + } + } + + return ret; + +fail: + log_warning("Failed to read /proc/sysvipc/shm: %m"); + return -errno; +} + +static int clean_sysvipc_sem(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/sem", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open /proc/sysvipc/sem: %m"); + return -errno; + } + + FOREACH_LINE(line, f, goto fail) { + uid_t uid, cuid; + gid_t gid, cgid; + int semid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &semid, &uid, &gid, &cuid, &cgid) != 5) + continue; + + if (uid != delete_uid) + continue; + + if (semctl(semid, 0, IPC_RMID) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + log_warning("Failed to remove SysV semaphores object %i: %m", semid); + ret = -errno; + } + } + + return ret; + +fail: + log_warning("Failed to read /proc/sysvipc/sem: %m"); + return -errno; +} + +static int clean_sysvipc_msg(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/msg", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open /proc/sysvipc/msg: %m"); + return -errno; + } + + FOREACH_LINE(line, f, goto fail) { + uid_t uid, cuid; + gid_t gid, cgid; + pid_t cpid, lpid; + int msgid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7) + continue; + + if (uid != delete_uid) + continue; + + if (msgctl(msgid, IPC_RMID, NULL) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + log_warning("Failed to remove SysV message queue %i: %m", msgid); + ret = -errno; + } + } + + return ret; + +fail: + log_warning("Failed to read /proc/sysvipc/msg: %m"); + return -errno; +} + +static int clean_posix_shm_internal(DIR *dir, uid_t uid) { + struct dirent *de; + int ret = 0, r; + + assert(dir); + + FOREACH_DIRENT(de, dir, goto fail) { + struct stat st; + + if (STR_IN_SET(de->d_name, "..", ".")) + continue; + + if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + log_warning("Failed to stat() POSIX shared memory segment %s: %m", de->d_name); + ret = -errno; + continue; + } + + if (st.st_uid != uid) + continue; + + if (S_ISDIR(st.st_mode)) { + _cleanup_closedir_ DIR *kid; + + kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME); + if (!kid) { + if (errno != ENOENT) { + log_warning("Failed to enter shared memory directory %s: %m", de->d_name); + ret = -errno; + } + } else { + r = clean_posix_shm_internal(kid, uid); + if (r < 0) + ret = r; + } + + if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) { + + if (errno == ENOENT) + continue; + + log_warning("Failed to remove POSIX shared memory directory %s: %m", de->d_name); + ret = -errno; + } + } else { + + if (unlinkat(dirfd(dir), de->d_name, 0) < 0) { + + if (errno == ENOENT) + continue; + + log_warning("Failed to remove POSIX shared memory segment %s: %m", de->d_name); + ret = -errno; + } + } + } + + return ret; + +fail: + log_warning("Failed to read /dev/shm: %m"); + return -errno; +} + +static int clean_posix_shm(uid_t uid) { + _cleanup_closedir_ DIR *dir = NULL; + + dir = opendir("/dev/shm"); + if (!dir) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open /dev/shm: %m"); + return -errno; + } + + return clean_posix_shm_internal(dir, uid); +} + +static int clean_posix_mq(uid_t uid) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *de; + int ret = 0; + + dir = opendir("/dev/mqueue"); + if (!dir) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open /dev/mqueue: %m"); + return -errno; + } + + FOREACH_DIRENT(de, dir, goto fail) { + struct stat st; + char fn[1+strlen(de->d_name)+1]; + + if (STR_IN_SET(de->d_name, "..", ".")) + continue; + + if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + log_warning("Failed to stat() MQ segment %s: %m", de->d_name); + ret = -errno; + continue; + } + + if (st.st_uid != uid) + continue; + + fn[0] = '/'; + strcpy(fn+1, de->d_name); + + if (mq_unlink(fn) < 0) { + if (errno == ENOENT) + continue; + + log_warning("Failed to unlink POSIX message queue %s: %m", fn); + ret = -errno; + } + } + + return ret; + +fail: + log_warning("Failed to read /dev/mqueue: %m"); + return -errno; +} + +int clean_ipc(uid_t uid) { + int ret = 0, r; + + /* Refuse to clean IPC of the root user */ + if (uid == 0) + return 0; + + r = clean_sysvipc_shm(uid); + if (r < 0) + ret = r; + + r = clean_sysvipc_sem(uid); + if (r < 0) + ret = r; + + r = clean_sysvipc_msg(uid); + if (r < 0) + ret = r; + + r = clean_posix_shm(uid); + if (r < 0) + ret = r; + + r = clean_posix_mq(uid); + if (r < 0) + ret = r; + + return ret; +} diff --git a/src/shared/clean-ipc.h b/src/shared/clean-ipc.h new file mode 100644 index 000000000..2de57fad2 --- /dev/null +++ b/src/shared/clean-ipc.h @@ -0,0 +1,26 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +int clean_ipc(uid_t uid); diff --git a/src/test/test-ipcrm.c b/src/test/test-ipcrm.c new file mode 100644 index 000000000..4944bf6ad --- /dev/null +++ b/src/test/test-ipcrm.c @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "util.h" +#include "clean-ipc.h" + +int main(int argc, char *argv[]) { + uid_t uid; + + assert_se(argc == 2); + assert_se(parse_uid(argv[1], &uid) >= 0); + + return clean_ipc(uid) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +}