Skip to content

AVRO-4313: [java] Tighten javaAnnotation string-literal validation in SpecificCompiler - #3892

Merged
RyanSkraba merged 3 commits into
apache:mainfrom
iemejia:AVRO-4313
Jul 26, 2026
Merged

AVRO-4313: [java] Tighten javaAnnotation string-literal validation in SpecificCompiler#3892
RyanSkraba merged 3 commits into
apache:mainfrom
iemejia:AVRO-4313

Conversation

@iemejia

@iemejia iemejia commented Jul 20, 2026

Copy link
Copy Markdown
Member

What is the purpose of the change

Avro copies a schema's javaAnnotation property verbatim into the Java
source it generates (e.g. @Deprecated, @SuppressWarnings("unchecked")).
Before doing so, SpecificCompiler checks the value with a regex to make sure
it only looks like a Java annotation and nothing more.

That check had a hole. The sub-pattern for a quoted string (like "unchecked")
accepted an unescaped double-quote inside the string body. So a single
string literal could run past its closing quote and swallow whatever came after
it, while the whole value still matched the "valid annotation" shape.

A crafted value like:

java.lang.SuppressWarnings("x") static { System.exit(1); } @java.lang.SuppressWarnings("y")

therefore passed validation and was written straight into the generated class —
a code-injection at code-generation time.

What this PR changes

Tighten the string-literal grammar so its body may only contain:

  • a recognized escape sequence: \\ \" \n \t \f \b, or
  • any character that is not a quote, backslash, or line terminator.

Now an unescaped quote ends the literal, so the injected trailing tokens fail
validation and the value is rejected instead of emitted. Line terminators
(CR, LF, NEL, LS, PS) are excluded too, so a value can't span multiple lines in
the generated source.

Legitimate annotations still validate: SuppressWarnings("unchecked"),
Deprecated(forRemoval = true, since = "forever"), and values with escaped
quotes.

Verifying this change

  • Added annotationCannotBreakOutViaStringLiteral in TestSpecificCompiler:
    it feeds the crafted value above alongside a normal annotation, and asserts
    the injected code is absent from every generated file while the valid
    annotation is still emitted. It fails on the old grammar and passes on the new.
  • The existing docsAreEscaped_avro4053 test and the full compiler module
    suite continue to pass.

Documentation

  • Does this pull request introduce a new feature? (no)
  • If yes, how is the feature documented? (not applicable)

… SpecificCompiler

The string-literal grammar used to validate javaAnnotation values accepted
an unescaped quote inside the literal body, letting a single literal span
past its intended closing quote and absorb surrounding tokens. Constrain the
body to recognized escape sequences or characters that are not a quote,
backslash, or line terminator, and add a regression test.
@github-actions github-actions Bot added the Java Pull Requests for Java binding label Jul 20, 2026
@iemejia
iemejia requested a review from Copilot July 20, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses AVRO-4313 by tightening validation of javaAnnotation values in the Java SpecificCompiler, preventing crafted annotation strings from abusing overly-permissive string-literal matching to inject additional Java tokens into generated sources.

Changes:

  • Tighten the PATTERN_STRING regex used by SpecificCompiler annotation validation to disallow unescaped quotes/backslashes and line breaks in string literals.
  • Add a regression test that attempts to inject extra declarations via a crafted javaAnnotation value and asserts it is not emitted verbatim, while a valid annotation still is.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java Tightens the regex used to validate annotation string literals to prevent quote-based breakout/injection.
lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java Adds a regression test ensuring crafted javaAnnotation values can’t escape a string literal and inject code into generated output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Also reject NEL, LS and PS in addition to CR and LF so an annotation value
cannot span multiple lines in the generated source.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment on lines +1045 to +1056
Collection<SpecificCompiler.OutputFile> outputs = new SpecificCompiler(SchemaParser.parseSingle(jsonSchema))
.compile();
for (SpecificCompiler.OutputFile outputFile : outputs) {
// The payload is echoed (safely escaped) inside the SCHEMA$ string constant,
// so we must distinguish that from a verbatim emission as code. Real injected
// code would carry unescaped quotes; the schema literal escapes them as \".
assertFalse(outputFile.contents.contains("SuppressWarnings(\"x\") static { System.exit(1); }"),
"Code injection present? " + outputFile.contents);
// The legitimate annotation in the same list must still be emitted.
assertTrue(outputFile.contents.contains("@SuppressWarnings(\"unchecked\")"),
"Valid annotation missing? " + outputFile.contents);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 90299cc. The loop now asserts the injection payload is absent from every generated file, and separately asserts the valid annotation is emitted in at least one output (via an accumulator checked after the loop), so the test no longer depends on the number of output files.

Assert the injection payload is absent from every generated file and the
valid annotation is emitted in at least one, rather than requiring it in
each output file.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@iemejia
iemejia requested a review from RyanSkraba July 20, 2026 18:04
@RyanSkraba
RyanSkraba merged commit 44f7aa3 into apache:main Jul 26, 2026
9 checks passed
@RyanSkraba

Copy link
Copy Markdown
Contributor

Cherry-picked to branch-1.12.

RyanSkraba pushed a commit that referenced this pull request Jul 26, 2026
… SpecificCompiler (#3892)

* AVRO-4313: [java] Tighten javaAnnotation string-literal validation in SpecificCompiler

The string-literal grammar used to validate javaAnnotation values accepted
an unescaped quote inside the literal body, letting a single literal span
past its intended closing quote and absorb surrounding tokens. Constrain the
body to recognized escape sequences or characters that are not a quote,
backslash, or line terminator, and add a regression test.

* AVRO-4313: Exclude all line terminators from annotation string literals

Also reject NEL, LS and PS in addition to CR and LF so an annotation value
cannot span multiple lines in the generated source.

* AVRO-4313: Make injection regression test robust to multiple outputs

Assert the injection payload is absent from every generated file and the
valid annotation is emitted in at least one, rather than requiring it in
each output file.
@iemejia
iemejia deleted the AVRO-4313 branch July 31, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Java Pull Requests for Java binding

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants