blob: bc66019af44d71ce464b239620b4e51a96cc7525 [file] [view]
# Security Policy
This document describes the security model, threat boundaries, and security
recommendations for Protobuf. It is intended to help developers understand what
security guarantees Protobuf provides, and how to safely parse and serialize
messages.
--------------------------------------------------------------------------------
## Reporting a Vulnerability
If you believe you have discovered a security vulnerability in Protobuf, please
report it via
[Google's official vulnerability disclosure channel](https://bughunters.google.com/report).
For reports that do not meet the criteria or bar for the Google Vulnerability
Reward Program (VRP) but are still security-sensitive, you can privately report
them by opening a
[draft GitHub Security Advisory](https://github.com/protocolbuffers/protobuf/security/advisories/new).
If an issue relates to something which is listed as Best Effort or Out of Threat
Model below, please open a
[public issue](https://github.com/protocolbuffers/protobuf/issues) instead.
--------------------------------------------------------------------------------
## Best Practices
* Handle `.proto` files as "code" by default, similarly to the handling of
`.java` or `.cc` files. Executing untrusted code can potentially be a supply
chain attack vector.
* Prefer to use the binary wire format encoding where possible: all
Google-maintained Protobuf implementations treat binary wire format parsing
as the primary use case to handle untrusted inputs. Other formats are
considered secondary.
* **Apply Defensive limits:** Prefer to have a layer that applies defensive
limits or custom filtering around the Protobuf parse: for example gRPC
enforces a 4 MiB payload limit by default. We recommend having sensible
limits defensively applied on all untrusted inputs.
* While all Google maintained Protobuf implementations are considered to be
intended to be used with untrusted inputs, not all runtimes are equally
hardened:
* **JavaProto** (including the Kotlin bindings) is recommended as the
default implementation for the best security posture, as it benefits
from the JVM's memory safety guarantees.
* **C++Proto** is recommended for applications requiring optimal
performance, and has been extensively fuzzed and hardened such that
Google uses it without sandboxing to parse untrusted binary format
inputs in critical surfaces. When using C++, developers must remain
aware of native memory management risks.
* In languages that have multiple supported implementations, strongly
prefer to use the default implementation for best security. The
non-default implementations are generally supported for more exotic
use-cases and may not have as much hardening attention. The default
Python and PHP runtimes use a C extension that has been hardened more
than the fallback behaviors that do not use any C extension.
* **Use latest releases:** Ensure that the generated code and the runtime
library version match exactly and are kept up to date with the latest patch
release. Certain obscure and low severity issues may only be patched on the
latest release.
## Threat Model and Security Boundaries
Protobuf is a serialization format designed to parse data efficiently. Depending
on the input format, language runtime, and integration context, different parts
of the Protobuf ecosystem are hardened against adversarial inputs.
Our threat model divides surfaces into three tiers:
### Proactively Hardened
These surfaces are fully supported and hardened against adversarial inputs.
* Security vulnerabilities identified in these paths are treated with high
priority, actively fixed, and issued CVEs.
* Where necessary, certain patches that are technically breaking may be done
if it is inherently necessary to close a security issue. This also includes
that gencode-runtime version compatibility guarantees may be broken if it is
strictly necessary to close a security issue (see documentation
[here](https://protobuf.dev/support/cross-version-runtime-guarantee/#exception)).
These cases are rare and we will avoid this wherever it is possible to close
the security concern without a breaking change.
* Google trusts these surfaces enough to use in sensitive, publicly exposed
endpoints without sandboxing.
### Reactively hardened
The Protobuf team welcomes bug reports and pull requests to improve the
hardening of these surfaces. However, we typically will not break backwards
compatibility guarantees to address security issues in these areas (especially
for lower severity risks).
Defensive hardening is applied to these surfaces, but with weaker guarantees
compared to our proactively hardened surfaces.
* Serious security issues are still highly prioritized for fixing on these
surfaces.
* We do not break backwards compatibility guarantees to address lower severity
issues on these surfaces outside of major version bumps. In some cases this
means low-impact known issues may even be left open if they inherently
cannot be closed without a breaking change. Serious issues (like RCE) would
still be urgently addressed.
* When using these surfaces on potentially malicious inputs, especially
security sensitive usages are recommended to consider application-level
isolation (such as sandboxing) or other defensive handling.
### Outside of CVE Threat Model (Best-Effort Hardened)
These are surfaces where Protobuf libraries are not as hardened against
adversarial inputs.
Outside of threat model does not mean we do not care at all about
security-relevant behaviors on these surfaces: we still apply defensive
hardening where feasible, and welcome reports on these surfaces. However, as
these surfaces are expected to be used with trusted inputs, the higher priority
for those cases is other topics including stability, performance, and developer
ergonomics.
## Proactively Hardened
### Parsing of Binary Wire Format Encoded Data **(Primary Use Case)**
This is considered the primary surface of security concern in Protobuf
libraries.
In this use case, the `.proto` schema files, the compiler (`protoc`), and the
generated code are fully trusted. The incoming binary wire format bytes are
untrusted and may be adversarial.
The parsing library will safely process or reject any arbitrary byte stream
without exposing the server to memory corruption, out-of-bounds reads/writes, or
remote code execution (RCE).
Note that the intended surface here is only parsing: once a message is in-memory
it is treated as a trusted object in our threat model. For that reason,
serialization or any other in-memory handling of parsed objects is not
considered a surface within the threat model. For example, parsing an untrusted
wire format payload should not be able to reach uncontrolled recursion, but
serializing an arbitrarily in-memory object may. This is similar in nature to
how modern browsers do not throw `RangeError` on `JSON.parse()`, but do on
`JSON.stringify()`.
### Parsing of ProtoJSON Format
[ProtoJSON](https://protobuf.dev/programming-guides/json/) allows using `.proto`
schemas with standard JSON encoding. This is considered an ancillary supported
encoding, and the binary format should be preferred where feasible.
Under the same threat model as publicly exposed binary wire format services,
ProtoJSON parsing is intended to be used with untrusted inputs. ProtoJSON
serialization is similarly not considered within the threat model risks.
--------------------------------------------------------------------------------
## Reactively Hardened
These areas are defensive against malicious inputs, but application-level
defense (such as sandboxing) is recommended.
### Parsing Text Format
Text Format is designed for local debugging, testing, and managing trusted
configurations by developers. It is not intended to be used as an interchange
format and is not recommended to expose public services which consume Text
Format as an encoding.
Note: Text Format parsing currently does not enforce any depth limit in several
supported runtimes (they support opt-in depth limits). We cannot enforce a depth
limit by default without breaking backwards compatibility, but may begin to
enforce depth limits by default as part of a future breaking change release.
### Lite Runtimes (C++ Lite, Java Lite) Denial of Service Risks
Lite runtimes target mobile and web usage: they are optimized for those
constrained environments, and prioritize small binary size at the expense of
other properties.
Lite runtimes are still intended to be used to parse untrusted inputs, but DoS
issues are considered to be much less severe in mobile contexts, as the
opportunity and impact of an attacker successfully freezing one app is low
compared to reducing the availability of a public service.
We still intend to mitigate such risks, but Lite gencode/runtimes are not
recommended for servers exposing public endpoints.
### 'Wrong Kind of Exception Thrown'
In memory-safe runtimes (Java, Go, Python, C#), reaching a catchable exception
or runtime error (such as `IndexOutOfBoundsException` or `StackOverflowError`)
instead of the declared exception (such as `InvalidProtocolBufferException`) is
treated as an important bug to fix, but is considered to be a minor security
concern relative to serious issues like native heap corruption, data leakage,
remote code execution, or DoS vectors from unbounded computation or memory use.
It is recommended that RPC handler code be defensive against unexpected
exceptions if they are exceptionally sensitive to such behavior.
## Outside of CVE Threat Model (Best-effort Hardened)
### `protoc` CLI
The `protoc` CLI is an offline developer tool. `.proto` files are considered
source code (equivalent to `.java` or `.cpp` source files).
A supported modality of the CLI is to parse untrusted `proto` files to emit
`FileDescriptors` which can enable further machine processing of the schemas.
While `protoc` is hardened on a best-effort basis for this use case, we
recommend using defensive validation and sandboxing whenever running `protoc`
against potentially malicious inputs.
Untrusted flags being passed to `protoc` are fully outside of our threat model:
CLI flags are never adversarial, and arbitrary behavior driven by CLI flags may
be working as intended.
Caution: Compiling and executing generated code from untrusted schemas is
functionally equivalent to compiling and running arbitrary third-party `.java`
or `.cpp` source code and executing it: there may be intentional language
features which act as intentional code injection into the generated code. You
must treat untrusted `.proto` files the same as any other programming language
code in terms of supply-chain risk in this way, and not blindly execute the
gencode which was generated off of untrusted `.proto` files.
### DynamicMessage on Untrusted Descriptors
Protobuf supports encoding schemas into a Protobuf message format (e.g.
`FileDescriptorSet` or `DescriptorProto`). These messages can be handled as any
other Protobuf type. Parsing untrusted binary-encoded `DescriptorProto` falls
within the "primary use-case" described above.
In addition to simply processing `DescriptorProto`, it is additionally possible
in most runtimes to use a type named `DynamicMessage` which allows for using
runtime-loaded descriptors instead of using generated code and to use that type
with the reflection APIs.
**There are inherent denial of service risks to using `DynamicMessage` (and
other reflection-based APIs operating on runtime-loaded descriptors, including
upb Defs and MiniTables) with untrusted descriptors which cannot be hardened
against.**
Descriptors are treated as trusted schema, and need to be handled as though they
are source code.
If you use `DynamicMessage` with descriptors from an untrusted source, Denial of
Service avenues are inherently available to an attacker.
* **Memory amplification is inherent.** Memory use can be O(N*M) where N is "#
of messages observed on the wire" and M is "size of the message definition".
Since untrusted descriptors give an affordance for arbitrarily large message
definitions, using `DynamicMessage` with untrusted descriptors and untrusted
binary format inherently has memory amplification risks.
* **Invalid shapes may reach unexpected exceptions or crashes.** Descriptors
that do not maintain the invariants that the runtimes expect may reach
unexpected exceptions, panics, assertion failures, or `CHECK` failures
(process aborts). Examples include 'forged' well-known types (descriptors
using a well-known type name such as `google.protobuf.Any`,
`google.protobuf.Timestamp`, or `google.protobuf.Struct` but with a
different shape than the real type), and map entry messages which do not
have the intended `key`/`value` structure.
Recommendation: Use `DynamicMessage` only with trusted descriptors (via trusted
side channel source / config pushes). If your use-case requires handling
descriptors from an untrusted source, you must validate and sanitize them as you
would user provided SQL, and should additionally isolate that processing (such
as by sandboxing) if it is sensitive to DoS risks.
Caution: Usage of `DynamicMessage` with malicious descriptors reaching an RCE or
information leak would still be treated as an important bug for us to fix as
part of defense in depth. Any RCE or information leak concerns on this surface
should be reported via a
[draft GitHub Security Advisory](https://github.com/protocolbuffers/protobuf/security/advisories/new).
Reports of Denial of Service (including memory amplification, uncaught or
unexpected exceptions, and `CHECK` failures) that require an untrusted
descriptor are not considered security vulnerabilities, and may be filed as
[public issues](https://github.com/protocolbuffers/protobuf/issues) but may be
closed as "Won't Fix (Infeasible)" depending on the details of the report.
### Adversarial Application Code
Violating runtime API constraints or passing invalid arguments directly to a C++
API is considered an application integration error rather than a library
vulnerability.
Protobuf libraries do harden against the impact of certain classes of mistakes
being worse; for example, we often will panic if we can detect that
out-of-bounds memory reads will occur in some cases. This is considered
defense-in-depth and misuse is not considered a vulnerability.
Excepting the surfaces enumerated above as hardened, Protobuf APIs in
memory-safe languages reaching memory safety problems on 'bad' parameters is
considered an high priority bug, but typically not within scope for CVE
disclosure.
In languages like PHP, this means that use of PHP Protobuf in an unconstrained
multi-tenant system where malicious application code may try to attack other
jobs concurrently running is not within our threat model.
Examples:
* Passing negative or invalid buffer size parameters directly to
`ParseFromArray` in C++ is wrong application code. It may be hardened to
panic instead of risk out of bounds memory reads, but is not intended to be
gracefully handled as a malformed-wire-bytes input would be (following C++
idioms).
* In a memory-safe language like Python, if code like
`msg.repeatedField[-2147483649]` can reach a segfault, that is considered an
important bug to fix, but it is not considered to be within CVE scope.
### Differential Parsing (Gateway propagation of original payload)
Differential parsing is a risk stemming from two different libraries parsing the
same data with different interpretations.
In some contexts and for some formats differential parsing is considered a
security-sensitive topic. The primary risk is around flows that would validate
in a gateway, forwarding the data unmodified, and then a backend handles the
original payload and interprets the data differently, bypassing the intended
checks.
The Protobuf binary format is explicitly designed for propagation of unknown
fields, where the gateway may not be aware of the content at all and forwarding
will result in the next server who has an updated version of the schema will
corresponding parse to a different interpretation because it is aware of those
fields.
Additionally, there may be certain edge-case byte sequences where a few
different interpretations which may be considered acceptable within spec. Google
maintained runtimes will never encode these sequences, but they may successfully
parse them.
When using ProtoJSON format, the underlying JSON format itself contains
significant inherent ambiguities (as noted in ECMA-404 and RFC-8259, including
that there is no spec around the handling of duplicate keys and numeric
precision). As ProtoJSON is built on top of that foundation, ProtoJSON inherits
these ambiguities where certain sequences may have multiple different spec
permissible implementations. Spec Protobuf implementations strongly attempt to
avoid ever encoding such sequences (including that they always quote large
int64s, and don't emit duplicate fields), but the parsing behavior may differ in
such sequences and ProtoJSON cannot spec behavior which is unimplementable when
using ecosystem of JSON parsers.
For both formats, an architecture where a gateway performs validation and
forwards the original user request to a second server which reparses the user's
request but presumes validation has already occurred is outside of our threat
model.
For best security practice, it is recommended to:
* Use a different set of message schema for your public API and internal
messages. Besides the security benefits, this decoupling also allows for
easier evolution of your system, where public APIs often need to change
slowly but internal ones can evolve faster.
* Where you do propagate the same message type, always prefer for the gateway
to parse and reserialize instead of forwarding the original payload
verbatim, as this will commonly normalize edge case byte sequences.
* Wherever possible to have each microservice verify any relevant ACLs for
actions it is taking based on its interpretation of the request rather than
rely on gateway validation.
### Specific depth cap exceeded but without uncontrolled recursion
Protobuf parsers apply depth limits (which are configurable): the purpose of
these depth limits is to prevent resource exhaustion issues stemming from
uncontrolled recursion.
To generally maintain consistent and interoperable behavior, we intend these
depth limits to be consistent in behavior in what payloads will be accepted or
rejected for a given integer depth.
Issues where an edge case is successfully parsed deeper than the exact intended
limit are viewed as simple bugs as long as it does not expose meaningful
resource exhaustion risks.
We welcome reports and patches for issues of that nature, but do not view it as
a security concern and so these issues can be filed via our public GitHub Issues
flow.
### Canonical Representation and Signature Verification
There is **no canonical representation** of Protobuf messages.
* **Deterministic Serialization:** Many runtimes support deterministic
serialization, which guarantees that a given build of a binary will
serialize the same message to the same sequence of bytes. However,
deterministic does not mean canonical: rebuilding the binary, changing the
compiler version, or minor schema modifications can legally result in an
alternate serialized byte representation that would have the same
interpretation when parsed. For more details, see
[Protobuf Serialization is Not Canonical](https://protobuf.dev/programming-guides/serialization-not-canonical/).
* **Recommendation:** Do not use the serialized byte output of Protobuf
messages to compute stable cryptographic signatures. You may still sign a
given encoded byte sequence, knowing that there are other byte sequences
that would be equally valid representations of the same message. If you need
a stable signature of a given message, you must define and implement your
own canonicalization specification over the parsed message fields and not
over the encoded messages.
### Risks if wire bytes are modified in-transit
In terms of transport security, Protobuf is functionally equivalent to a
plaintext format: the encoding has no built-in signing or other integrity
features.
Best practice is to transport Protobuf encoded data over https. If signing or
other integrity features are needed, it is expected to be done in the layers
built on top of the Protobuf libraries.
### API surfaces which not intended for direct public use
Protobuf has APIs which are not advertised or intended for direct public use.
These APIs may have non-obvious invariants for how they must be used.
Most notably, `upb` is a library which is used as an implementation detail API
of our other Protobuf libraries to use. `upb` itself is a highly optimized C
library which requires callers maintain invariants to be sound.
Security issues may arise if our language-specific runtimes which use `upb` do
not maintain those necessary invariants, or if `upb` has reachable bad behavior
when all intended invariants are maintained. However, it is not considered a
security topic if arbitrary bad behavior may be reachable if `upb` APIs are
directly misused (including that `upb's` APIs accept MiniDescriptors/MiniTables
which are considered trusted types, and so will have arbitrary behavior if those
types do not meet the intended invariants).
## Dependency Pinning
### In Builds of Protobuf
Due to the diversity of build systems used across the language bindings
supported by Protobuf, there is not a singular policy on how narrowly
dependencies are described. In many cases, Protobuf's dependencies' versions are
allowed to float within the range of versions that also fit within our
[Support Matrix](https://protobuf.dev/support/version-support/). In no cases do
we require that dependencies of Protobuf are pinned to a specific commit. Users
building directly from source are strongly advised to review what versions are
downloaded and installed by the dependency management tools used by their local
build system.
### In Build Infrastructure
Protobuf's build infrastructure may treat other Google-controlled open source
projects as inherently trusted. For these trusted resources we allowing version
pinning using release tags rather than SHAs. For example lines similar to:
`uses: protocolbuffers/protobuf-ci/checkout@v6`
can be found in our GitHub Actions configuration files. Resources that are not
Google-controlled should be pinned with SHAs of the commit to prevent supply
attacks, followed with a comment documenting the version:
`uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2`