Browse Source

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

keep-around/964a6d9fb555cc86528eb1cc1f6d044f85584842
Lennart Poettering 9 years ago
parent
commit
66cdd0f2d0
  1. 1
      .gitignore
  2. 14
      Makefile.am
  3. 20
      man/logind.conf.xml
  4. 1
      src/login/logind-gperf.gperf
  5. 8
      src/login/logind-user.c
  6. 1
      src/login/logind.c
  7. 1
      src/login/logind.conf
  8. 2
      src/login/logind.h
  9. 360
      src/shared/clean-ipc.c
  10. 26
      src/shared/clean-ipc.h
  11. 32
      src/test/test-ipcrm.c

1
.gitignore

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

14
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

20
man/logind.conf.xml

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

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

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

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

1
src/login/logind.conf

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

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

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

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

@ -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;
}
Loading…
Cancel
Save