Skip to content

Commit 23bef0f

Browse files
committed
prevent panic caused by accessing unexported fields
This PR fixes a panic caused by predicate trying to access unexported fields without properly checking if the field is exported. Instead of accessing the fields by calling `.Interface()`, we always use the reflect.Value and only call `Interface()` when we reached out to the last key. Signed-off-by: Tiago Silva <tiago.silva@goteleport.com>
1 parent 93c5cee commit 23bef0f

File tree

2 files changed

+40
-6
lines changed

2 files changed

+40
-6
lines changed

lib.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func Not(a BoolPredicate) BoolPredicate {
131131

132132
// GetFieldByTag returns a field from the object based on the tag.
133133
func GetFieldByTag(ival interface{}, tagName string, fieldNames []string) (interface{}, error) {
134-
i, err := getFieldByTag(ival, tagName, fieldNames)
134+
i, err := getFieldByTag(reflect.ValueOf(ival), tagName, fieldNames)
135135
if err == nil {
136136
return i, nil
137137
}
@@ -161,12 +161,11 @@ func (n notFoundError) Error() string {
161161
return fmt.Sprintf("field name %v is not found", strings.Join(n.fieldNames, "."))
162162
}
163163

164-
func getFieldByTag(ival interface{}, tagName string, fieldNames []string) (interface{}, error) {
164+
func getFieldByTag(val reflect.Value, tagName string, fieldNames []string) (interface{}, error) {
165165
if len(fieldNames) == 0 {
166166
return nil, trace.BadParameter("missing field names")
167167
}
168168

169-
val := reflect.ValueOf(ival)
170169
if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr {
171170
val = val.Elem()
172171
}
@@ -183,7 +182,7 @@ func getFieldByTag(ival interface{}, tagName string, fieldNames []string) (inter
183182

184183
// If it's an embedded field, traverse it.
185184
if tagValue == "" && valType.Field(i).Anonymous {
186-
value := val.Field(i).Interface()
185+
value := val.Field(i)
187186
val, err := getFieldByTag(value, tagName, fieldNames)
188187
if err == nil {
189188
return val, nil
@@ -192,9 +191,12 @@ func getFieldByTag(ival interface{}, tagName string, fieldNames []string) (inter
192191

193192
parts := strings.Split(tagValue, ",")
194193
if parts[0] == fieldName {
195-
value := val.Field(i).Interface()
194+
value := val.Field(i)
196195
if len(rest) == 0 {
197-
return value, nil
196+
if value.CanInterface() {
197+
return value.Interface(), nil
198+
}
199+
return nil, trace.BadParameter("field %v is not accessible", fieldName)
198200
}
199201

200202
return getFieldByTag(value, tagName, rest)

parse_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,38 @@ func (s *PredicateSuite) TestContains() {
408408
s.False(pr.(BoolPredicate)())
409409
}
410410

411+
func (s *PredicateSuite) TestContainsUnexportedFieldAvoidPanic() {
412+
type embedTestStruct struct {
413+
Param struct {
414+
Key1 map[string][]string `json:"key1,omitempty"`
415+
Key2 map[string]string `json:"key2,omitempty"`
416+
} `json:"param,omitempty"`
417+
}
418+
type LocalTestStruct struct {
419+
embedTestStruct
420+
}
421+
val := LocalTestStruct{
422+
embedTestStruct: embedTestStruct{
423+
Param: struct {
424+
Key1 map[string][]string "json:\"key1,omitempty\""
425+
Key2 map[string]string "json:\"key2,omitempty\""
426+
}{
427+
Key1: map[string][]string{"key": {"a", "b", "c"}},
428+
},
429+
},
430+
}
431+
432+
getID := func(fields []string) (interface{}, error) {
433+
return GetFieldByTag(val, "json", fields[1:])
434+
}
435+
p := s.getParserWithOpts(getID, GetStringMapValue)
436+
437+
pr, err := p.Parse(`Contains(val.param.key1["key"], "a")`)
438+
s.NoError(err)
439+
s.True(pr.(BoolPredicate)())
440+
441+
}
442+
411443
func (s *PredicateSuite) TestEquals() {
412444
val := TestStruct{}
413445
val.Param.Key2 = map[string]string{"key": "a"}

0 commit comments

Comments
 (0)