Repository navigation
Conversation
|
Hi @JasonLunn @bshaffer @amanda-tarafa - following up on this PR. Since I'm an external contributor, the Could one of you take a look and:
Happy to address any feedback. Thanks! |
6a395e9 to
cb897ec
Compare
|
This PR addresses the issue described in #28679. |
bdd46d1 to
ce42e58
Compare
esrauchg
left a comment
There was a problem hiding this comment.
Thank your for the PR! Just one small adjustment recommended and then I think this will be good to go.
| // @ - verbatim strings | ||
| // newlines - line injection | ||
| // null bytes - string truncation | ||
| if (c == ';' || c == '{' || c == '}' || c == '(' || c == ')' || |
There was a problem hiding this comment.
(I think I accidentally commented on the specific commit instead of the PR, so posting again here)
Nit: I think
constexpr absl::CharSet kBannedBackageChars(";{}()'"`$# ... ");
And then just
if (kBannedBackageChars.contains(c))
(Same comment for other files)
There was a problem hiding this comment.
Done — switched all three validators to use constexpr absl::CharSet. Much cleaner, thanks for the suggestion.
| "protocol_compiler --proto_path=$tmpdir --ruby_out=$tmpdir foo.proto"); | ||
|
|
||
| // Newlines in the option value should be rejected (either by protoc parser | ||
| // or by our validator if they arrive as literal \n bytes). |
There was a problem hiding this comment.
Was there a missing test here?
There was a problem hiding this comment.
Good catch, yeah that was missing. Added ExpectErrorSubstring for it now.
d125fa7 to
ea270ce
Compare
|
Updated — both changes addressed in the latest push:
Let me know if anything else needs adjusting. |
esrauchg
left a comment
There was a problem hiding this comment.
Just one further nit and then this is good to go. Thanks for the contribution!
| // to allow Unicode identifiers while blocking dangerous characters. | ||
| bool IsValidCSharpNamespace(absl::string_view ns, std::string* error) { | ||
| for (char c : ns) { | ||
| if (c == '\0' || kBannedNamespaceChars.contains(c)) { |
There was a problem hiding this comment.
You pulled off \0 because its annoying to embed one in a char* literal, I think you can actually write it like this though:
constexpr absl::CharSet kBannedNamespaceChars =
absl::CharSet(";{}()'"`$#=<>|&[]/\@\n\r!?*~^%,") | absl::CharSet::Char('\0');
(In modern C++ you could also do it with a stringview literal which works cleanly with embedded \0, like "a\0b"sv, but I think we avoid those in our style guide)
There was a problem hiding this comment.
Nice, didn't know about the union operator on CharSet. Updated all three to use it.
ea270ce to
af1e496
Compare
|
Updated — using |
|
Hi @esrauchg, thanks for the approval! Could the safe for tests label be added so CI can run? Just want to make sure everything's green before merge. Appreciate it! |
af1e496 to
4144598
Compare
|
Hi @esrauchg, I've pushed a fix adding the missing |
|
All checks passing now, ready to merge whenever you get a chance. |
|
@esrauchg Could you also apply the |
|
@esrauchg Friendly ping — this has been approved and CI is green for a week now. Could you merge when you get a chance? @mkruskal-google @haberman Tagging in case esrauchg is unavailable — PR is approved, all checks passing, just needs someone to hit merge. Thanks! |
|
Hi @bshaffer and @shaod2, I hope you are both doing well. I wanted to respectfully follow up on this pull request. The review has been approved and all CI checks are passing, so I believe everything is in order for a merge. I completely understand how busy things can get, and I truly appreciate your time. Please do let me know if there is anything further you need from me. |
|
Sorry I was out of office but am now back. Unfortunately our github flow is kind of sucky (for 'good reasons') and stuff gets stuck sometimes. Just for 'how the sausage is made is made', we actually have a separate internal repo that is our source of truth for this code (and other pieces of code that aren't necessarily all open sourced). The commit flow when we accept external PRs is that the PR gets into a state that a bot will pick up and create a second internal PR (which also does things like run additional internal only tests that aren't in the external repo), and after that PR is committed it will sync back out and then the original PR marked closed. It looks like this maybe is stuck because the branch has a conflict, can you merge? |
|
Hey @esrauchg Noticed this branch now shows a merge conflict with main (in src/google/protobuf/compiler/php/generator_unittest.cc) looks like main added a new Edition2026Fails test right next to the namespace-validation tests this PR adds. No actual logic conflict, just adjacent insertions. Happy to rebase and keep both blocks. let me know if you'd prefer I do that now. |
|
Yeah, do you mind fixing the conflicts? The merge conflict showed up because I clicked a button to attempt rebase (which is a behavior that PRs sent to us opt into letting maintainers of our repo do to your fork that you're sending the PR from, usually it goes cleanly and doesn't need a manual touch). |
Add input validation for language-specific namespace/package options (php_namespace, php_metadata_namespace, ruby_package, csharp_namespace) in their respective code generators. These options are currently emitted directly into generated source code without character validation. Specially crafted .proto files could inject arbitrary code into generated PHP, Ruby, or C# output. Each generator validates its namespace option using a constexpr absl::CharSet denylist of injection-enabling characters, rejecting invalid input with a clear error message before emitting code. A denylist (rather than an ASCII allowlist) is used so that Unicode identifiers, common in some language ecosystems, remain supported while still blocking dangerous characters. - PHP generator: validates php_namespace and php_metadata_namespace - Ruby generator + RBS generator: validates ruby_package - C# generator: validates csharp_namespace Unit tests added for all three generators verifying rejection of malicious input and acceptance of valid namespaces.
3856b6e to
b2f333a
Compare
|
@esrauchg Rebased on current main and resolved the conflict in generator_unittest.cc Branch is up to date and pushed to my fork, should show as mergeable now. Could you also apply the "safe for tests" label so CI can run? |
|
@esrauchg approval and checks are all green now. This should be ready to merge whenever you get a chance. |
## Summary Add input validation for language-specific namespace/package options (`php_namespace`, `php_metadata_namespace`, `ruby_package`, `csharp_namespace`) in their respective code generators. These options are currently emitted directly into generated source code without character validation. Specially crafted `.proto` files can inject arbitrary code into generated PHP, Ruby, or C# output. Fixes #28679 ## Changes - **PHP generator**: Validates `php_namespace` and `php_metadata_namespace` — denies injection-enabling characters (`;`, `{}`, `()`, quotes, `$`, etc.) while allowing Unicode identifiers - **Ruby generator + RBS generator**: Validates `ruby_package` — same denylist approach - **C# generator**: Validates `csharp_namespace` — same denylist approach Each generator rejects invalid options with a clear error message before emitting code. ## Motivation When processing untrusted `.proto` files (e.g., from third-party dependencies, shared proto registries, or CI pipelines), these options can contain characters like semicolons, braces, or backticks that break out of the intended code structure. Validating at generation time prevents this class of injection. ## Testing The denied character sets match characters that can never be valid in a namespace/package identifier in any of the target languages. Unicode identifiers are allowed. Existing `.proto` files with valid namespace options are unaffected. Closes #28667 COPYBARA_INTEGRATE_REVIEW=#28667 from thalha-a9:fix/validate-codegen-namespace-options b2f333a FUTURE_COPYBARA_INTEGRATE_REVIEW=#28667 from thalha-a9:fix/validate-codegen-namespace-options b2f333a PiperOrigin-RevId: 957188071
## Summary Add input validation for language-specific namespace/package options (`php_namespace`, `php_metadata_namespace`, `ruby_package`, `csharp_namespace`) in their respective code generators. These options are currently emitted directly into generated source code without character validation. Specially crafted `.proto` files can inject arbitrary code into generated PHP, Ruby, or C# output. Fixes #28679 ## Changes - **PHP generator**: Validates `php_namespace` and `php_metadata_namespace` — denies injection-enabling characters (`;`, `{}`, `()`, quotes, `$`, etc.) while allowing Unicode identifiers - **Ruby generator + RBS generator**: Validates `ruby_package` — same denylist approach - **C# generator**: Validates `csharp_namespace` — same denylist approach Each generator rejects invalid options with a clear error message before emitting code. ## Motivation When processing untrusted `.proto` files (e.g., from third-party dependencies, shared proto registries, or CI pipelines), these options can contain characters like semicolons, braces, or backticks that break out of the intended code structure. Validating at generation time prevents this class of injection. ## Testing The denied character sets match characters that can never be valid in a namespace/package identifier in any of the target languages. Unicode identifiers are allowed. Existing `.proto` files with valid namespace options are unaffected. Closes #28667 COPYBARA_INTEGRATE_REVIEW=#28667 from thalha-a9:fix/validate-codegen-namespace-options b2f333a FUTURE_COPYBARA_INTEGRATE_REVIEW=#28667 from thalha-a9:fix/validate-codegen-namespace-options b2f333a PiperOrigin-RevId: 957188071
php_class_prefix is pasted directly into `class <prefix><Name> extends ...`,
so a prefix containing `{`, `}` or `;` can close the generated declaration and
open an attacker-defined one. The result is still valid PHP and the real
message class is still emitted, so nothing fails to build.
protocolbuffers#28667 added this validation for php_namespace and php_metadata_namespace but
not for php_class_prefix, which is read from names.cc rather than
php_generator.cc.
IsValidPhpNamespace becomes IsValidPhpOption so the error names the offending
option. The message still begins "Invalid character", so the tests added in
protocolbuffers#28667 are unaffected.
## Summary Add input validation for language-specific namespace/package options (`php_namespace`, `php_metadata_namespace`, `ruby_package`, `csharp_namespace`) in their respective code generators. These options are currently emitted directly into generated source code without character validation. Specially crafted `.proto` files can inject arbitrary code into generated PHP, Ruby, or C# output. Fixes #28679 ## Changes - **PHP generator**: Validates `php_namespace` and `php_metadata_namespace` — denies injection-enabling characters (`;`, `{}`, `()`, quotes, `$`, etc.) while allowing Unicode identifiers - **Ruby generator + RBS generator**: Validates `ruby_package` — same denylist approach - **C# generator**: Validates `csharp_namespace` — same denylist approach Each generator rejects invalid options with a clear error message before emitting code. ## Motivation When processing untrusted `.proto` files (e.g., from third-party dependencies, shared proto registries, or CI pipelines), these options can contain characters like semicolons, braces, or backticks that break out of the intended code structure. Validating at generation time prevents this class of injection. ## Testing The denied character sets match characters that can never be valid in a namespace/package identifier in any of the target languages. Unicode identifiers are allowed. Existing `.proto` files with valid namespace options are unaffected. Closes #28667 COPYBARA_INTEGRATE_REVIEW=#28667 from thalha-a9:fix/validate-codegen-namespace-options b2f333a PiperOrigin-RevId: 957224133
…29130) ## Summary Follow-up to #28667, which added character validation for `php_namespace`, `php_metadata_namespace`, `ruby_package` and `csharp_namespace` because they are emitted directly into generated source without validation. Two options in the same family were not covered and are still unvalidated on `main`: | option | read at | validated before this PR | |---|---|---| | `php_class_prefix` | `php/names.cc` (`ClassNamePrefixImpl`) | no | | `objc_class_prefix` | `objectivec/names.cc` (`FileClassPrefix`) | no | `php_class_prefix` is easy to miss by inspection: the guard added in #28667 and both of its call sites are in `php_generator.cc`, while `php_class_prefix` is read from `names.cc`. ## What happens today **PHP.** The generator emits `class <php_class_prefix><Name> extends ...`, so a prefix that closes the declaration and opens its own is emitted verbatim: ```proto option php_class_prefix = "Pfx{} class Injected { public static function pwn() { return 'INJECTED'; } } class "; ``` `protoc --php_out` exits 0 and the generated file contains: ```php class Pfx{} class Injected { public static function pwn() { return 'INJECTED'; } } class M extends \Google\Protobuf\Internal\Message ``` That is valid PHP (`php -l`: no syntax errors), the injected class is defined and its code runs, and `Tp\M` is still generated correctly — so nothing fails to build and nothing looks wrong. **Objective-C.** `FileClassPrefix` returns the option verbatim, so with `objc_class_prefix = "A\";void pwn(){}//"` the value lands in `@interface` / `@implementation` declarations, a `typedef struct` tag and the `.prefix = "..."` string literal. I have not built the resulting Objective-C, so I am claiming unvalidated emission here rather than a working payload. Worth noting separately: a prefix check already exists, but it validates style rather than characters, so it warns about a legitimate short prefix and says nothing about a hostile one: ``` objc_class_prefix = "AB" -> warning: Invalid 'option objc_class_prefix = "AB";' objc_class_prefix = "A\";void pwn(){}//" -> no diagnostic at all ``` ## Changes - **PHP generator** — generalised `IsValidPhpNamespace` to `IsValidPhpOption(value, option_name, error)` so the error names the offending option, and applied it to `php_class_prefix` alongside the two existing options. The message still begins `Invalid character`, so the tests added in #28667 are unaffected. - **Objective-C generator** — new `ValidateObjCClassPrefixChars()` rejects prefixes containing anything outside `[A-Za-z0-9_]`, run from `ValidateObjCClassPrefixes()` over every file ahead of the expected-prefixes opt-outs. - Tests for both, in the reject/accept pattern used by #28667. ## Notes for review Three things I'd rather flag than have you find: 1. **The Objective-C check is an allowlist, not a denylist.** The prefix is pasted into C identifiers, where nothing outside `[A-Za-z0-9_]` is ever valid, so an allowlist closes the class rather than chasing characters. I checked every `objc_class_prefix` in this repo (including the deliberately-empty one) and none would be rejected. Happy to switch to the `absl::CharSet` denylist used for PHP/Ruby/C# if you'd prefer consistency over precision. 2. **Placement matters.** The character check runs in `ValidateObjCClassPrefixes()` over every file, ahead of both documented opt-outs (`expected_prefixes_path=-` and `expected_prefixes_suppressions`), since disabling the naming *policy* should not disable a correctness check. There is a regression test per opt-out. 3. **Java is gated, but to a much narrower bar** — `java/file.cc` rejects only `;`, `\r`, `\n` and space, and its comment says it checks "only a subset". Since Java accepts `/**/` as a whitespace substitute, `java_outer_classname = "Evil/**/implements/**/Cloneable"` and `"Evil/*"` are both emitted into the class declaration on current `main`. I could not get code execution out of that (`;` blocks statement injection); the demonstrated effect is generated code that will not compile. **Not changed in this PR** — mentioning it in case you want the bar aligned, and happy to do it in a follow-up. Also, for whatever it's worth to release planning: #28667 itself is not in any released binary yet — the validation string is absent from both `protoc 35.1` and `protoc 36.0-rc2`, since it landed after the v36 branch was cut. ## Testing `src/google/protobuf/compiler/php/generator_unittest.cc` — `InvalidPhpClassPrefixRejected`, `ValidPhpClassPrefixAccepted`. `src/google/protobuf/compiler/objectivec/generator_unittest.cc` (new file, plus its `cc_test` target) — `InvalidObjCClassPrefixRejected`, `InvalidObjCClassPrefixRejectedWithExpectedPrefixesDisabled`, `InvalidObjCClassPrefixRejectedWhenSuppressed`, `ValidObjCClassPrefixAccepted`, `ObjCClassPrefixWithUnderscoreAccepted`. The two opt-out tests fail with the character check in its original location inside `ValidateObjCClassPrefix()`, and pass with it moved. Both directions are covered, so the tests fail if the validation is removed *and* if it is made too strict. --- _Both tests were run locally against this branch (`bazel test //src/google/protobuf/compiler/php:generator_unittest //src/google/protobuf/compiler/objectivec:generator_unittest`), and re-run with the two guards reverted to confirm they fail without the fix._ Closes #29130 COPYBARA_INTEGRATE_REVIEW=#29130 from BarakSrour:validate-class-prefix-options a2bbd8f PiperOrigin-RevId: 966161028
Summary
Add input validation for language-specific namespace/package options (
php_namespace,php_metadata_namespace,ruby_package,csharp_namespace) in their respective code generators.These options are currently emitted directly into generated source code without character validation. Specially crafted
.protofiles can inject arbitrary code into generated PHP, Ruby, or C# output.Fixes #28679
Changes
php_namespaceandphp_metadata_namespace— denies injection-enabling characters (;,{},(), quotes,$, etc.) while allowing Unicode identifiersruby_package— same denylist approachcsharp_namespace— same denylist approachEach generator rejects invalid options with a clear error message before emitting code.
Motivation
When processing untrusted
.protofiles (e.g., from third-party dependencies, shared proto registries, or CI pipelines), these options can contain characters like semicolons, braces, or backticks that break out of the intended code structure. Validating at generation time prevents this class of injection.Testing
The denied character sets match characters that can never be valid in a namespace/package identifier in any of the target languages. Unicode identifiers are allowed. Existing
.protofiles with valid namespace options are unaffected.