Skip to content

Commit 20262ed

Browse files
committed
encoding/prototext: implement recursion limit
Fixes golang/protobuf#1705 For protocolbuffers/protobuf#25068 Change-Id: I11b6fd51a71cd2ba413ca4d4ef2a2eb49710f9ee Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/734761 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Lasse Folger <lassefolger@google.com>
1 parent e9def1a commit 20262ed

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

encoding/prototext/decode.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"unicode/utf8"
1010

11+
"google.golang.org/protobuf/encoding/protowire"
1112
"google.golang.org/protobuf/internal/encoding/messageset"
1213
"google.golang.org/protobuf/internal/encoding/text"
1314
"google.golang.org/protobuf/internal/errors"
@@ -49,12 +50,19 @@ type UnmarshalOptions struct {
4950
protoregistry.MessageTypeResolver
5051
protoregistry.ExtensionTypeResolver
5152
}
53+
54+
// RecursionLimit limits how deeply messages may be nested.
55+
// If zero, a default limit is applied.
56+
RecursionLimit int
5257
}
5358

5459
// Unmarshal reads the given []byte and populates the given [proto.Message]
5560
// using options in the UnmarshalOptions object.
5661
// The provided message must be mutable (e.g., a non-nil pointer to a message).
5762
func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error {
63+
if o.RecursionLimit == 0 {
64+
o.RecursionLimit = protowire.DefaultRecursionLimit
65+
}
5866
return o.unmarshal(b, m)
5967
}
6068

@@ -102,8 +110,14 @@ func (d decoder) syntaxError(pos int, f string, x ...any) error {
102110
return errors.New(head+f, x...)
103111
}
104112

113+
var errRecursionDepth = errors.New("exceeded maximum recursion depth")
114+
105115
// unmarshalMessage unmarshals into the given protoreflect.Message.
106116
func (d decoder) unmarshalMessage(m protoreflect.Message, checkDelims bool) error {
117+
if d.opts.RecursionLimit--; d.opts.RecursionLimit < 0 {
118+
return errRecursionDepth
119+
}
120+
107121
messageDesc := m.Descriptor()
108122
if !flags.ProtoLegacy && messageset.IsMessageSet(messageDesc) {
109123
return errors.New("no support for proto1 MessageSets")
@@ -437,6 +451,10 @@ func (d decoder) unmarshalList(fd protoreflect.FieldDescriptor, list protoreflec
437451
// unmarshalMap unmarshals into given protoreflect.Map. A map value is a
438452
// textproto message containing {key: <kvalue>, value: <mvalue>}.
439453
func (d decoder) unmarshalMap(fd protoreflect.FieldDescriptor, mmap protoreflect.Map) error {
454+
if d.opts.RecursionLimit--; d.opts.RecursionLimit < 0 {
455+
return errRecursionDepth
456+
}
457+
440458
// Determine ahead whether map entry is a scalar type or a message type in
441459
// order to call the appropriate unmarshalMapValue func inside
442460
// unmarshalMapEntry.

encoding/prototext/decode_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,74 @@ unknown: ""
16211621
type_url: "pb2.Nested"
16221622
`,
16231623
wantErr: "(line 3:1): conflict with [pb2.Nested] field",
1624+
}, {
1625+
desc: "just at recursion limit (no submessages)",
1626+
umo: prototext.UnmarshalOptions{RecursionLimit: 1},
1627+
inputMessage: &pbeditions.Nested{},
1628+
inputText: `opt_string: "hey"`,
1629+
}, {
1630+
desc: "just at recursion limit",
1631+
umo: prototext.UnmarshalOptions{RecursionLimit: 3},
1632+
inputMessage: &pbeditions.Nested{},
1633+
inputText: `opt_nested: { opt_nested: { opt_string: "hey" } }`,
1634+
}, {
1635+
desc: "just at recursion limit: maps",
1636+
umo: prototext.UnmarshalOptions{
1637+
// Maps are syntatic sugar for repeated fields of a synthetic
1638+
// message type (with key as field 1, value as field 2).
1639+
RecursionLimit: 2,
1640+
},
1641+
inputMessage: &pbeditions.Maps{},
1642+
inputText: `str_to_nested: { key: "missing" }`,
1643+
}, {
1644+
desc: "just at recursion limit: maps of messages",
1645+
umo: prototext.UnmarshalOptions{
1646+
RecursionLimit: 3,
1647+
},
1648+
inputMessage: &pbeditions.Maps{},
1649+
inputText: `str_to_nested: {
1650+
key: "missing"
1651+
}
1652+
str_to_nested: {
1653+
key: "contains"
1654+
value: {
1655+
opt_string: "here"
1656+
}
1657+
}
1658+
`,
1659+
}, {
1660+
desc: "exceed recursion limit",
1661+
umo: prototext.UnmarshalOptions{RecursionLimit: 1},
1662+
inputMessage: &pbeditions.Nested{},
1663+
inputText: `opt_nested: { opt_string: "hey" }`,
1664+
wantErr: "exceeded maximum recursion depth",
1665+
}, {
1666+
desc: "exceed recursion limit: maps",
1667+
umo: prototext.UnmarshalOptions{
1668+
// Maps are syntatic sugar for repeated fields of a synthetic
1669+
// message type (with key as field 1, value as field 2).
1670+
RecursionLimit: 1,
1671+
},
1672+
inputMessage: &pbeditions.Maps{},
1673+
inputText: `str_to_nested: { key: "missing" }`,
1674+
wantErr: "exceeded maximum recursion depth",
1675+
}, {
1676+
desc: "exceed recursion limit: maps of messages",
1677+
umo: prototext.UnmarshalOptions{
1678+
RecursionLimit: 2,
1679+
},
1680+
inputMessage: &pbeditions.Maps{},
1681+
inputText: `str_to_nested: {
1682+
key: "missing"
1683+
}
1684+
str_to_nested: {
1685+
key: "contains"
1686+
value: {
1687+
opt_string: "here"
1688+
}
1689+
}
1690+
`,
1691+
wantErr: "exceeded maximum recursion depth",
16241692
}}
16251693

16261694
for _, msg := range makeMessages(protobuild.Message{},

0 commit comments

Comments
 (0)