From bfa059fbd6406aa7da7d915561926d4ea988ee22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 20 Jul 2026 18:51:15 +0200 Subject: [PATCH 1/3] 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. --- .../compiler/specific/SpecificCompiler.java | 6 ++++- .../specific/TestSpecificCompiler.java | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java index ea1e1a11b55..579845d1eb2 100644 --- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java +++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java @@ -1054,7 +1054,11 @@ public String[] javaAnnotations(JsonProperties props) { private static final String PATTERN_IDENTIFIER_PART = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; private static final String PATTERN_IDENTIFIER = String.format("(?:%s(?:\\.%s)*)", PATTERN_IDENTIFIER_PART, PATTERN_IDENTIFIER_PART); - private static final String PATTERN_STRING = "\"(?:\\\\[\\\\\"ntfb]|(? 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); + } + } + private int countOccurrences(Pattern pattern, String textToSearch) { int count = 0; for (Matcher matcher = pattern.matcher(textToSearch); matcher.find();) { From 2a0f2ad8dcd7998395b155ee32ce971918e15663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 20 Jul 2026 19:25:19 +0200 Subject: [PATCH 2/3] 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. --- .../org/apache/avro/compiler/specific/SpecificCompiler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java index 579845d1eb2..4cfe1eea48c 100644 --- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java +++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java @@ -1058,7 +1058,9 @@ public String[] javaAnnotations(JsonProperties props) { // are not a quote, backslash or line terminator, and a closing quote. The body // must not be able to contain an unescaped quote, otherwise a single literal // could span past the intended closing quote and swallow surrounding tokens. - private static final String PATTERN_STRING = "\"(?:\\\\[\\\\\"ntfb]|[^\"\\\\\\r\\n])*\""; + // Line terminators (CR, LF, NEL, LS, PS) are excluded so a value cannot break + // across lines in the generated source. + private static final String PATTERN_STRING = "\"(?:\\\\[\\\\\"ntfb]|[^\"\\\\\\r\\n\\x85\\x{2028}\\x{2029}])*\""; private static final String PATTERN_NUMBER = "(?:\\((?:byte|char|short|int|long|float|double)\\))?[x0-9_.]*[fl]?"; private static final String PATTERN_LITERAL_VALUE = String.format("(?:%s|%s|true|false)", PATTERN_STRING, PATTERN_NUMBER); From 90299cc5ee906a814d1474feb32883df90768972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 20 Jul 2026 19:29:49 +0200 Subject: [PATCH 3/3] 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. --- .../avro/compiler/specific/TestSpecificCompiler.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java index 1d01c35dc55..918e28a8954 100644 --- a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java +++ b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java @@ -1044,16 +1044,18 @@ void annotationCannotBreakOutViaStringLiteral() { + " {\"name\": \"value\", \"type\": \"string\"}\n" + " ]\n" + "}"; Collection outputs = new SpecificCompiler(SchemaParser.parseSingle(jsonSchema)) .compile(); + boolean validAnnotationEmitted = false; 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 \". + // The injection must be absent from every generated file. 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); + validAnnotationEmitted |= outputFile.contents.contains("@SuppressWarnings(\"unchecked\")"); } + // The legitimate annotation in the same list must still be emitted somewhere. + assertTrue(validAnnotationEmitted, "Valid annotation missing from generated output"); } private int countOccurrences(Pattern pattern, String textToSearch) {