From 8d53b4534a5923721b5f1e9dd7e8f4a903d02d51 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 18 Jan 2012 15:40:21 +0100 Subject: [PATCH] exec: introduce ControlGroupPersistant= to make cgroups persistant --- TODO | 2 -- man/systemd.exec.xml | 15 ++++++++++ src/cgroup-util.c | 47 ++++++++++++++++++++++++++++---- src/cgroup-util.h | 2 +- src/cgroup.c | 19 +++++-------- src/cgroup.h | 8 +++--- src/conf-parser.c | 30 ++++++++++++++++++++ src/conf-parser.h | 1 + src/dbus-execute.c | 1 + src/execute.c | 17 ++++++++++-- src/execute.h | 1 + src/load-fragment-gperf.gperf.m4 | 3 +- src/login/logind-session.c | 2 +- src/util.c | 24 ++++++++++------ src/util.h | 2 +- 15 files changed, 135 insertions(+), 39 deletions(-) diff --git a/TODO b/TODO index e2a19adef..0c3cc9dec 100644 --- a/TODO +++ b/TODO @@ -84,8 +84,6 @@ Features: * tmpfiles: apply "x" on "D" too (see patch from William Douglas) * tmpfiles: support generation of char/block devices, symlinks and one-line files (think sysfs) -* Introduce ControlGroupPersistant=yes to set +t on the tasks file when creating the cgroup - * don't set $HOME in services unless requested * hide PAM/TCPWrap options in fragment parser when compile time disabled diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index c1ab6b901..97bdbba56 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -776,6 +776,21 @@ the group. + + ControlGroupPersistant= + Takes a boolean + argument. If true, the control groups + created for this unit will be marked + to be persistant, i.e. systemd will + not remove them when stopping the + unit. The default is false, meaning + that the control groups will be + removed when the unit is stopped. For + details about the semantics of this + logic see PaxControlGroups. + + ControlGroupAttribute= diff --git a/src/cgroup-util.c b/src/cgroup-util.c index f74280f49..904d30095 100644 --- a/src/cgroup-util.c +++ b/src/cgroup-util.c @@ -173,7 +173,7 @@ int cg_rmdir(const char *controller, const char *path, bool honour_sticky) { return -ENOMEM; } - r = file_is_sticky(tasks); + r = file_is_priv_sticky(tasks); free(tasks); if (r > 0) { @@ -571,7 +571,7 @@ static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct return 1; } - is_sticky = file_is_sticky(p) > 0; + is_sticky = file_is_priv_sticky(p) > 0; free(p); if (is_sticky) @@ -606,7 +606,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root) { return -ENOMEM; } - is_sticky = file_is_sticky(p) > 0; + is_sticky = file_is_priv_sticky(p) > 0; free(p); if (!is_sticky) @@ -712,7 +712,11 @@ int cg_set_group_access(const char *controller, const char *path, mode_t mode, u assert(controller); assert(path); - if ((r = cg_get_path(controller, path, NULL, &fs)) < 0) + if (mode != (mode_t) -1) + mode &= 0777; + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) return r; r = chmod_and_chown(fs, mode, uid, gid); @@ -721,16 +725,47 @@ int cg_set_group_access(const char *controller, const char *path, mode_t mode, u return r; } -int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) { +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky) { char *fs; int r; assert(controller); assert(path); - if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0) + if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0) + return 0; + + if (mode != (mode_t) -1) + mode &= 0666; + + r = cg_get_path(controller, path, "tasks", &fs); + if (r < 0) return r; + if (sticky >= 0 && mode != (mode_t) -1) + /* Both mode and sticky param are passed */ + mode |= (sticky ? S_ISVTX : 0); + else if ((sticky >= 0 && mode == (mode_t) -1) || + (mode != (mode_t) -1 && sticky < 0)) { + struct stat st; + + /* Only one param is passed, hence read the current + * mode from the file itself */ + + r = lstat(fs, &st); + if (r < 0) { + free(fs); + return -errno; + } + + if (mode == (mode_t) -1) + /* No mode set, we just shall set the sticky bit */ + mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0); + else + /* Only mode set, leave sticky bit untouched */ + mode = (st.st_mode & ~0777) | mode; + } + r = chmod_and_chown(fs, mode, uid, gid); free(fs); diff --git a/src/cgroup-util.h b/src/cgroup-util.h index f09373bd0..37e4255a9 100644 --- a/src/cgroup-util.h +++ b/src/cgroup-util.h @@ -60,7 +60,7 @@ int cg_attach(const char *controller, const char *path, pid_t pid); int cg_create_and_attach(const char *controller, const char *path, pid_t pid); int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); -int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky); int cg_install_release_agent(const char *controller, const char *agent); diff --git a/src/cgroup.c b/src/cgroup.c index e141b4153..9aff02e7b 100644 --- a/src/cgroup.c +++ b/src/cgroup.c @@ -60,7 +60,7 @@ int cgroup_bonding_realize_list(CGroupBonding *first) { return 0; } -void cgroup_bonding_free(CGroupBonding *b, bool remove_or_trim) { +void cgroup_bonding_free(CGroupBonding *b, bool trim) { assert(b); if (b->unit) { @@ -79,13 +79,8 @@ void cgroup_bonding_free(CGroupBonding *b, bool remove_or_trim) { } } - if (b->realized && b->ours && remove_or_trim) { - - if (cgroup_bonding_is_empty(b) > 0) - cg_delete(b->controller, b->path); - else - cg_trim(b->controller, b->path, false); - } + if (b->realized && b->ours && trim) + cg_trim(b->controller, b->path, false); free(b->controller); free(b->path); @@ -159,21 +154,21 @@ int cgroup_bonding_set_group_access_list(CGroupBonding *first, mode_t mode, uid_ return 0; } -int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid) { +int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky) { assert(b); if (!b->realized) return -EINVAL; - return cg_set_task_access(b->controller, b->path, mode, uid, gid); + return cg_set_task_access(b->controller, b->path, mode, uid, gid, sticky); } -int cgroup_bonding_set_task_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid) { +int cgroup_bonding_set_task_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid, int sticky) { CGroupBonding *b; int r; LIST_FOREACH(by_unit, b, first) { - r = cgroup_bonding_set_task_access(b, mode, uid, gid); + r = cgroup_bonding_set_task_access(b, mode, uid, gid, sticky); if (r < 0) return r; } diff --git a/src/cgroup.h b/src/cgroup.h index f33d8440e..db4feb916 100644 --- a/src/cgroup.h +++ b/src/cgroup.h @@ -53,8 +53,8 @@ struct CGroupBonding { int cgroup_bonding_realize(CGroupBonding *b); int cgroup_bonding_realize_list(CGroupBonding *first); -void cgroup_bonding_free(CGroupBonding *b, bool remove_or_trim); -void cgroup_bonding_free_list(CGroupBonding *first, bool remove_or_trim); +void cgroup_bonding_free(CGroupBonding *b, bool trim); +void cgroup_bonding_free_list(CGroupBonding *first, bool trim); int cgroup_bonding_install(CGroupBonding *b, pid_t pid); int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid); @@ -62,8 +62,8 @@ int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid); int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); int cgroup_bonding_set_group_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); -int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); -int cgroup_bonding_set_task_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); +int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); +int cgroup_bonding_set_task_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, Set *s); int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, Set *s); diff --git a/src/conf-parser.c b/src/conf-parser.c index 3ccd1c067..ac8d9f5ac 100644 --- a/src/conf-parser.c +++ b/src/conf-parser.c @@ -509,6 +509,36 @@ int config_parse_bool( return 0; } +int config_parse_tristate( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int k; + int *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Tristates are like booleans, but can also take the 'default' value, i.e. "-1" */ + + k = parse_boolean(rvalue); + if (k < 0) { + log_error("[%s:%u] Failed to parse boolean value, ignoring: %s", filename, line, rvalue); + return 0; + } + + *b = !!k; + return 0; +} + int config_parse_string( const char *filename, unsigned line, diff --git a/src/conf-parser.h b/src/conf-parser.h index e970ee283..35edcb63a 100644 --- a/src/conf-parser.h +++ b/src/conf-parser.h @@ -95,6 +95,7 @@ int config_parse_long(const char *filename, unsigned line, const char *section, int config_parse_uint64(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_size(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_tristate(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_path(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_strv(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/dbus-execute.c b/src/dbus-execute.c index caeaf7793..e02d61ca8 100644 --- a/src/dbus-execute.c +++ b/src/dbus-execute.c @@ -416,5 +416,6 @@ const BusProperty bus_exec_context_properties[] = { { "KillSignal", bus_property_append_int, "i", offsetof(ExecContext, kill_signal) }, { "UtmpIdentifier", bus_property_append_string, "s", offsetof(ExecContext, utmp_id), true }, { "ControlGroupModify", bus_property_append_bool, "b", offsetof(ExecContext, control_group_modify) }, + { "ControlGroupModify", bus_property_append_bool, "b", offsetof(ExecContext, control_group_persistant) }, { NULL, } }; diff --git a/src/execute.c b/src/execute.c index cacc8a73c..650c6c143 100644 --- a/src/execute.c +++ b/src/execute.c @@ -993,7 +993,7 @@ int exec_spawn(ExecCommand *command, char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; unsigned n_env = 0; int saved_stdout = -1, saved_stdin = -1; - bool keep_stdout = false, keep_stdin = false; + bool keep_stdout = false, keep_stdin = false, set_access = false; /* child */ @@ -1218,11 +1218,21 @@ int exec_spawn(ExecCommand *command, if (cgroup_bondings && context->control_group_modify) { err = cgroup_bonding_set_group_access_list(cgroup_bondings, 0755, uid, gid); if (err >= 0) - err = cgroup_bonding_set_task_access_list(cgroup_bondings, 0644, uid, gid); + err = cgroup_bonding_set_task_access_list(cgroup_bondings, 0644, uid, gid, context->control_group_persistant); if (err < 0) { r = EXIT_CGROUP; goto fail_child; } + + set_access = true; + } + } + + if (cgroup_bondings && !set_access && context->control_group_persistant >= 0) { + err = cgroup_bonding_set_task_access_list(cgroup_bondings, (mode_t) -1, (uid_t) -1, (uid_t) -1, context->control_group_persistant); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; } } @@ -1488,6 +1498,7 @@ void exec_context_init(ExecContext *c) { c->mount_flags = MS_SHARED; c->kill_signal = SIGTERM; c->send_sigkill = true; + c->control_group_persistant = -1; } void exec_context_done(ExecContext *c) { @@ -1673,6 +1684,7 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { "%sNonBlocking: %s\n" "%sPrivateTmp: %s\n" "%sControlGroupModify: %s\n" + "%sControlGroupPersistant: %s\n" "%sPrivateNetwork: %s\n", prefix, c->umask, prefix, c->working_directory ? c->working_directory : "/", @@ -1680,6 +1692,7 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { prefix, yes_no(c->non_blocking), prefix, yes_no(c->private_tmp), prefix, yes_no(c->control_group_modify), + prefix, yes_no(c->control_group_persistant), prefix, yes_no(c->private_network)); STRV_FOREACH(e, c->environment) diff --git a/src/execute.h b/src/execute.h index 187e8d227..ff33fa92d 100644 --- a/src/execute.h +++ b/src/execute.h @@ -163,6 +163,7 @@ struct ExecContext { bool private_network; bool control_group_modify; + int control_group_persistant; /* This is not exposed to the user but available * internally. We need it to make sure that whenever we spawn diff --git a/src/load-fragment-gperf.gperf.m4 b/src/load-fragment-gperf.gperf.m4 index c3f295710..8adedc59e 100644 --- a/src/load-fragment-gperf.gperf.m4 +++ b/src/load-fragment-gperf.gperf.m4 @@ -86,7 +86,8 @@ $1.KillMode, config_parse_kill_mode, 0, $1.KillSignal, config_parse_kill_signal, 0, offsetof($1, exec_context.kill_signal) $1.SendSIGKILL, config_parse_bool, 0, offsetof($1, exec_context.send_sigkill) $1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) -$1.ControlGroupModify, config_parse_bool, 0, offsetof($1, exec_context.control_group_modify)' +$1.ControlGroupModify, config_parse_bool, 0, offsetof($1, exec_context.control_group_modify) +$1.ControlGroupPersistant, config_parse_tristate, 0, offsetof($1, exec_context.control_group_persistant)' )m4_dnl Unit.Names, config_parse_unit_names, 0, 0 Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description) diff --git a/src/login/logind-session.c b/src/login/logind-session.c index bb802e5fc..5ea7e260a 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -434,7 +434,7 @@ static int session_create_one_group(Session *s, const char *controller, const ch if (r < 0) return r; - r = cg_set_task_access(controller, path, 0644, s->user->uid, s->user->gid); + r = cg_set_task_access(controller, path, 0644, s->user->uid, s->user->gid, -1); if (r >= 0) r = cg_set_group_access(controller, path, 0755, s->user->uid, s->user->gid); diff --git a/src/util.c b/src/util.c index b6e490d45..8004bebbd 100644 --- a/src/util.c +++ b/src/util.c @@ -3483,7 +3483,9 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { } if (honour_sticky) - keep_around = st.st_uid == 0 && (st.st_mode & S_ISVTX); + keep_around = + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); is_dir = S_ISDIR(st.st_mode); @@ -3497,7 +3499,9 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { continue; } - keep_around = st.st_uid == 0 && (st.st_mode & S_ISVTX); + keep_around = + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); } is_dir = de->d_type == DT_DIR; @@ -3559,7 +3563,7 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky if (delete_root) { - if (honour_sticky && file_is_sticky(path) > 0) + if (honour_sticky && file_is_priv_sticky(path) > 0) return r; if (rmdir(path) < 0 && errno != ENOENT) { @@ -3578,11 +3582,13 @@ int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { * first change the access mode and only then hand out * ownership to avoid a window where access is too open. */ - if (chmod(path, mode) < 0) - return -errno; + if (mode != (mode_t) -1) + if (chmod(path, mode) < 0) + return -errno; - if (chown(path, uid, gid) < 0) - return -errno; + if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (chown(path, uid, gid) < 0) + return -errno; return 0; } @@ -5810,7 +5816,7 @@ int block_get_whole_disk(dev_t d, dev_t *ret) { return -ENOENT; } -int file_is_sticky(const char *p) { +int file_is_priv_sticky(const char *p) { struct stat st; assert(p); @@ -5819,7 +5825,7 @@ int file_is_sticky(const char *p) { return -errno; return - st.st_uid == 0 && + (st.st_uid == 0 || st.st_uid == getuid()) && (st.st_mode & S_ISVTX); } diff --git a/src/util.h b/src/util.h index 590dc1781..6acfcc837 100644 --- a/src/util.h +++ b/src/util.h @@ -478,7 +478,7 @@ bool in_charset(const char *s, const char* charset); int block_get_whole_disk(dev_t d, dev_t *ret); -int file_is_sticky(const char *p); +int file_is_priv_sticky(const char *p); int strdup_or_null(const char *a, char **b); -- 2.30.2