Browse Source

reflect: check for access in the Interface method call

This fixes a type system loophole. The following program would
incorrectly run in TinyGo, while it would trigger a panic in Go:

    package main

    import "reflect"

    func main() {
        v := reflect.ValueOf(struct {
            x int
        }{})
        x := v.Field(0).Interface()
        println("x:", x.(int))
    }

Playground link: https://play.golang.org/p/nvvA18XFqFC

The panic in Go is the following:

    panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

I've shortened it in TinyGo to save a little bit of space.
pull/1743/head
Ayke van Laethem 4 years ago
committed by Ron Evans
parent
commit
f800f7507c
  1. 16
      src/reflect/value.go
  2. 11
      src/runtime/hashmap.go

16
src/reflect/value.go

@ -28,6 +28,13 @@ func (v Value) isIndirect() bool {
return v.flags&valueFlagIndirect != 0
}
// isExported returns whether the value represented by this Value could be
// accessed without violating type system constraints. For example, it is not
// set for unexported struct fields.
func (v Value) isExported() bool {
return v.flags&valueFlagExported != 0
}
func Indirect(v Value) Value {
if v.Kind() != Ptr {
return v
@ -51,6 +58,15 @@ func ValueOf(i interface{}) Value {
}
func (v Value) Interface() interface{} {
if !v.isExported() {
panic("(reflect.Value).Interface: unexported")
}
return valueInterfaceUnsafe(v)
}
// valueInterfaceUnsafe is used by the runtime to hash map keys. It should not
// be subject to the isExported check.
func valueInterfaceUnsafe(v Value) interface{} {
if v.isIndirect() && v.typecode.Size() <= unsafe.Sizeof(uintptr(0)) {
// Value was indirect but must be put back directly in the interface
// value.

11
src/runtime/hashmap.go

@ -349,6 +349,13 @@ func hashmapStringDelete(m *hashmap, key string) {
// Hashmap with interface keys (for everything else).
// This is a method that is intentionally unexported in the reflect package. It
// is identical to the Interface() method call, except it doesn't check whether
// a field is exported and thus allows circumventing the type system.
// The hash function needs it as it also needs to hash unexported struct fields.
//go:linkname valueInterfaceUnsafe reflect.valueInterfaceUnsafe
func valueInterfaceUnsafe(v reflect.Value) interface{}
func hashmapInterfaceHash(itf interface{}) uint32 {
x := reflect.ValueOf(itf)
if x.RawType() == 0 {
@ -384,13 +391,13 @@ func hashmapInterfaceHash(itf interface{}) uint32 {
case reflect.Array:
var hash uint32
for i := 0; i < x.Len(); i++ {
hash |= hashmapInterfaceHash(x.Index(i).Interface())
hash |= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)))
}
return hash
case reflect.Struct:
var hash uint32
for i := 0; i < x.NumField(); i++ {
hash |= hashmapInterfaceHash(x.Field(i).Interface())
hash |= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)))
}
return hash
default:

Loading…
Cancel
Save