1// Strict protowire inspection for generated envelope payloads.
2//
3// scanEnvelope runs before generated unmarshal: it enforces maximum depth,
4// field count, repeated-element counts and lengths, recursively rejects
5// unknown fields and wire types, and requires exactly one top-level
6// envelope variant occurrence. Repeated occurrences of singular fields and
7// concatenated envelope payloads are rejected; declared repeated fields
8// remain legal. Generated unmarshal and semantic validation run after.
9package wire
10
11import (
12 "errors"
13 "fmt"
14 "math"
15 "sync"
16
17 "google.golang.org/protobuf/encoding/protowire"
18 "google.golang.org/protobuf/proto"
19 "google.golang.org/protobuf/reflect/protoreflect"
20)
21
22// Scanner bounds. They cap decode work before any allocation a hostile
23// envelope could inflate; semantic validators apply tighter per-message
24// limits afterwards.
25const (
26 maxScanDepth = 16
27 maxScanFields = 65536
28 maxScanRepeated = 8192
29 maxScanBytesField = AbsoluteEnvelopeLimit
30 maxScanStringField = 4 << 20
31)
32
33var (
34 ErrScanDepth = errors.New("wire: envelope exceeds maximum nesting depth")
35 ErrScanFields = errors.New("wire: envelope exceeds maximum field count")
36 ErrScanRepeated = errors.New("wire: envelope exceeds maximum repeated elements")
37 ErrScanLength = errors.New("wire: envelope field exceeds maximum length")
38 ErrScanUnknown = errors.New("wire: envelope has unknown field")
39 ErrScanWireType = errors.New("wire: envelope has invalid wire type")
40 ErrScanDuplicate = errors.New("wire: envelope repeats singular field")
41 ErrScanEmpty = errors.New("wire: envelope has no payload variant")
42 ErrScanTrailing = errors.New("wire: envelope has trailing bytes")
43 ErrScanTruncated = errors.New("wire: envelope is truncated")
44 ErrScanNotMessage = errors.New("wire: envelope field is not a message")
45 ErrScanVarintRange = errors.New("wire: envelope varint exceeds declared field range")
46)
47
48// scanSpec describes the expected shape of one message level: known fields
49// by number with their wire type and repetition, resolved from the
50// descriptor at scan time.
51type scanSpec struct {
52 fields map[protoreflect.FieldNumber]scanField
53 // oneofs marks every field number that belongs to a oneof: its
54 // members are exclusive, so any second occurrence is a duplicate.
55 oneofs map[protoreflect.FieldNumber]bool
56}
57
58// scanField records one known field's expectations.
59type scanField struct {
60 kind protoreflect.Kind
61 isList bool
62 isMessage bool
63}
64
65// specCache memoizes scan specs by descriptor: descriptors are process
66// singletons, so each message shape is reflected exactly once instead
67// of on every received envelope (the receive hot path).
68var specCache sync.Map // map[protoreflect.MessageDescriptor]scanSpec
69
70// specForMessage returns the cached scan spec for one message descriptor.
71func specForMessage(descriptor protoreflect.MessageDescriptor) scanSpec {
72 if cached, ok := specCache.Load(descriptor); ok {
73 return cached.(scanSpec)
74 }
75 spec := scanSpec{
76 fields: make(map[protoreflect.FieldNumber]scanField),
77 oneofs: make(map[protoreflect.FieldNumber]bool),
78 }
79 fields := descriptor.Fields()
80 for i := range fields.Len() {
81 field := fields.Get(i)
82 spec.fields[field.Number()] = scanField{
83 kind: field.Kind(),
84 isList: field.IsList(),
85 isMessage: field.Kind() == protoreflect.MessageKind || field.Kind() == protoreflect.GroupKind,
86 }
87 }
88 oneofs := descriptor.Oneofs()
89 for i := range oneofs.Len() {
90 oneof := oneofs.Get(i)
91 members := oneof.Fields()
92 for j := range members.Len() {
93 spec.oneofs[members.Get(j).Number()] = true
94 }
95 }
96 actual, _ := specCache.LoadOrStore(descriptor, spec)
97 return actual.(scanSpec)
98}
99
100// ScanEnvelope strictly inspects one serialized envelope of the given type
101// before generated unmarshal. It returns nil only when the payload is
102// well-formed, known, singular at the top level, and fully consumed.
103func ScanEnvelope(message proto.Message, payload []byte) error {
104 if message == nil {
105 return ErrScanNotMessage
106 }
107 if len(payload) == 0 {
108 return ErrScanEmpty
109 }
110 if len(payload) > AbsoluteEnvelopeLimit {
111 return ErrScanLength
112 }
113 scanner := &envelopeScanner{fields: 0}
114 consumed, err := scanner.scanMessage(message.ProtoReflect().Descriptor(), payload, 0)
115 if err != nil {
116 return err
117 }
118 if consumed != len(payload) {
119 return ErrScanTrailing
120 }
121 return nil
122}
123
124type envelopeScanner struct {
125 fields int
126}
127
128// scanMessage scans one message level, returning bytes consumed.
129func (s *envelopeScanner) scanMessage(descriptor protoreflect.MessageDescriptor, payload []byte, depth int) (int, error) {
130 if depth > maxScanDepth {
131 return 0, ErrScanDepth
132 }
133 spec := specForMessage(descriptor)
134 consumed := 0
135 variantSeen := false
136 repeated := 0
137 seen := map[protoreflect.FieldNumber]bool{}
138 for len(payload) > 0 {
139 number, wireType, length := protowire.ConsumeTag(payload)
140 if length < 0 {
141 return 0, ErrScanTruncated
142 }
143 field, ok := spec.fields[number]
144 if !ok {
145 return 0, fmt.Errorf("%w: field %d", ErrScanUnknown, number)
146 }
147 if err := checkWireType(field, wireType); err != nil {
148 return 0, err
149 }
150 s.fields++
151 if s.fields > maxScanFields {
152 return 0, ErrScanFields
153 }
154 if spec.oneofs[number] {
155 // Oneof members are exclusive: any second occurrence —
156 // same variant or another — is a duplicate, and the
157 // top level requires exactly one variant.
158 if variantSeen {
159 return 0, fmt.Errorf("%w: oneof field %d", ErrScanDuplicate, number)
160 }
161 variantSeen = true
162 } else if !field.isList {
163 if seen[number] {
164 return 0, fmt.Errorf("%w: field %d", ErrScanDuplicate, number)
165 }
166 seen[number] = true
167 } else {
168 repeated++
169 if repeated > maxScanRepeated {
170 return 0, ErrScanRepeated
171 }
172 }
173 fieldDescriptor := descriptor.Fields().ByNumber(number)
174 n, err := s.scanField(fieldDescriptor, field, wireType, payload[length:], depth)
175 if err != nil {
176 return 0, err
177 }
178 consumed += length + n
179 payload = payload[length+n:]
180 }
181 if depth == 0 && isEnvelopeDescriptor(descriptor) && !variantSeen {
182 return 0, ErrScanEmpty
183 }
184 return consumed, nil
185}
186
187// isEnvelopeDescriptor reports whether the descriptor is a top-level
188// directional envelope requiring exactly one variant. Session, broker, and
189// daemonmux envelopes are separate conversations with disjoint tag spaces;
190// both directions of each require exactly one variant. The daemonmux
191// preamble messages are adapter messages, not envelopes, so they are not
192// listed here.
193func isEnvelopeDescriptor(descriptor protoreflect.MessageDescriptor) bool {
194 switch string(descriptor.FullName()) {
195 case "vev.wire.v1.ClientEnvelope", "vev.wire.v1.ServerEnvelope",
196 "vev.wire.v1.BrokerClientEnvelope", "vev.wire.v1.BrokerServerEnvelope",
197 "vev.wire.v1.MuxClientEnvelope", "vev.wire.v1.MuxServerEnvelope":
198 return true
199 default:
200 return false
201 }
202}
203
204// checkVarintRange rejects a raw varint that cannot be represented by the
205// declared scalar kind. Generated unmarshal truncates the high bits of 32-bit
206// fields, so a hostile 64-bit varint could otherwise alias a legitimate value
207// (for example an in-range enum code, a bool, or a negative int32).
208func checkVarintRange(kind protoreflect.Kind, value uint64) error {
209 switch kind {
210 case protoreflect.BoolKind:
211 // Protobuf treats any nonzero varint as true, so a strict scanner
212 // rejects the non-canonical values above one.
213 if value > 1 {
214 return fmt.Errorf("%w: kind %v", ErrScanVarintRange, kind)
215 }
216 case protoreflect.Uint32Kind, protoreflect.Sint32Kind, protoreflect.EnumKind:
217 if value > math.MaxUint32 {
218 return fmt.Errorf("%w: kind %v", ErrScanVarintRange, kind)
219 }
220 case protoreflect.Int32Kind:
221 // A canonical negative int32 sign-extends into ten bytes, so the
222 // valid encodings are 0..MaxInt32 and the sign-extended range
223 // [MaxUint64-MaxInt32, MaxUint64]. Anything between the two
224 // truncates to a different int32 and is rejected.
225 if value > math.MaxInt32 && value < math.MaxUint64-uint64(math.MaxInt32) {
226 return fmt.Errorf("%w: kind %v", ErrScanVarintRange, kind)
227 }
228 }
229 return nil
230}
231
232func checkWireType(field scanField, wireType protowire.Type) error {
233 switch field.kind {
234 case protoreflect.BoolKind, protoreflect.EnumKind,
235 protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Uint32Kind,
236 protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind:
237 if wireType != protowire.VarintType {
238 return fmt.Errorf("%w: want varint", ErrScanWireType)
239 }
240 case protoreflect.Fixed32Kind, protoreflect.Sfixed32Kind, protoreflect.FloatKind:
241 if wireType != protowire.Fixed32Type {
242 return fmt.Errorf("%w: want fixed32", ErrScanWireType)
243 }
244 case protoreflect.Fixed64Kind, protoreflect.Sfixed64Kind, protoreflect.DoubleKind:
245 if wireType != protowire.Fixed64Type {
246 return fmt.Errorf("%w: want fixed64", ErrScanWireType)
247 }
248 case protoreflect.StringKind, protoreflect.BytesKind, protoreflect.MessageKind, protoreflect.GroupKind:
249 if wireType != protowire.BytesType {
250 return fmt.Errorf("%w: want length-delimited", ErrScanWireType)
251 }
252 default:
253 return fmt.Errorf("%w: kind %v", ErrScanWireType, field.kind)
254 }
255 return nil
256}
257
258func (s *envelopeScanner) scanField(descriptor protoreflect.FieldDescriptor, field scanField, wireType protowire.Type, payload []byte, depth int) (int, error) {
259 switch wireType {
260 case protowire.VarintType:
261 value, length := protowire.ConsumeVarint(payload)
262 if length < 0 {
263 return 0, ErrScanTruncated
264 }
265 if err := checkVarintRange(field.kind, value); err != nil {
266 return 0, err
267 }
268 return length, nil
269 case protowire.Fixed32Type:
270 if len(payload) < 4 {
271 return 0, ErrScanTruncated
272 }
273 return 4, nil
274 case protowire.Fixed64Type:
275 if len(payload) < 8 {
276 return 0, ErrScanTruncated
277 }
278 return 8, nil
279 case protowire.BytesType:
280 value, length := protowire.ConsumeBytes(payload)
281 if length < 0 {
282 return 0, ErrScanTruncated
283 }
284 switch {
285 case field.isMessage:
286 if descriptor == nil || descriptor.Message() == nil {
287 return 0, ErrScanNotMessage
288 }
289 if len(value) > maxScanBytesField {
290 return 0, ErrScanLength
291 }
292 if _, err := s.scanMessage(descriptor.Message(), value, depth+1); err != nil {
293 return 0, err
294 }
295 return length, nil
296 case field.kind == protoreflect.StringKind:
297 if len(value) > maxScanStringField {
298 return 0, ErrScanLength
299 }
300 return length, nil
301 default:
302 if len(value) > maxScanBytesField {
303 return 0, ErrScanLength
304 }
305 return length, nil
306 }
307 default:
308 return 0, fmt.Errorf("%w: %d", ErrScanWireType, wireType)
309 }
310}
311
312// validatedUnmarshal scans payload, unmarshals into message, and reports a
313// trailing-garbage-safe result. Callers run semantic validation afterwards.
314func validatedUnmarshal(message proto.Message, payload []byte) error {
315 if err := ScanEnvelope(message, payload); err != nil {
316 return err
317 }
318 options := proto.UnmarshalOptions{DiscardUnknown: false}
319 if err := options.Unmarshal(payload, message); err != nil {
320 return err
321 }
322 return nil
323}