(Specifications) A bug in specifications with no direct impact on client implementations
Description
Brief/Intro
A vulnerability exists in the accounts/abi package where unvalidated fieldName from user-provided ABI definitions are used to construct struct types using reflect.StructOf. This can cause a panic when the invalid characters are included in the fieldName.
Vulnerability Details
In the type.go file of the accounts/abi package, the code dynamically constructs tuple types based on ABI component definitions as follow:
case"tuple":var(fields[]reflect.StructFieldelems[]*Typenames[]stringexpressionstring// canonical parameter expression)expression+="("overloadedNames:=make(map[string]string)foridx,c:=rangecomponents{cType,err:=NewType(c.Type,c.InternalType,c.Components)iferr!=nil{returnType{},err}fieldName,err:=overloadedArgName(c.Name,overloadedNames)iferr!=nil{returnType{},err}overloadedNames[fieldName]=fieldNamefields=append(fields,reflect.StructField{Name:fieldName,// reflect.StructOf will panic for any exported field.Type:cType.GetType(),Tag:reflect.StructTag("json:\""+c.Name+"\""),})elems=append(elems,&cType)names=append(names,c.Name)expression+=cType.stringKindifidx!=len(components)-1{expression+=","}}expression+=")"typ.TupleType=reflect.StructOf(fields)typ.TupleElems=elemstyp.TupleRawNames=namestyp.T=TupleTytyp.stringKind=expression
The fieldName is taken directly from the ABI component's Name without validation. However, the reflect.StructOf function requires that all field names be valid with the following check. Otherwise, the panic will rise.
if !isValidFieldName(field.Name) {
panic("reflect.StructOf: field " + strconv.Itoa(i) + " has invalid name")
}
---
// isValidFieldName checks if a string is a valid (struct) field name or not.
//
// According to the language spec, a field name should be an identifier.
//
// identifier = letter { letter | unicode_digit } .
// letter = unicode_letter | "_" .
func isValidFieldName(fieldName string) bool {
for i, c := range fieldName {
if i == 0 && !isLetter(c) {
return false
}
if !(isLetter(c) || unicode.IsDigit(c)) {
return false
}
}
return len(fieldName) > 0
}
// TestCrashers contains some strings which previously caused the abi codec to crash.
func TestCrashers(t *testing.T) {
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"foo.Bar"}]}]}]`))
}