]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
report: tighten rules on metrics names
authorLennart Poettering <lennart@amutable.com>
Wed, 18 Feb 2026 13:43:07 +0000 (14:43 +0100)
committerLennart Poettering <lennart@amutable.com>
Fri, 20 Feb 2026 07:25:12 +0000 (08:25 +0100)
Let's stay close to Varlink's naming rules and insist that metrics
prefixes must be valid varlink interface names, and suffixes are valid
varlink field names.

The former rule is clear: because a metric <x>.<y> can only be provided
by a varlink service <x>, it is obvious we should validate them the
same way. Validating the suffix via varlink field rules is not that
obvious, but I think it makes sense to stay close to Varlink naming
rules if we already started out at one place.

src/report/report.c

index 12a815814cb96ea0a75a076d7f65a2aa68aac073..562fe6653da8fec2bdb6b0081d9067921a9c3432 100644 (file)
@@ -23,6 +23,7 @@
 #include "string-util.h"
 #include "strv.h"
 #include "time-util.h"
+#include "varlink-idl-util.h"
 #include "verbs.h"
 
 #define METRICS_MAX 1024U
@@ -112,6 +113,29 @@ static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b)
         return strcmp_ptr(fields_str_a, fields_str_b);
 }
 
+static int metrics_name_valid(const char *metric_name) {
+
+        /* Validates a metrics family name. Since the prefix shall match the Varlink service name, we'll
+         * enforce Varlink interface naming rules on it. Given how close we are to Varlink let's also enforce
+         * rules on metrics names similar to those of Varlink field names. */
+
+        const char *e = strrchr(metric_name, '.');
+        if (!e)
+                return false;
+
+        _cleanup_free_ char *j = strndup(metric_name, e - metric_name);
+        if (!j)
+                return -ENOMEM;
+
+        if (!varlink_idl_interface_name_is_valid(j))
+                return false;
+
+        if (!varlink_idl_field_name_is_valid(e+1))
+                return false;
+
+        return true;
+}
+
 static bool metric_startswith_prefix(const char *metric_name, const char *prefix) {
         if (isempty(metric_name) || isempty(prefix))
                 return false;
@@ -141,6 +165,16 @@ static bool metrics_validate_one(LinkInfo *li, sd_json_variant *metric) {
                 return false;
         }
 
+        r = metrics_name_valid(metric_name);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to determine if '%s' is a valid metric name: %m", metric_name);
+                return false;
+        }
+        if (!r) {
+                log_debug("Metric name '%s' is not valid, skipping.", metric_name);
+                return false;
+        }
+
         return metric_startswith_prefix(metric_name, li->metric_prefix);
 }
 
@@ -434,13 +468,16 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) {
         else if (r < 0)
                 return log_error_errno(r, "Failed to enumerate '%s': %m", sources_path);
         else {
-                /* Filter out non-sockets/non-symlinks entries */
+                /* Filter out non-sockets/non-symlinks and badly named entries */
                 FOREACH_ARRAY(i, de->entries, de->n_entries) {
                         struct dirent *d = *i;
 
                         if (!IN_SET(d->d_type, DT_SOCK, DT_LNK))
                                 continue;
 
+                        if (!varlink_idl_interface_name_is_valid(d->d_name))
+                                continue;
+
                         de->entries[m++] = *i;
                 }