diff --git a/exp/api/remote/genproto/v2/symbols.go b/exp/api/remote/genproto/v2/symbols.go index 5e7494656..8d297ab45 100644 --- a/exp/api/remote/genproto/v2/symbols.go +++ b/exp/api/remote/genproto/v2/symbols.go @@ -30,6 +30,8 @@ package writev2 +import "fmt" + // SymbolsTable implements table for easy symbol use. type SymbolsTable struct { strings []string @@ -88,10 +90,21 @@ func (t *SymbolsTable) Reset() { } // DesymbolizeLabels decodes label references, with given symbols to labels. -func DesymbolizeLabels(labelRefs []uint32, symbols, buf []string) []string { +// +// The references are decoded from a remote-write request, so they are not +// trusted: an odd number of references, or a reference outside the symbols +// table, is reported as an error rather than panicking. +func DesymbolizeLabels(labelRefs []uint32, symbols, buf []string) ([]string, error) { + if len(labelRefs)%2 != 0 { + return nil, fmt.Errorf("invalid labelRefs length %v, must be even", len(labelRefs)) + } result := buf[:0] for i := 0; i < len(labelRefs); i += 2 { - result = append(result, symbols[labelRefs[i]], symbols[labelRefs[i+1]]) + nameRef, valueRef := labelRefs[i], labelRefs[i+1] + if int(nameRef) >= len(symbols) || int(valueRef) >= len(symbols) { + return nil, fmt.Errorf("labelRefs %v (name), %v (value) outside of symbols table (size %v)", nameRef, valueRef, len(symbols)) + } + result = append(result, symbols[nameRef], symbols[valueRef]) } - return result + return result, nil } diff --git a/exp/api/remote/genproto/v2/symbols_test.go b/exp/api/remote/genproto/v2/symbols_test.go index 161cdbddb..2c956d37f 100644 --- a/exp/api/remote/genproto/v2/symbols_test.go +++ b/exp/api/remote/genproto/v2/symbols_test.go @@ -70,7 +70,10 @@ func TestSymbolsTable(t *testing.T) { ls := []string{"__name__", "qwer", "zxcv", "1234"} encoded := s.SymbolizeLabels(ls, nil) requireEqual(t, []uint32{1, 3, 4, 5}, encoded) - decoded := DesymbolizeLabels(encoded, s.Symbols(), nil) + decoded, err := DesymbolizeLabels(encoded, s.Symbols(), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } requireEqual(t, ls, decoded) // Different buf. @@ -78,3 +81,30 @@ func TestSymbolsTable(t *testing.T) { encoded = s.SymbolizeLabels(ls, []uint32{1, 3, 4, 5}) requireEqual(t, []uint32{1, 3, 6, 5}, encoded) } + +func TestDesymbolizeLabelsInvalidInput(t *testing.T) { + // Label references come from a remote-write request, so malformed input has + // to be reported as an error rather than panicking the receiver. + for _, tcase := range []struct { + name string + labelRefs []uint32 + symbols []string + }{ + {name: "odd number of refs", labelRefs: []uint32{1}, symbols: []string{"", "a"}}, + {name: "odd number of refs, longer", labelRefs: []uint32{1, 1, 1}, symbols: []string{"", "a"}}, + {name: "name ref out of range", labelRefs: []uint32{9, 1}, symbols: []string{"", "a"}}, + {name: "value ref out of range", labelRefs: []uint32{1, 9}, symbols: []string{"", "a"}}, + {name: "refs against empty symbols", labelRefs: []uint32{0, 0}, symbols: nil}, + } { + t.Run(tcase.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("unexpected panic: %v", r) + } + }() + if _, err := DesymbolizeLabels(tcase.labelRefs, tcase.symbols, nil); err == nil { + t.Fatal("expected an error, got none") + } + }) + } +}