]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add absolute-path properties to varlink StartTransient
authorMichael Vogt <michael@amutable.com>
Thu, 21 May 2026 14:57:15 +0000 (16:57 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 1 Jul 2026 14:14:37 +0000 (16:14 +0200)
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.

src/core/varlink-unit.c
test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh

index a3efed436da4228fda94e18feaf067cd80d3dcf4..c95dc8c45214c17a488d7f4437f727f2e8ebef65 100644 (file)
@@ -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=<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").
@@ -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
index 4c237fd6cb798370cbcbd101d4e2ed2b69672799..fc7d16b199c31c8e88b51acf24a8ccd83824aa3c 100755 (executable)
@@ -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