Skip to content

Commit fa6048b

Browse files
committed
Make ParseKey validate user input
1 parent 7c8e7b0 commit fa6048b

File tree

12 files changed

+402
-308
lines changed

12 files changed

+402
-308
lines changed

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,16 @@ require (
3939
github.com/onsi/gomega v1.12.0
4040
github.com/pelletier/go-toml v1.8.1 // indirect
4141
github.com/peterbourgon/ff/v3 v3.0.0
42-
github.com/prometheus/client_golang v1.10.0
42+
github.com/prometheus/client_golang v1.11.0
43+
github.com/prometheus/common v0.29.0
4344
github.com/pyroscope-io/dotnetdiag v1.2.1
4445
github.com/rivo/uniseg v0.2.0 // indirect
4546
github.com/shirou/gopsutil v3.21.4+incompatible
4647
github.com/sirupsen/logrus v1.7.0
4748
github.com/tklauser/go-sysconf v0.3.6 // indirect
4849
github.com/twmb/murmur3 v1.1.5
4950
github.com/wacul/ptr v1.0.0 // indirect
50-
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
51+
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c
5152
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
5253
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
5354
golang.org/x/tools v0.1.0

go.sum

Lines changed: 243 additions & 200 deletions
Large diffs are not rendered by default.

pkg/agent/session.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ func (ps *ProfileSession) createNames(tags map[string]string) error {
109109
if err != nil {
110110
return err
111111
}
112-
tagsCopy["__name__"] = appName + "." + string(t)
112+
appName += "." + string(t)
113+
if err = flameql.ValidateAppName(appName); err != nil {
114+
return err
115+
}
116+
tagsCopy["__name__"] = appName
113117
ps.names[t] = segment.NewKey(tagsCopy).Normalized()
114118
}
115119
return nil

pkg/flameql/error.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,13 @@ func (e *Error) Error() string { return e.Inner.Error() + ": " + e.Expr }
3333
func (e *Error) Unwrap() error { return e.Inner }
3434

3535
func newInvalidTagKeyRuneError(k string, r rune) *Error {
36-
return newErr(ErrInvalidTagKey, fmt.Sprintf("%s: character is not allowed: %q", k, r))
36+
return newInvalidRuneError(ErrInvalidTagKey, k, r)
37+
}
38+
39+
func newInvalidAppNameRuneError(k string, r rune) *Error {
40+
return newInvalidRuneError(ErrInvalidAppName, k, r)
41+
}
42+
43+
func newInvalidRuneError(err error, k string, r rune) *Error {
44+
return newErr(err, fmt.Sprintf("%s: character is not allowed: %q", k, r))
3745
}

pkg/flameql/flameql.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ const (
3131
EQL_REGEX // =~
3232
)
3333

34+
const (
35+
ReservedTagKeyName = "__name__"
36+
)
37+
38+
var reservedTagKeys = []string{
39+
ReservedTagKeyName,
40+
}
41+
3442
// IsNegation reports whether the operator assumes negation.
3543
func (o Op) IsNegation() bool { return o < EQL }
3644

@@ -55,3 +63,52 @@ func (m *TagMatcher) Match(v string) bool {
5563
panic("invalid match operator")
5664
}
5765
}
66+
67+
// ValidateTagKey report an error if the given key k violates constraints.
68+
//
69+
// The function should be used to validate user input. The function returns
70+
// ErrTagKeyReserved if the key is valid but reserved for internal use.
71+
func ValidateTagKey(k string) error {
72+
if len(k) == 0 {
73+
return ErrTagKeyIsRequired
74+
}
75+
for _, r := range k {
76+
if !IsTagKeyRuneAllowed(r) {
77+
return newInvalidTagKeyRuneError(k, r)
78+
}
79+
}
80+
if IsTagKeyReserved(k) {
81+
return newErr(ErrTagKeyReserved, k)
82+
}
83+
return nil
84+
}
85+
86+
// ValidateAppName report an error if the given app name n violates constraints.
87+
func ValidateAppName(n string) error {
88+
if len(n) == 0 {
89+
return ErrAppNameIsRequired
90+
}
91+
for _, r := range n {
92+
if !IsAppNameRuneAllowed(r) {
93+
return newInvalidAppNameRuneError(n, r)
94+
}
95+
}
96+
return nil
97+
}
98+
99+
func IsTagKeyRuneAllowed(r rune) bool {
100+
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_'
101+
}
102+
103+
func IsAppNameRuneAllowed(r rune) bool {
104+
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' || r == '.'
105+
}
106+
107+
func IsTagKeyReserved(k string) bool {
108+
for _, s := range reservedTagKeys {
109+
if s == k {
110+
return true
111+
}
112+
}
113+
return false
114+
}

pkg/flameql/flameql_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package flameql
22

33
import (
4+
"errors"
5+
46
. "github.com/onsi/ginkgo"
57
. "github.com/onsi/gomega"
68
)
@@ -31,3 +33,54 @@ var _ = Describe("TagMatcher", func() {
3133
}
3234
})
3335
})
36+
37+
var _ = Describe("ValidateTagKey", func() {
38+
It("reports error if a key violates constraints", func() {
39+
type testCase struct {
40+
key string
41+
err error
42+
}
43+
44+
testCases := []testCase{
45+
{"foo_BAR_12_baz_qux", nil},
46+
47+
{ReservedTagKeyName, ErrTagKeyReserved},
48+
{"", ErrTagKeyIsRequired},
49+
{"#", ErrInvalidTagKey},
50+
}
51+
52+
for _, tc := range testCases {
53+
err := ValidateTagKey(tc.key)
54+
if tc.err != nil {
55+
Expect(errors.Is(err, tc.err)).To(BeTrue())
56+
continue
57+
}
58+
Expect(err).To(BeNil())
59+
}
60+
})
61+
})
62+
63+
var _ = Describe("ValidateAppName", func() {
64+
It("reports error if an app name violates constraints", func() {
65+
type testCase struct {
66+
appName string
67+
err error
68+
}
69+
70+
testCases := []testCase{
71+
{"foo.BAR-1.2_baz_qux", nil},
72+
73+
{"", ErrAppNameIsRequired},
74+
{"#", ErrInvalidAppName},
75+
}
76+
77+
for _, tc := range testCases {
78+
err := ValidateAppName(tc.appName)
79+
if tc.err != nil {
80+
Expect(errors.Is(err, tc.err)).To(BeTrue())
81+
continue
82+
}
83+
Expect(err).To(BeNil())
84+
}
85+
})
86+
})

pkg/flameql/parse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func ParseQuery(s string) (*Query, error) {
2828
q.Matchers = m
2929
return &q, nil
3030
default:
31-
if !IsTagKeyRuneAllowed(c) {
31+
if !IsAppNameRuneAllowed(c) {
3232
return nil, newErr(ErrInvalidAppName, s[:offset+1])
3333
}
3434
}

pkg/flameql/tag_key.go

Lines changed: 0 additions & 46 deletions
This file was deleted.

pkg/flameql/tag_key_test.go

Lines changed: 0 additions & 34 deletions
This file was deleted.

pkg/storage/query_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,21 +142,21 @@ var _ = Describe("Querying", func() {
142142
dimension.Key("app.name{baz=xxx,foo=bar}"),
143143
dimension.Key("app.name{baz=xxx,waldo=fred}"),
144144
}},
145-
{`app.name{non-existing-key!="bar"}`, []dimension.Key{
145+
{`app.name{non_existing_key!="bar"}`, []dimension.Key{
146146
dimension.Key("app.name{baz=qux,foo=bar}"),
147147
dimension.Key("app.name{baz=xxx,foo=bar}"),
148148
dimension.Key("app.name{baz=xxx,waldo=fred}"),
149149
}},
150-
{`app.name{non-existing-key!~"bar"}`, []dimension.Key{
150+
{`app.name{non_existing_key!~"bar"}`, []dimension.Key{
151151
dimension.Key("app.name{baz=qux,foo=bar}"),
152152
dimension.Key("app.name{baz=xxx,foo=bar}"),
153153
dimension.Key("app.name{baz=xxx,waldo=fred}"),
154154
}},
155155

156156
{`app.name{foo="non-existing-value"}`, nil},
157157
{`app.name{foo=~"non-existing-.*"}`, nil},
158-
{`app.name{non-existing-key="bar"}`, nil},
159-
{`app.name{non-existing-key=~"bar"}`, nil},
158+
{`app.name{non_existing_key="bar"}`, nil},
159+
{`app.name{non_existing_key=~"bar"}`, nil},
160160

161161
{`non-existing-app{}`, nil},
162162
}

pkg/storage/segment/key.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77
"time"
88

9+
"github.com/pyroscope-io/pyroscope/pkg/flameql"
910
"github.com/pyroscope-io/pyroscope/pkg/structs/sortedmap"
1011
)
1112

@@ -28,26 +29,21 @@ const (
2829

2930
func NewKey(labels map[string]string) *Key { return &Key{labels: labels} }
3031

31-
// TODO: should rewrite this at some point to not rely on regular expressions & splits
3232
func ParseKey(name string) (*Key, error) {
33-
k := &Key{
34-
labels: make(map[string]string),
35-
}
36-
37-
p := parser{
38-
parserState: nameParserState,
39-
key: "",
40-
value: "",
41-
}
42-
33+
k := &Key{labels: make(map[string]string)}
34+
p := parser{parserState: nameParserState}
35+
var err error
4336
for _, r := range name + "{" {
4437
switch p.parserState {
4538
case nameParserState:
46-
p.nameParserCase(r, k)
39+
err = p.nameParserCase(r, k)
4740
case tagKeyParserState:
4841
p.tagKeyParserCase(r)
4942
case tagValueParserState:
50-
p.tagValueParserCase(r, k)
43+
err = p.tagValueParserCase(r, k)
44+
}
45+
if err != nil {
46+
return nil, err
5147
}
5248
}
5349
return k, nil
@@ -60,14 +56,19 @@ type parser struct {
6056
}
6157

6258
// ParseKey's nameParserState switch case
63-
func (p *parser) nameParserCase(r int32, k *Key) {
59+
func (p *parser) nameParserCase(r int32, k *Key) error {
6460
switch r {
6561
case '{':
6662
p.parserState = tagKeyParserState
67-
k.labels["__name__"] = strings.TrimSpace(p.value)
63+
appName := strings.TrimSpace(p.value)
64+
if err := flameql.ValidateAppName(appName); err != nil {
65+
return err
66+
}
67+
k.labels["__name__"] = appName
6868
default:
6969
p.value += string(r)
7070
}
71+
return nil
7172
}
7273

7374
// ParseKey's tagKeyParserState switch case
@@ -84,15 +85,22 @@ func (p *parser) tagKeyParserCase(r int32) {
8485
}
8586

8687
// ParseKey's tagValueParserState switch case
87-
func (p *parser) tagValueParserCase(r int32, k *Key) {
88+
func (p *parser) tagValueParserCase(r int32, k *Key) error {
8889
switch r {
8990
case ',', '}':
9091
p.parserState = tagKeyParserState
91-
k.labels[strings.TrimSpace(p.key)] = strings.TrimSpace(p.value)
92+
key := strings.TrimSpace(p.key)
93+
if !flameql.IsTagKeyReserved(key) {
94+
if err := flameql.ValidateTagKey(key); err != nil {
95+
return err
96+
}
97+
}
98+
k.labels[key] = strings.TrimSpace(p.value)
9299
p.key = ""
93100
default:
94101
p.value += string(r)
95102
}
103+
return nil
96104
}
97105

98106
func (k *Key) SegmentKey() string {

0 commit comments

Comments
 (0)