logind: automatically remove SysV + POSIX IPC objects when the users owning them fully log out

This commit is contained in:
Lennart Poettering 2014-03-14 01:38:19 +01:00
parent bcdbbd7ee1
commit 66cdd0f2d0
11 changed files with 464 additions and 2 deletions

1
.gitignore vendored
View file

@ -148,6 +148,7 @@
/test-id128
/test-inhibit
/test-install
/test-ipcrm
/test-job-type
/test-journal
/test-journal-enum

View file

@ -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

View file

@ -317,6 +317,26 @@
to.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>RemoveIPC=</varname></term>
<listitem><para>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.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>

View file

@ -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)

View file

@ -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);

View file

@ -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;

View file

@ -25,3 +25,4 @@
#IdleAction=ignore
#IdleActionSec=30min
#RuntimeDirectorySize=10%
#RemoveIPC=yes

View file

@ -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;

360
src/shared/clean-ipc.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <dirent.h>
#include <mqueue.h>
#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;
}

26
src/shared/clean-ipc.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#include <sys/types.h>
int clean_ipc(uid_t uid);

32
src/test/test-ipcrm.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#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;
}