#include "manager.h"
#include "path-util.h"
#include "pidref.h"
+#include "process-util.h"
#include "selinux-access.h"
#include "service.h"
#include "set.h"
#include "strv.h"
#include "unit-name.h"
#include "unit.h"
+#include "user-util.h"
#include "varlink-automount.h"
#include "varlink-cgroup.h"
#include "varlink-common.h"
bool set_credentials_encrypted_set;
TransientSetCredential *set_credentials_encrypted;
size_t n_set_credentials_encrypted;
+
+ const char *user;
+ const char *group;
+ char **supplementary_groups;
+ int nice; /* INT_MAX means unset */
} TransientExecContextParameters;
static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) {
static void transient_exec_context_parameters_done(TransientExecContextParameters *p) {
assert(p);
strv_free(p->environment);
+ strv_free(p->supplementary_groups);
transient_set_credential_array_free(p->set_credentials, p->n_set_credentials);
transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted);
}
}
static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
- /* Key names compatible with D-Bus property names */
+ /* Key names compatible with D-Bus property names. */
static const sd_json_dispatch_field exec_dispatch[] = {
- { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 },
- { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 },
- { "SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0 },
- { "SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0 },
+ { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 },
+ { "Group", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(TransientExecContextParameters, group), SD_JSON_RELAX },
+ { "Nice", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int32, offsetof(TransientExecContextParameters, nice), 0 },
+ { "SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0 },
+ { "SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0 },
+ { "SupplementaryGroups", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecContextParameters, supplementary_groups), 0 },
+ { "User", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(TransientExecContextParameters, user), SD_JSON_RELAX },
+ { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 },
{}
};
return r;
}
+ /* User/Group names already validated by json_dispatch_const_user_group_name() in the dispatch table:
+ * either NULL or a non-empty validated name. */
+ if (p->user) {
+ r = free_and_strdup(&c->user, p->user);
+ if (r < 0)
+ return r;
+
+ unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "User",
+ "User=%s", p->user);
+ }
+
+ if (p->group) {
+ r = free_and_strdup(&c->group, p->group);
+ if (r < 0)
+ return r;
+
+ unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "Group",
+ "Group=%s", p->group);
+ }
+
+ if (p->supplementary_groups) {
+ _cleanup_free_ char *joined = NULL;
+
+ STRV_FOREACH(g, p->supplementary_groups)
+ if (!isempty(*g) && !valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid supplementary group name: %s", *g);
+
+ r = strv_extend_strv(&c->supplementary_groups, p->supplementary_groups, /* filter_duplicates= */ true);
+ if (r < 0)
+ return r;
+
+ joined = strv_join(p->supplementary_groups, " ");
+ if (!joined)
+ return -ENOMEM;
+
+ unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "SupplementaryGroups",
+ "SupplementaryGroups=%s", joined);
+ }
+
+ if (p->nice != INT_MAX) {
+ if (!nice_is_valid(p->nice))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid Nice= value: %i", p->nice);
+
+ c->nice = p->nice;
+ c->nice_set = true;
+
+ unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Nice", "Nice=%i", p->nice);
+ }
+
return 0;
}
.notify_unit_changes = -1,
.context.service.type = _SERVICE_TYPE_INVALID,
.context.service.remain_after_exit = -1,
+ .context.exec.nice = INT_MAX,
};
Manager *manager = ASSERT_PTR(userdata);
const char *bad_field = NULL;
grep '^secret-value$' "$CRED_OUTPUT" >/dev/null
rm -f "$CRED_OUTPUT"
+# Exec.User, Exec.Group, Exec.SupplementaryGroups, Exec.Nice
+# The nobody group is different on different distros so resolve here.
+NOBODY_GROUP=$(id -gn nobody)
+defer_transient_cleanup varlink-transient-ids.service
+ids_payload=$(jq -cn --arg g "$NOBODY_GROUP" \
+ '{context:{ID:"varlink-transient-ids.service",
+ Exec:{User:"nobody",Group:$g,SupplementaryGroups:[$g],Nice:5},
+ Service:{Type:"oneshot",RemainAfterExit:true,
+ ExecStart:[{path:"/bin/true"}]}}}')
+result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$ids_payload")
+echo "$result" | jq -e '.context.Exec.User == "nobody"'
+echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.Group == $g'
+echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.SupplementaryGroups == [$g]'
+echo "$result" | jq -e '.context.Exec.Nice == 5'
+timeout 30 bash -c 'until systemctl is-active varlink-transient-ids.service; do sleep 0.5; done'
+systemctl show -P User varlink-transient-ids.service | grep '^nobody$' >/dev/null
+systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" >/dev/null
+systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null
+systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null
+
# Error cases: verify specific varlink error types
set +o pipefail
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
defer_transient_cleanup varlink-transient-bad-env.service
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting"
+# Invalid User= name is rejected at JSON dispatch time as a parameter error
+defer_transient_cleanup varlink-transient-bad-user.service
+varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
+ '{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "Invalid argument"
+# Out-of-range Nice= value is rejected
+defer_transient_cleanup varlink-transient-bad-nice.service
+varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
+ '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting"
# Invalid credential ID
defer_transient_cleanup varlink-transient-bad-cred-id.service
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \