Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
private static final String FILE_HEADER = "/*\n * Autogenerated by Avro\n *\n * DO NOT EDIT DIRECTLY\n */\n";

public SpecificCompiler(Protocol protocol) {
this();

Check warning on line 151 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-latest)

[this-escape] possible 'this' escape before subclass is fully initialized
// enqueue all types
for (Schema s : protocol.getTypes()) {
enqueue(s);
Expand All @@ -161,7 +161,7 @@
}

public SpecificCompiler(Collection<Schema> schemas) {
this();

Check warning on line 164 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-latest)

[this-escape] possible 'this' escape before subclass is fully initialized
for (Schema schema : schemas) {
enqueue(schema);
}
Expand All @@ -182,7 +182,7 @@
this.templateDir = System.getProperty("org.apache.avro.specific.templates",
"/org/apache/avro/compiler/specific/templates/java/classic/");
initializeVelocity();
initializeSpecificData();

Check warning on line 185 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-latest)

[this-escape] previous possible 'this' escape happens here via invocation

Check warning on line 185 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-latest)

[this-escape] previous possible 'this' escape happens here via invocation
}

/**
Expand Down Expand Up @@ -427,7 +427,7 @@
}

private void initializeSpecificData() {
addLogicalTypeConversions(specificData);

Check warning on line 430 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-latest)

[this-escape] previous possible 'this' escape happens here via invocation

Check warning on line 430 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-latest)

[this-escape] previous possible 'this' escape happens here via invocation
specificData.addLogicalTypeConversion(new Conversions.DecimalConversion());
}

Expand Down Expand Up @@ -940,7 +940,7 @@
// with error(s)
return "void";
}
default:

Check warning on line 943 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-24.04-arm)

[fallthrough] possible fall-through into case

Check warning on line 943 in lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

View workflow job for this annotation

GitHub Actions / Java Test (ubuntu-latest)

[fallthrough] possible fall-through into case
return javaType(schema, false);
}
}
Expand Down Expand Up @@ -1054,7 +1054,13 @@
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.
// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,33 @@ 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();
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);
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) {
int count = 0;
for (Matcher matcher = pattern.matcher(textToSearch); matcher.find();) {
Expand Down
Loading