mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-01 03:14:16 +00:00
internal/fuzz/wirefuzz: add test to verify initialization checks
The UnmarshalInitialized flag produced by Unmarshal and Validate are filters such that must never have false positives (i.e., report a partial message as initialized) otherwise it is incorrect. It can tolerate some degree of false negatives (i.e., report an initialized message as partial), but that leads to significant performance degradation needing to do the full initialization check. These should be the exception, not the norm. Adjust the fuzzer to search for false-negative cases. For now, we only require that the Unmarshal and Validate report initialized for any "normalized" messages which we produce by marshaling intermediate message again. This is to work around a known case where they cannot determine initialization status if the wire data relies on protobuf's merge functionality (where two or more partial messages merge together to form an initialized message). Change-Id: I6bb6c6594981ca08a92583bae77e5a2d44924af6 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/231577 Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
d0a499bc65
commit
4d5be764fb
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"google.golang.org/protobuf/internal/impl"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
piface "google.golang.org/protobuf/runtime/protoiface"
|
||||
|
||||
fuzzpb "google.golang.org/protobuf/internal/testprotos/fuzz"
|
||||
@ -17,14 +18,11 @@ import (
|
||||
|
||||
// Fuzz is a fuzzer for proto.Marshal and proto.Unmarshal.
|
||||
func Fuzz(data []byte) (score int) {
|
||||
// Unmarshal and Validate should agree about the validity of the message.
|
||||
m1 := &fuzzpb.Fuzz{}
|
||||
vout, valid := impl.Validate(m1.ProtoReflect().Type(), piface.UnmarshalInput{
|
||||
Buf: data,
|
||||
})
|
||||
vinit := vout.Flags&piface.UnmarshalInitialized != 0
|
||||
if err := (proto.UnmarshalOptions{
|
||||
AllowPartial: true,
|
||||
}).Unmarshal(data, m1); err != nil {
|
||||
mt := m1.ProtoReflect().Type()
|
||||
_, valid := impl.Validate(mt, piface.UnmarshalInput{Buf: data})
|
||||
if err := (proto.UnmarshalOptions{AllowPartial: true}).Unmarshal(data, m1); err != nil {
|
||||
switch valid {
|
||||
case impl.ValidationUnknown:
|
||||
case impl.ValidationInvalid:
|
||||
@ -39,22 +37,46 @@ func Fuzz(data []byte) (score int) {
|
||||
default:
|
||||
panic("unmarshal ok with validation status: " + valid.String())
|
||||
}
|
||||
if proto.CheckInitialized(m1) != nil && vinit {
|
||||
panic("validation reports partial message is initialized")
|
||||
|
||||
// Unmarshal, Validate, and CheckInitialized should agree about initialization.
|
||||
checkInit := proto.CheckInitialized(m1) == nil
|
||||
methods := m1.ProtoReflect().ProtoMethods()
|
||||
in := piface.UnmarshalInput{Message: mt.New(), Resolver: protoregistry.GlobalTypes}
|
||||
if checkInit {
|
||||
// If the message initialized, the both Unmarshal and Validate should
|
||||
// report it as such. False negatives are tolerated, but have a
|
||||
// significant impact on performance. In general, they should always
|
||||
// properly determine initialization for any normalized message,
|
||||
// we produce by re-marshaling the message.
|
||||
in.Buf, _ = proto.Marshal(m1)
|
||||
if out, _ := methods.Unmarshal(in); out.Flags&piface.UnmarshalInitialized == 0 {
|
||||
panic("unmarshal reports initialized message as partial")
|
||||
}
|
||||
if out, _ := impl.Validate(mt, in); out.Flags&piface.UnmarshalInitialized == 0 {
|
||||
panic("validate reports initialized message as partial")
|
||||
}
|
||||
} else {
|
||||
// If the message is partial, then neither Unmarshal nor Validate
|
||||
// should ever report it as such. False positives are unacceptable.
|
||||
in.Buf = data
|
||||
if out, _ := methods.Unmarshal(in); out.Flags&piface.UnmarshalInitialized != 0 {
|
||||
panic("unmarshal reports partial message as initialized")
|
||||
}
|
||||
if out, _ := impl.Validate(mt, in); out.Flags&piface.UnmarshalInitialized != 0 {
|
||||
panic("validate reports partial message as initialized")
|
||||
}
|
||||
}
|
||||
data1, err := proto.MarshalOptions{
|
||||
AllowPartial: true,
|
||||
}.Marshal(m1)
|
||||
|
||||
// Round-trip Marshal and Unmarshal should produce the same messages.
|
||||
data1, err := proto.MarshalOptions{AllowPartial: !checkInit}.Marshal(m1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if proto.Size(m1) != len(data1) {
|
||||
panic(fmt.Errorf("size does not match output %v", m1))
|
||||
panic(fmt.Errorf("size does not match output: %d != %d", proto.Size(m1), len(data1)))
|
||||
}
|
||||
m2 := &fuzzpb.Fuzz{}
|
||||
if err := (proto.UnmarshalOptions{
|
||||
AllowPartial: true,
|
||||
}).Unmarshal(data1, m2); err != nil {
|
||||
if err := (proto.UnmarshalOptions{AllowPartial: !checkInit}).Unmarshal(data1, m2); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !proto.Equal(m1, m2) {
|
||||
|
Loading…
Reference in New Issue
Block a user