Codegen rejects moderately large schemas with element memory limit exceeded. Around 300 proto files is enough to break protoc-gen-buffa, and there is currently no option anywhere to raise the limit.
Introduced by #319 (element-memory bound on repeated elements and map entries), shipped in v0.9.0.
Reproduction
Against googleapis, taking the first N files of find google -name '*.proto':
protoc -I. --buffa_out=out @filelist.txt
| protos |
CodeGeneratorRequest size |
result |
| 250 |
~4.4 MB |
OK |
| 300 |
~5.5 MB |
--buffa_out: failed to decode CodeGeneratorRequest: element memory limit exceeded |
| 7071 (all) |
~81 MB |
same |
Why descriptors hit this so easily
The limit charges size_of::<Element>() per repeated length-delimited element, and the descriptor types are unusually large structs:
size_of::<FileDescriptorProto>() = 704
size_of::<FieldDescriptorProto>() = 416
size_of::<DescriptorProto>() = 360
Measured against real FileDescriptorSets, the element footprint is consistently about 6.3x the encoded byte size:
| descriptor set |
bytes |
smallest budget that decodes |
| googleapis, all 7071 protos, with source info |
81,431,653 |
512 MiB |
googleapis, source_code_info stripped (#286) |
20,721,688 |
128 MiB |
| 1600 protos |
4,582,704 |
32 MiB |
| 351 protos (aiplatform) |
1,164,985 |
8 MiB |
So DEFAULT_ELEMENT_MEMORY_LIMIT (32 MiB) works out to roughly 5 MB of descriptors — and 5 MB * 6.3 = 31.5 MiB lands right on the default. A CodeGeneratorRequest is worse than a bare FileDescriptorSet of the same schema, because protoc includes source_code_info and every transitive import.
Affected paths
All four decode with default options, and none is overridable:
| location |
symptom |
protoc-gen-buffa/src/main.rs:104 |
failed to decode CodeGeneratorRequest: element memory limit exceeded |
protoc-gen-buffa-packaging/src/main.rs:174 |
same |
buffa-build/src/lib.rs:1695 |
failed to decode FileDescriptorSet: element memory limit exceeded |
DescriptorPool::decode (buffa-descriptor/src/pool.rs:280) |
see below |
DescriptorPool::decode is the worst of the four, because generated descriptor_pool() calls it as:
DescriptorPool::decode(FILE_DESCRIPTOR_SET_BYTES)
.expect("embedded FileDescriptorSet is well-formed")
A consumer with a large schema and reflection enabled therefore gets a runtime panic asserting the bytes are malformed — when they are perfectly well-formed and it was the limit that rejected them. There is no override on that path at all.
Workarounds today
None for the plugin path. buffa-build::Config exposes no decode-limit method, protoc-gen-buffa parses no such option, and the generated descriptor_pool() is hardcoded. Splitting the schema into smaller protoc invocations is the only avoidance.
Fix
The underlying issue is that a DoS bound designed for untrusted input is being applied to trusted input: protoc's own output, the descriptor set from a protoc that buffa-build just invoked, and bytes that buffa-codegen itself embedded. The generated doc comment already states this ("emitted by buffa-codegen from the same descriptors it generated this code from, so a panic indicates a codegen bug, not consumer input").
Planned:
- Tooling paths lift or significantly raise the limit —
protoc-gen-buffa, protoc-gen-buffa-packaging, buffa-build, and the generated descriptor_pool().
DescriptorPool::decode gains an options-taking variant so callers can set the element-memory limit. The existing entry point stays bounded, since it is public and may legitimately be handed an untrusted descriptor set (an uploaded FDS, a reflection service).
- The protoc plugins get an
element_memory_limit option, and a higher default than the library's — the caller controls the protobuf inputs in that context.
- The panic message in generated
descriptor_pool() gets fixed — it is actively misleading when the limit is the cause.
The global DEFAULT_ELEMENT_MEMORY_LIMIT is deliberately not being raised: that would weaken the protection #319 added for genuinely untrusted input. The fix is per-path.
Codegen rejects moderately large schemas with
element memory limit exceeded. Around 300 proto files is enough to breakprotoc-gen-buffa, and there is currently no option anywhere to raise the limit.Introduced by #319 (element-memory bound on repeated elements and map entries), shipped in v0.9.0.
Reproduction
Against googleapis, taking the first N files of
find google -name '*.proto':CodeGeneratorRequestsize--buffa_out: failed to decode CodeGeneratorRequest: element memory limit exceededWhy descriptors hit this so easily
The limit charges
size_of::<Element>()per repeated length-delimited element, and the descriptor types are unusually large structs:Measured against real
FileDescriptorSets, the element footprint is consistently about 6.3x the encoded byte size:source_code_infostripped (#286)So
DEFAULT_ELEMENT_MEMORY_LIMIT(32 MiB) works out to roughly 5 MB of descriptors — and5 MB * 6.3 = 31.5 MiBlands right on the default. ACodeGeneratorRequestis worse than a bareFileDescriptorSetof the same schema, because protoc includessource_code_infoand every transitive import.Affected paths
All four decode with default options, and none is overridable:
protoc-gen-buffa/src/main.rs:104failed to decode CodeGeneratorRequest: element memory limit exceededprotoc-gen-buffa-packaging/src/main.rs:174buffa-build/src/lib.rs:1695failed to decode FileDescriptorSet: element memory limit exceededDescriptorPool::decode(buffa-descriptor/src/pool.rs:280)DescriptorPool::decodeis the worst of the four, because generateddescriptor_pool()calls it as:A consumer with a large schema and reflection enabled therefore gets a runtime panic asserting the bytes are malformed — when they are perfectly well-formed and it was the limit that rejected them. There is no override on that path at all.
Workarounds today
None for the plugin path.
buffa-build::Configexposes no decode-limit method,protoc-gen-buffaparses no such option, and the generateddescriptor_pool()is hardcoded. Splitting the schema into smallerprotocinvocations is the only avoidance.Fix
The underlying issue is that a DoS bound designed for untrusted input is being applied to trusted input: protoc's own output, the descriptor set from a protoc that
buffa-buildjust invoked, and bytes thatbuffa-codegenitself embedded. The generated doc comment already states this ("emitted bybuffa-codegenfrom the same descriptors it generated this code from, so a panic indicates a codegen bug, not consumer input").Planned:
protoc-gen-buffa,protoc-gen-buffa-packaging,buffa-build, and the generateddescriptor_pool().DescriptorPool::decodegains an options-taking variant so callers can set the element-memory limit. The existing entry point stays bounded, since it is public and may legitimately be handed an untrusted descriptor set (an uploaded FDS, a reflection service).element_memory_limitoption, and a higher default than the library's — the caller controls the protobuf inputs in that context.descriptor_pool()gets fixed — it is actively misleading when the limit is the cause.The global
DEFAULT_ELEMENT_MEMORY_LIMITis deliberately not being raised: that would weaken the protection #319 added for genuinely untrusted input. The fix is per-path.