diff --git a/pkg/metric/metric.go b/pkg/metric/metric.go index a6303fa2b..30ba8ea47 100644 --- a/pkg/metric/metric.go +++ b/pkg/metric/metric.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "math" + re "regexp" "sort" "strings" "time" @@ -486,6 +487,20 @@ func nameToPrometheusName(name string) string { return strings.ReplaceAll(strings.TrimPrefix(name, "/"), "/", "_") } +var validMetricNameRegexp = re.MustCompile("^(?:/[_\\w]+)+$") + +// verifyName verifies that the given metric name is a valid path-style metric +// name. +func verifyName(name string) error { + if !strings.HasPrefix(name, "/") { + return fmt.Errorf("metric name must start with a '/': %q", name) + } + if !validMetricNameRegexp.MatchString(name) { + return fmt.Errorf("invalid metric name: %q", name) + } + return nil +} + // RegisterCustomUint64Metric registers a metric with the given name. // // Register must only be called at init and will return and error if called @@ -555,6 +570,9 @@ func MustRegisterCustomUint64Metric(name string, cumulative, sync bool, descript // // Metrics must be statically defined (i.e., at init). func NewUint64Metric(name string, sync bool, units pb.MetricMetadata_Units, description string, fields ...Field) (*Uint64Metric, error) { + if err := verifyName(name); err != nil { + return nil, err + } f, err := newFieldMapper(fields...) if err != nil { return nil, err @@ -832,6 +850,9 @@ type DistributionMetric struct { // NewDistributionMetric creates and registers a new distribution metric. func NewDistributionMetric(name string, sync bool, bucketer Bucketer, unit pb.MetricMetadata_Units, description string, fields ...Field) (*DistributionMetric, error) { + if err := verifyName(name); err != nil { + return nil, err + } if initialized.Load() { return nil, ErrInitializationDone } diff --git a/pkg/metric/metric_test.go b/pkg/metric/metric_test.go index 5dc58763b..ce4426720 100644 --- a/pkg/metric/metric_test.go +++ b/pkg/metric/metric_test.go @@ -66,6 +66,32 @@ func verifyPrometheusParsing(t *testing.T) { } } +func TestVerifyName(t *testing.T) { + for name, wantErr := range map[string]bool{ + "": true, + "/": true, + "/foo": false, + "/foo/bar": false, + "/foo/bar/baz": false, + "/foo/bar/bar": false, + "/foo//bar/baz": true, + "//foo/bar": true, + "//": true, + "foo": true, + "foo/bar": true, + "/foo-bar": true, + "/foo bar": true, + "/foo_bar": false, + } { + t.Run(name, func(t *testing.T) { + err := verifyName(name) + if gotErr := (err != nil); gotErr != wantErr { + t.Errorf("verifyName(%q) got err=%v wantErr=%v", name, err, wantErr) + } + }) + } +} + func TestInitialize(t *testing.T) { defer resetTest() diff --git a/pkg/sentry/kernel/syscalls.go b/pkg/sentry/kernel/syscalls.go index c0582e67b..eb3957c67 100644 --- a/pkg/sentry/kernel/syscalls.go +++ b/pkg/sentry/kernel/syscalls.go @@ -402,7 +402,7 @@ func RegisterSyscallTable(s *SyscallTable) { unimplementedSyscallNumbers[i] = []*metric.FieldValue{s} } allowedValues[len(allowedValues)-1] = outOfRangeSyscallNumber[0] - unimplementedSyscallCounter = metric.MustCreateNewUint64Metric("unimplemented_syscalls", true, "Number of times the application tried to call an unimplemented syscall, broken down by syscall number", metric.NewField("sysno", allowedValues...)) + unimplementedSyscallCounter = metric.MustCreateNewUint64Metric("/unimplemented_syscalls", true, "Number of times the application tried to call an unimplemented syscall, broken down by syscall number", metric.NewField("sysno", allowedValues...)) }) s.Init() }