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;
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=<path>". 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").
{ 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),
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),
#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
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 \
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 \
# 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