iemejia commented on code in PR #3892:
URL: https://github.com/apache/avro/pull/3892#discussion_r3616456736


##########
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]|(?<!\\\\).)*\"";
+  // A string literal is a quote, a body of escape sequences or characters that
+  // 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])*\"";

Review Comment:
   Good catch — fixed in 2a0f2ad. The character class now also excludes NEL 
(`\x85`), LS (`\x{2028}`) and PS (`\x{2029}`) in addition to CR and LF, so no 
line terminator can appear unescaped inside the string literal. Verified that 
annotation values containing these characters are now rejected while legitimate 
annotations still validate.



##########
lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java:
##########
@@ -1031,6 +1031,31 @@ void docsAreEscaped_avro4053() {
     }
   }
 
+  @Test
+  void annotationCannotBreakOutViaStringLiteral() {
+    // A crafted javaAnnotation value tries to terminate the first annotation,
+    // inject arbitrary declarations plus a static initializer, then reopen a
+    // second valid annotation. It relies on a string literal spanning past its
+    // intended closing quote. Such values must be rejected, not emitted 
verbatim.
+    String jsonSchema = "{\n" + "  \"type\": \"record\",\n" + "  \"name\": 
\"Injected\",\n"
+        + "  \"javaAnnotation\": [\n"
+        + "    \"java.lang.SuppressWarnings(\\\"x\\\") static { 
System.exit(1); } @java.lang.SuppressWarnings(\\\"y\\\")\",\n"
+        + "    \"SuppressWarnings(\\\"unchecked\\\")\"\n" + "  ],\n" + "  
\"fields\": [\n"
+        + "    {\"name\": \"value\", \"type\": \"string\"}\n" + "  ]\n" + "}";
+    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);
+    }

Review Comment:
   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.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to