From: Michael Vogt Date: Thu, 21 May 2026 14:57:15 +0000 (+0200) Subject: core: add absolute-path properties to varlink StartTransient X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ab344eb750f5c5e37c906528278d9be9db6d340d;p=thirdparty%2Fsystemd.git core: add absolute-path properties to varlink StartTransient Similar to PR#42360 this commit adds missing properties for absolute path handling in io.systemd.Unit.StartTransient for the `Exec` context and a macro helper to share the common code. The properties added are: IPCNamespacePath, NetworkNamespacePath, RootDirectory, RootHashPath, RootHashSignaturePath, RootImage, RootVerity, UserNamespacePath, RootMStack. Note that RootHashPath, RootHashSignaturePath need custom apply functions because the varlink name "RootHashPath" differs from the name that needs to be written into the unit file ("RootHash=") and the iovec must be cleared. --- diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index a3efed436da..c95dc8c4521 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -691,6 +691,16 @@ typedef struct TransientExecContextParameters { const char *user; const char *group; char **supplementary_groups; + /* Absolute-path strings: must validate via path_is_absolute(). */ + const char *ipc_namespace_path; + const char *network_namespace_path; + const char *root_directory; + const char *root_hash_path; + const char *root_hash_sig_path; + const char *root_image; + const char *root_mstack; + const char *root_verity; + const char *user_namespace_path; bool nice_set; int nice; bool oom_score_adjust_set; @@ -1210,6 +1220,83 @@ 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"); +/* Generate an apply fn for an exec-context absolute-path string: validate via path_is_absolute(), + * copy into ExecContext, write "Name=". Mirrors D-Bus bus_set_transient_path. */ +#define DEFINE_APPLY_EXEC_ABSOLUTE_PATH(field, JsonName) \ + static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \ + int r; \ + assert(c); \ + assert(p); \ + if (!p->field) \ + return 0; \ + if (!path_is_absolute(p->field)) \ + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), \ + JsonName ": expects an absolute path"); \ + 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_ABSOLUTE_PATH(ipc_namespace_path, "IPCNamespacePath"); +DEFINE_APPLY_EXEC_ABSOLUTE_PATH(network_namespace_path, "NetworkNamespacePath"); +DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_directory, "RootDirectory"); +DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_image, "RootImage"); +DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_mstack, "RootMStack"); +DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_verity, "RootVerity"); +DEFINE_APPLY_EXEC_ABSOLUTE_PATH(user_namespace_path, "UserNamespacePath"); + +/* RootHashPath and RootHashSignaturePath need custom apply fns: the corresponding unit-file + * directive is "RootHash="/"RootHashSignature=" (not the JSON name), and setting the path form + * must clear the byte-buffer (iovec) form to match the D-Bus semantics. */ +static int apply_exec_root_hash_path(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + int r; + + assert(c); + assert(p); + + if (!p->root_hash_path) + return 0; + + if (!path_is_absolute(p->root_hash_path)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "RootHashPath: expects an absolute path"); + + r = free_and_strdup(&c->root_hash_path, p->root_hash_path); + if (r < 0) + return r; + iovec_done(&c->root_hash); + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "RootHash", + "RootHash=%s", p->root_hash_path); + return 0; +} + +static int apply_exec_root_hash_sig_path(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + int r; + + assert(c); + assert(p); + + if (!p->root_hash_sig_path) + return 0; + + if (!path_is_absolute(p->root_hash_sig_path)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "RootHashSignaturePath: expects an absolute path"); + + r = free_and_strdup(&c->root_hash_sig_path, p->root_hash_sig_path); + if (r < 0) + return r; + iovec_done(&c->root_hash_sig); + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "RootHashSignature", + "RootHashSignature=%s", p->root_hash_sig_path); + return 0; +} + /* Per-property descriptor for fields of the Exec context. * * json_name - JSON key inside the Exec object (e.g. "Nice"). @@ -1254,9 +1341,20 @@ typedef struct TransientExecProperty { { json, "Exec." json, SD_JSON_VARIANT_STRING, dispatch_fn, \ offsetof(TransientExecContextParameters, field), json_flags, apply_exec_##field } +/* Absolute-path string property: parsed via sd_json_dispatch_const_string + path_is_absolute(). */ +#define EXEC_PROPERTY_ABSOLUTE_PATH(json, field) \ + { json, "Exec." json, SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, \ + offsetof(TransientExecContextParameters, field), 0, 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_ABSOLUTE_PATH("RootDirectory", root_directory), + EXEC_PROPERTY_ABSOLUTE_PATH("RootImage", root_image), + EXEC_PROPERTY_ABSOLUTE_PATH("RootMStack", root_mstack), EXEC_PROPERTY_TRISTATE_BOOL("RootEphemeral", root_ephemeral), + EXEC_PROPERTY ("RootHashPath", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecContextParameters, root_hash_path), 0, apply_exec_root_hash_path), + EXEC_PROPERTY ("RootHashSignaturePath", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecContextParameters, root_hash_sig_path), 0, apply_exec_root_hash_sig_path), + EXEC_PROPERTY_ABSOLUTE_PATH("RootVerity", root_verity), 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), @@ -1266,6 +1364,9 @@ static const TransientExecProperty exec_properties[] = { 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_ABSOLUTE_PATH("NetworkNamespacePath", network_namespace_path), + EXEC_PROPERTY_ABSOLUTE_PATH("IPCNamespacePath", ipc_namespace_path), + EXEC_PROPERTY_ABSOLUTE_PATH("UserNamespacePath", user_namespace_path), EXEC_PROPERTY_TRISTATE_BOOL("LockPersonality", lock_personality), EXEC_PROPERTY_TRISTATE_BOOL("MemoryDenyWriteExecute", memory_deny_write_execute), EXEC_PROPERTY_TRISTATE_BOOL("RestrictRealtime", restrict_realtime), @@ -1278,6 +1379,7 @@ static const TransientExecProperty exec_properties[] = { #undef EXEC_PROPERTY #undef EXEC_PROPERTY_TRISTATE_BOOL #undef EXEC_PROPERTY_STRING +#undef EXEC_PROPERTY_ABSOLUTE_PATH /* 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 diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh index 4c237fd6cb7..fc7d16b199c 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh @@ -253,6 +253,17 @@ systemctl show -P UMask varlink-transient-procctl.service | grep '^0022$' >/dev/ systemctl show -P NoNewPrivileges varlink-transient-procctl.service | grep '^yes$' >/dev/null systemctl show -P MemoryDenyWriteExecute varlink-transient-procctl.service | grep '^yes$' >/dev/null +# Exec.RootHashPath / Exec.RootHashSignaturePath: the unit-file directive must be +# "RootHash=" / "RootHashSignature=" (not the JSON name), otherwise the next daemon-reload +# would drop the setting as an unknown key. +defer_transient_cleanup varlink-transient-roothash.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-roothash.service","Exec":{"RootHashPath":"/etc/hostname","RootHashSignaturePath":"/etc/machine-id"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' >/dev/null +fragment=$(systemctl show -P FragmentPath varlink-transient-roothash.service) +test -n "$fragment" +grep '^RootHash=/etc/hostname$' "$fragment" >/dev/null +grep '^RootHashSignature=/etc/machine-id$' "$fragment" >/dev/null + # Error cases: verify specific varlink error types set +o pipefail varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ @@ -303,6 +314,11 @@ 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" +# Relative RootDirectory path is rejected +defer_transient_cleanup varlink-transient-bad-rd.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-rd.service","Exec":{"RootDirectory":"relative/path"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.RootDirectory" # Invalid credential ID defer_transient_cleanup varlink-transient-bad-cred-id.service expect_invalid_parameter \ @@ -319,9 +335,9 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ # Unknown field in Exec is rejected as PropertyNotSupported defer_transient_cleanup varlink-transient-unknown-exec.service unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) + '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"AmbientCapabilities":["cap_net_raw"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) echo "$unsupported_exec" | grep "io.systemd.Unit.PropertyNotSupported" -echo "$unsupported_exec" | grep "Exec.RootDirectory" +echo "$unsupported_exec" | grep "Exec.AmbientCapabilities" # Service field declared in the IDL but not yet settable at creation is rejected as PropertyNotSupported, # and the offending sub-property is identified defer_transient_cleanup varlink-transient-unknown-service.service