diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 142de0db8f..a3efed436d 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -691,7 +691,22 @@ typedef struct TransientExecContextParameters { const char *user; const char *group; char **supplementary_groups; - int nice; /* INT_MAX means unset */ + bool nice_set; + int nice; + bool oom_score_adjust_set; + int oom_score_adjust; + bool umask_set; + uint32_t umask; + /* Tristate bools: -1 = absent, 0/1 = no/yes. */ + int dynamic_user; + int ignore_sigpipe; + int lock_personality; + int memory_deny_write_execute; + int no_new_privileges; + int remove_ipc; + int restrict_realtime; + int restrict_suid_sgid; + int root_ephemeral; } TransientExecContextParameters; static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) { @@ -723,6 +738,14 @@ static void transient_service_parameters_done(TransientServiceParameters *p) { free(p->exec_start); } +static void transient_service_parameters_init(TransientServiceParameters *p) { + assert(p); + *p = (TransientServiceParameters) { + .type = _SERVICE_TYPE_INVALID, + .remain_after_exit = -1, + }; +} + static int dispatch_transient_exec_command(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field exec_command_dispatch[] = { { "path", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecCommandItem, path), SD_JSON_MANDATORY }, @@ -771,6 +794,15 @@ static void start_transient_context_parameters_done(StartTransientContextParamet transient_service_parameters_done(&p->service); } +static void transient_exec_context_parameters_init(TransientExecContextParameters *p); + +static void start_transient_context_parameters_init(StartTransientContextParameters *p) { + assert(p); + *p = (StartTransientContextParameters) {}; + transient_exec_context_parameters_init(&p->exec); + transient_service_parameters_init(&p->service); +} + typedef struct StartTransientParameters { StartTransientContextParameters context; JobMode mode; @@ -785,6 +817,16 @@ static void start_transient_parameters_done(StartTransientParameters *p) { free(p->unsupported_property); } +static void start_transient_parameters_init(StartTransientParameters *p) { + assert(p); + *p = (StartTransientParameters) { + .mode = JOB_REPLACE, + .notify_job_changes = -1, + .notify_unit_changes = -1, + }; + start_transient_context_parameters_init(&p->context); +} + static int dispatch_const_string_empty_as_null(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { const char **s = ASSERT_PTR(userdata); int r; @@ -809,11 +851,24 @@ static int dispatch_transient_working_directory(const char *name, sd_json_varian return sd_json_dispatch(variant, dispatch, flags, &p->working_directory); } -static int dispatch_transient_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - TransientExecContextParameters *p = ASSERT_PTR(userdata); - p->environment_set = true; - return sd_json_dispatch_strv(name, variant, flags, &p->environment); -} +/* Generate a parse wrapper that flips TransientExecContextParameters._set and delegates the + * actual value parse to the named primitive dispatcher. For fields where the JSON value type alone + * cannot distinguish "absent" from "explicitly set to the default" (int 0, empty strv, etc.). */ +#define DEFINE_TRANSIENT_EXEC_SETTABLE(field, primitive_dispatch) \ + static int dispatch_transient_##field( \ + const char *name, \ + sd_json_variant *variant, \ + sd_json_dispatch_flags_t flags, \ + void *userdata) { \ + TransientExecContextParameters *p = ASSERT_PTR(userdata); \ + p->field##_set = true; \ + return primitive_dispatch(name, variant, flags, &p->field); \ + } + +DEFINE_TRANSIENT_EXEC_SETTABLE(environment, sd_json_dispatch_strv); +DEFINE_TRANSIENT_EXEC_SETTABLE(nice, sd_json_dispatch_int32); +DEFINE_TRANSIENT_EXEC_SETTABLE(oom_score_adjust, sd_json_dispatch_int32); +DEFINE_TRANSIENT_EXEC_SETTABLE(umask, sd_json_dispatch_uint32); static int dispatch_transient_set_credential_array( sd_json_variant *variant, @@ -869,24 +924,7 @@ static int dispatch_transient_set_credential_encrypted(const char *name, sd_json return dispatch_transient_set_credential_array(variant, &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. */ - static const sd_json_dispatch_field exec_dispatch[] = { - { "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 }, - {} - }; - - StartTransientContextParameters *p = ASSERT_PTR(userdata); - p->exec.present = true; - return sd_json_dispatch_full(variant, exec_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->exec, &p->bad_exec_field); -} +static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); static int dispatch_transient_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field service_dispatch[] = { @@ -976,146 +1014,320 @@ static int transient_apply_set_credentials( return 0; } -static int transient_exec_context_apply_properties( - Unit *u, - ExecContext *c, - TransientExecContextParameters *p, - const char **reterr_field) { +static int apply_exec_environment(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + int r; + assert(p); + + if (!p->environment_set) + return 0; + + r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE); + if (IN_SET(r, -E2BIG, -EINVAL)) + /* Convert E2BIG into EINVAL so the central loop maps it to Exec.Environment. */ + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + r == -E2BIG ? "Too many environment assignments." : "Invalid Environment list."); + return r; +} + +static int apply_exec_nice(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + assert(p); + + if (!p->nice_set) + return 0; + + 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; +} + +static int apply_exec_oom_score_adjust(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + if (!p->oom_score_adjust_set) + return 0; + + if (!oom_score_adjust_is_valid(p->oom_score_adjust)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid OOMScoreAdjust= value: %i", p->oom_score_adjust); + + c->oom_score_adjust = p->oom_score_adjust; + c->oom_score_adjust_set = true; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "OOMScoreAdjust", + "OOMScoreAdjust=%i", p->oom_score_adjust); + return 0; +} + +static int apply_exec_set_credential(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + assert(p); + + if (!p->set_credentials_set) + return 0; + return transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false); +} + +static int apply_exec_set_credential_encrypted(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + assert(p); + + if (!p->set_credentials_encrypted_set) + return 0; + return transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true); +} + +static int apply_exec_supplementary_groups(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + _cleanup_free_ char *joined = NULL; + int r; + + assert(p); + + if (!p->supplementary_groups) + return 0; + + STRV_FOREACH(g, p->supplementary_groups) + if (!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); + return 0; +} + +static int apply_exec_umask(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + if (!p->umask_set) + return 0; + + /* mode_t bits beyond 07777 are reserved/meaningless; reject so a caller passing a stray + * negative or out-of-range int fails clearly instead of having the kernel silently mask it. */ + if (p->umask > 07777) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid UMask= value: %#" PRIo32, p->umask); + + c->umask = (mode_t) p->umask; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "UMask", "UMask=%04" PRIo32, p->umask); + return 0; +} + +static int apply_exec_working_directory(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + _cleanup_free_ char *simplified = NULL; + TransientWorkingDirectory *wd = &p->working_directory; + int r; + + assert(p); + + if (!p->working_directory_set) + return 0; + + if (wd->home && wd->path) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: 'home' and 'path' are mutually exclusive"); + if (!wd->home && !wd->path) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: must specify either 'home' or 'path'"); + + if (!wd->home) { + if (!path_is_absolute(wd->path)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: expects an absolute path"); + r = path_simplify_alloc(wd->path, &simplified); + if (r < 0) + return r; + if (!path_is_normalized(simplified)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: expects a normalized path"); + } + + free_and_replace(c->working_directory, simplified); + c->working_directory_home = wd->home; + c->working_directory_missing_ok = wd->missing_ok; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "WorkingDirectory", + "WorkingDirectory=%s%s", + c->working_directory_missing_ok ? "-" : "", + c->working_directory_home ? "~" : strempty(c->working_directory)); + return 0; +} + +/* Generate an apply fn for an exec-context string field: copy the parsed value to ExecContext and + * write the corresponding "Name=value" line, skipping if the caller didn't set the field. + * Used where the parsed value is NULL or already validated by the dispatch callback. */ +#define DEFINE_APPLY_EXEC_STRING(field, JsonName) \ + static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \ + int r; \ + \ + assert(p); \ + \ + if (!p->field) \ + return 0; \ + \ + r = free_and_strdup(&c->field, p->field); \ + if (r < 0) \ + return r; \ + \ + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, JsonName, \ + JsonName "=%s", p->field); \ + return 0; \ + } + +DEFINE_APPLY_EXEC_STRING(user, "User"); +DEFINE_APPLY_EXEC_STRING(group, "Group"); + +/* Generate an apply fn for an exec-context tristate bool: copy the parsed value to ExecContext and + * write the corresponding "Name=yes/no" line, skipping if the caller didn't set the field. + * Used for the sandbox bool batch where parse and apply are entirely mechanical. */ +#define DEFINE_APPLY_EXEC_TRISTATE_BOOL(field, JsonName) \ + static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \ + assert(c); \ + assert(p); \ + if (p->field < 0) \ + return 0; \ + c->field = p->field; \ + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, JsonName, \ + JsonName "=%s", yes_no(p->field)); \ + return 0; \ + } + +DEFINE_APPLY_EXEC_TRISTATE_BOOL(dynamic_user, "DynamicUser"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(ignore_sigpipe, "IgnoreSIGPIPE"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(lock_personality, "LockPersonality"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(memory_deny_write_execute, "MemoryDenyWriteExecute"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(no_new_privileges, "NoNewPrivileges"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(remove_ipc, "RemoveIPC"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(restrict_realtime, "RestrictRealtime"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(restrict_suid_sgid, "RestrictSUIDSGID"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(root_ephemeral, "RootEphemeral"); + +/* Per-property descriptor for fields of the Exec context. + * + * json_name - JSON key inside the Exec object (e.g. "Nice"). + * err_field - Qualified field name used for varlink error replies (e.g. "Exec.Nice"). + * json_type - Expected JSON type for sd_json_dispatch_full(). + * dispatch - Parses the JSON value into TransientExecContextParameters. + * parse_offset - offsetof() into TransientExecContextParameters; 0 if dispatch writes via &p directly. + * json_flags - Per-field flags forwarded to sd_json_dispatch_full(). + * apply - Writes the parsed value to ExecContext. Returns 0 on success (whether or not + * the property was set), <0 on error. -EINVAL is mapped to err_field by the + * central loop. + * tristate - If true, the int at parse_offset is initialized to -1 (absent sentinel) by + * transient_exec_context_parameters_init(). Defaults to false. + */ +typedef struct TransientExecProperty { + const char *json_name; + const char *err_field; + sd_json_variant_type_t json_type; + sd_json_dispatch_callback_t dispatch; + size_t parse_offset; + sd_json_dispatch_flags_t json_flags; + int (*apply)(Unit *u, ExecContext *c, TransientExecContextParameters *p); + bool tristate; +} TransientExecProperty; + +/* Property descriptors for the Exec object. Ordered to match src/shared/varlink-io.systemd.Unit.c so related + * fields (e.g. User/Group) stay close. JSON keys match the D-Bus property names. */ +#define EXEC_PROPERTY(json, type, dispatch_fn, parse_offset, json_flags, apply_fn) \ + { json, "Exec." json, type, dispatch_fn, parse_offset, json_flags, apply_fn } + +/* Tristate-bool property: parsed via sd_json_dispatch_tristate into an int (-1 = absent). + * Sets .tristate=true so transient_exec_context_parameters_init() seeds the int to -1 before + * dispatch. */ +#define EXEC_PROPERTY_TRISTATE_BOOL(json, field) \ + { json, "Exec." json, SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, \ + offsetof(TransientExecContextParameters, field), 0, apply_exec_##field, true } + +/* String property: dispatched into a const char* field and applied via the matching + * DEFINE_APPLY_EXEC_STRING() function. dispatch_fn/json_flags vary per field (e.g. User/Group use + * json_dispatch_const_user_group_name with SD_JSON_RELAX). */ +#define EXEC_PROPERTY_STRING(json, field, dispatch_fn, json_flags) \ + { json, "Exec." json, SD_JSON_VARIANT_STRING, dispatch_fn, \ + offsetof(TransientExecContextParameters, field), json_flags, apply_exec_##field } + +static const TransientExecProperty exec_properties[] = { + EXEC_PROPERTY ("WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0, apply_exec_working_directory), + EXEC_PROPERTY_TRISTATE_BOOL("RootEphemeral", root_ephemeral), + EXEC_PROPERTY_STRING ("User", user, json_dispatch_const_user_group_name, SD_JSON_RELAX), + EXEC_PROPERTY_STRING ("Group", group, json_dispatch_const_user_group_name, SD_JSON_RELAX), + EXEC_PROPERTY_TRISTATE_BOOL("DynamicUser", dynamic_user), + EXEC_PROPERTY ("SupplementaryGroups", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecContextParameters, supplementary_groups), 0, apply_exec_supplementary_groups), + EXEC_PROPERTY_TRISTATE_BOOL("NoNewPrivileges", no_new_privileges), + EXEC_PROPERTY ("UMask", SD_JSON_VARIANT_INTEGER, dispatch_transient_umask, 0, 0, apply_exec_umask), + EXEC_PROPERTY ("OOMScoreAdjust", SD_JSON_VARIANT_INTEGER, dispatch_transient_oom_score_adjust, 0, 0, apply_exec_oom_score_adjust), + EXEC_PROPERTY_TRISTATE_BOOL("IgnoreSIGPIPE", ignore_sigpipe), + EXEC_PROPERTY ("Nice", SD_JSON_VARIANT_INTEGER, dispatch_transient_nice, 0, 0, apply_exec_nice), + EXEC_PROPERTY_TRISTATE_BOOL("LockPersonality", lock_personality), + EXEC_PROPERTY_TRISTATE_BOOL("MemoryDenyWriteExecute", memory_deny_write_execute), + EXEC_PROPERTY_TRISTATE_BOOL("RestrictRealtime", restrict_realtime), + EXEC_PROPERTY_TRISTATE_BOOL("RestrictSUIDSGID", restrict_suid_sgid), + EXEC_PROPERTY_TRISTATE_BOOL("RemoveIPC", remove_ipc), + EXEC_PROPERTY ("Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0, apply_exec_environment), + EXEC_PROPERTY ("SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0, apply_exec_set_credential), + EXEC_PROPERTY ("SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0, apply_exec_set_credential_encrypted), +}; +#undef EXEC_PROPERTY +#undef EXEC_PROPERTY_TRISTATE_BOOL +#undef EXEC_PROPERTY_STRING + +/* Tristate-bool fields default to -1 (= absent). Default-zeroed slots would look "explicitly false" + * to the apply path. Initialize all tristate fields here so adding a new tristate property only + * requires a new exec_properties[] row, never a fresh designated initializer at the call site. + * The .tristate descriptor flag drives the loop -- mirrors the .cleanup hook used on the Service + * side, and avoids fragile dispatch-function-pointer equality checks. */ +static void transient_exec_context_parameters_init(TransientExecContextParameters *p) { + assert(p); + FOREACH_ELEMENT(prop, exec_properties) + if (prop->tristate) + *(int*) ((uint8_t*) p + prop->parse_offset) = -1; +} + +static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + /* Build dispatch table only once, its constant. */ + static sd_json_dispatch_field exec_dispatch[ELEMENTSOF(exec_properties) + 1] = {}; + static bool exec_dispatch_set = false; + + StartTransientContextParameters *p = ASSERT_PTR(userdata); + + if (!exec_dispatch_set) { + FOREACH_ELEMENT(prop, exec_properties) + exec_dispatch[prop - exec_properties] = (sd_json_dispatch_field) { + .name = prop->json_name, + .type = prop->json_type, + .callback = prop->dispatch, + .offset = prop->parse_offset, + .flags = prop->json_flags, + }; + exec_dispatch_set = true; + } + + p->exec.present = true; + return sd_json_dispatch_full(variant, exec_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->exec, &p->bad_exec_field); +} + +static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p, const char **reterr_field) { int r; assert(u); assert(c); assert(p); - if (p->working_directory_set) { - TransientWorkingDirectory *wd = &p->working_directory; - _cleanup_free_ char *simplified = NULL; - - if (wd->home && wd->path) { + FOREACH_ELEMENT(prop, exec_properties) { + r = prop->apply(u, c, p); + if (r < 0) { if (reterr_field) - *reterr_field = "Exec.WorkingDirectory"; - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "WorkingDirectory: 'home' and 'path' are mutually exclusive"); + *reterr_field = r == -EINVAL ? prop->err_field : NULL; + return r; } - if (!wd->home && !wd->path) { - if (reterr_field) - *reterr_field = "Exec.WorkingDirectory"; - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "WorkingDirectory: must specify either 'home' or 'path'"); - } - - if (!wd->home) { - if (!path_is_absolute(wd->path)) { - if (reterr_field) - *reterr_field = "Exec.WorkingDirectory"; - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "WorkingDirectory: expects an absolute path"); - } - r = path_simplify_alloc(wd->path, &simplified); - if (r < 0) - return r; - if (!path_is_normalized(simplified)) { - if (reterr_field) - *reterr_field = "Exec.WorkingDirectory"; - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "WorkingDirectory: expects a normalized path"); - } - } - - free_and_replace(c->working_directory, simplified); - c->working_directory_home = wd->home; - c->working_directory_missing_ok = wd->missing_ok; - - unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "WorkingDirectory", - "WorkingDirectory=%s%s", - c->working_directory_missing_ok ? "-" : "", - c->working_directory_home ? "~" : strempty(c->working_directory)); - } - - if (p->environment_set) { - r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE); - if (IN_SET(r, -E2BIG, -EINVAL)) { - if (reterr_field) - *reterr_field = "Exec.Environment"; - /* Convert E2BIG into EINVAL to ensure upper layers get the right error context */ - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - r == -E2BIG ? "Too many environment assignments." : "Invalid Environment list."); - } - if (r < 0) - return r; - } - - if (p->set_credentials_set) { - r = transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false); - if (r == -EINVAL && reterr_field) - *reterr_field = "Exec.SetCredential"; - if (r < 0) - return r; - } - - if (p->set_credentials_encrypted_set) { - r = transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true); - if (r == -EINVAL && reterr_field) - *reterr_field = "Exec.SetCredentialEncrypted"; - if (r < 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 (!valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)) { - if (reterr_field) - *reterr_field = "Exec.SupplementaryGroups"; - 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)) { - if (reterr_field) - *reterr_field = "Exec.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; @@ -1214,14 +1426,7 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters }; _cleanup_(sd_bus_error_free) sd_bus_error bus_error = SD_BUS_ERROR_NULL; - _cleanup_(start_transient_parameters_done) StartTransientParameters p = { - .mode = JOB_REPLACE, - .notify_job_changes = -1, - .notify_unit_changes = -1, - .context.service.type = _SERVICE_TYPE_INVALID, - .context.service.remain_after_exit = -1, - .context.exec.nice = INT_MAX, - }; + _cleanup_(start_transient_parameters_done) StartTransientParameters p = {}; Manager *manager = ASSERT_PTR(userdata); const char *bad_field = NULL; Unit *u; @@ -1230,6 +1435,8 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters assert(link); assert(parameters); + start_transient_parameters_init(&p); + r = mac_selinux_access_check_varlink(link, "start"); if (r < 0) return r; @@ -1280,17 +1487,21 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters if (r < 0) return sd_varlink_error_errno(link, r); - /* Apply exec-specific properties from context.Exec */ - ExecContext *c = unit_get_exec_context(u); - if (c) { + /* Apply context.Exec only when an Exec object was actually sent. The tristate bool fields are + * seeded to -1 ("absent") during Exec dispatch; without an Exec object they stay 0, which the + * apply functions would misread as "explicitly false" and write out bogus FooBar=no lines. */ + if (p.context.exec.present) { + ExecContext *c = unit_get_exec_context(u); + if (!c) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + bad_field = NULL; r = transient_exec_context_apply_properties(u, c, &p.context.exec, &bad_field); if (r == -EINVAL) return sd_varlink_error_invalid_parameter_name(link, bad_field ?: "Exec"); if (r < 0) return sd_varlink_error_errno(link, r); - } else if (p.context.exec.present) - return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + } /* Apply service-specific properties from context.Service */ Service *s = SERVICE(u); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh index cd3139c84a..4c237fd6cb 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh @@ -239,6 +239,20 @@ systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" 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 +# Exec.OOMScoreAdjust, Exec.UMask, Exec.NoNewPrivileges, Exec.MemoryDenyWriteExecute +# (int+validator, mode_t, two tristate-bool shapes) +defer_transient_cleanup varlink-transient-procctl.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-procctl.service","Exec":{"OOMScoreAdjust":250,"UMask":18,"NoNewPrivileges":true,"MemoryDenyWriteExecute":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Exec.OOMScoreAdjust == 250' +echo "$result" | jq -e '.context.Exec.UMask == 18' +echo "$result" | jq -e '.context.Exec.NoNewPrivileges == true' +timeout 30 bash -c 'until systemctl is-active varlink-transient-procctl.service; do sleep 0.5; done' +systemctl show -P OOMScoreAdjust varlink-transient-procctl.service | grep '^250$' >/dev/null +systemctl show -P UMask varlink-transient-procctl.service | grep '^0022$' >/dev/null +systemctl show -P NoNewPrivileges varlink-transient-procctl.service | grep '^yes$' >/dev/null +systemctl show -P MemoryDenyWriteExecute varlink-transient-procctl.service | grep '^yes$' >/dev/null + # Error cases: verify specific varlink error types set +o pipefail varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ @@ -284,6 +298,11 @@ defer_transient_cleanup varlink-transient-bad-nice.service expect_invalid_parameter \ '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ "Exec.Nice" +# Out-of-range OOMScoreAdjust= value is rejected +defer_transient_cleanup varlink-transient-bad-oom.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-oom.service","Exec":{"OOMScoreAdjust":9999},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.OOMScoreAdjust" # Invalid credential ID defer_transient_cleanup varlink-transient-bad-cred-id.service expect_invalid_parameter \