Skip to content

Codegen rejects large schemas: element memory limit exceeded (~300 protos) #331

Description

@iainmcgin

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:

  1. Tooling paths lift or significantly raise the limitprotoc-gen-buffa, protoc-gen-buffa-packaging, buffa-build, and the generated descriptor_pool().
  2. 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).
  3. 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.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions