Skip to content
Open
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 @@ -168,7 +168,7 @@ static String prettyQName(QName qname) {
}
String result = qname.getLocalPart();
if (qname.getNamespaceURI() != null) {
result += "(@" + qname.getNamespaceURI() + ")";
result += "(@" + javaCommentEscape(qname.getNamespaceURI()) + ")";
}
return result;
}
Expand Down Expand Up @@ -345,7 +345,7 @@ void printTopComment(SchemaType sType) throws IOException {
emit("/*");
if (sType.getName() != null) {
emit(" * XML Type: " + sType.getName().getLocalPart());
emit(" * Namespace: " + sType.getName().getNamespaceURI());
emit(" * Namespace: " + javaCommentEscape(sType.getName().getNamespaceURI()));
} else {
QName thename = null;

Expand All @@ -362,7 +362,7 @@ void printTopComment(SchemaType sType) throws IOException {
assert (thename != null);

emit(" * Localname: " + thename.getLocalPart());
emit(" * Namespace: " + thename.getNamespaceURI());
emit(" * Namespace: " + javaCommentEscape(thename.getNamespaceURI()));
}
emit(" * Java type: " + sType.getFullJavaName());
emit(" *");
Expand Down Expand Up @@ -504,15 +504,21 @@ void printJavaDocParagraph(String s) throws IOException{
void printJavaDocBody(String doc) throws IOException{
// add some poor mans code injection protection
// this is not protecting against annotation based RCEs like CVE-2018-16621
String docClean = doc.trim()
.replace("\t", "")
.replace("*/", "* /");
String docClean = javaCommentEscape(doc.trim().replace("\t", ""));

for (String s : docClean.split("[\\n\\r]+")) {
emit(" * " + s);
}
}

public static String javaCommentEscape(String str)
{
// forbidden: */, and the backslash of a unicode escape - those are decoded
// before comments are recognized (JLS 3.3), so an escaped */ ends the
// comment too and the rest of the schema text is compiled as code
return str.replace("\\", "\\\\").replace("*/", "* /");
}

public static String javaStringEscape(String str)
{
// forbidden: \n, \r, \", \\.
Expand Down Expand Up @@ -1427,7 +1433,7 @@ void printStaticFields(SchemaProperty[] properties, Map<SchemaProperty, Identifi
for (SchemaProperty prop : properties) {
final QName name = prop.getName();
propMap.put(prop, new Identifier(propMap.size()));
emit("new QName(\"" + name.getNamespaceURI() + "\", \"" + name.getLocalPart() + "\"),");
emit("new QName(\"" + javaStringEscape(name.getNamespaceURI()) + "\", \"" + javaStringEscape(name.getLocalPart()) + "\"),");
countQSet = Math.max(countQSet, (prop.acceptedNames() == null ? 0 : prop.acceptedNames().length));
}
outdent();
Expand All @@ -1444,7 +1450,7 @@ void printStaticFields(SchemaProperty[] properties, Map<SchemaProperty, Identifi
emit("QNameSet.forArray( new QName[] { ");
indent();
for (QName qname : qnames) {
emit("new QName(\"" + qname.getNamespaceURI() + "\", \"" + qname.getLocalPart() + "\"),");
emit("new QName(\"" + javaStringEscape(qname.getNamespaceURI()) + "\", \"" + javaStringEscape(qname.getLocalPart()) + "\"),");
}
outdent();
emit("}),");
Expand Down
25 changes: 25 additions & 0 deletions src/test/java/compile/scomp/checkin/CompilationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,31 @@ void annotation2javadoc() throws Exception {
assertTrue(act.contains("* / heck, I'm smart"));
}

@Test
void schemaTextIsNotCompiledAsCode() throws Exception {
deltree(xbeanOutput("compile/scomp/codeinject"));
File srcdir = xbeanOutput("compile/scomp/codeinject/src");
File classesdir = xbeanOutput("compile/scomp/codeinject/classes");
Parameters params = new Parameters();
params.setXsdFiles(xbeanCase("schemacompiler/codeinject.xsd"));
params.setSrcDir(srcdir);
params.setClassesDir(classesdir);
params.setName("codeinject");
params.setCopyAnn(true);
assertTrue(SchemaCompiler.compile(params), "generated sources didn't compile");

// the quote in the target namespace has to stay inside the string literal
Path p = new File(srcdir, "codeinjectQ/impl/TImpl.java").toPath();
String act = new String(Files.readAllBytes(p), StandardCharsets.UTF_8);
assertTrue(act.contains("new QName(\"codeinject\\\"q\", \"single\")"), "namespace not escaped");

// the unicode escape in the documentation must not close the javadoc comment
try (Stream<Path> s = Files.walk(classesdir.toPath())) {
assertFalse(s.anyMatch(f -> "Pwned.class".equals(f.getFileName().toString())),
"class injected through the schema documentation");
}
}

//TESTENV:

private static void dumpErrors(List<XmlError> errors, PrintWriter out) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2004 The Apache Software Foundation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->

<!-- the target namespace carries a quote, which has to stay inside the generated
QName string literal, and the documentation carries a unicode escape, which
javac decodes to */ before it looks for comments -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="codeinject&quot;q"
targetNamespace="codeinject&quot;q"
elementFormDefault="qualified">

<xs:complexType name="T">
<xs:annotation>
<xs:documentation>a doc \u002a\u002f class Pwned {} /**</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="single" type="xs:string"/>
</xs:sequence>
</xs:complexType>

<xs:element name="root" type="tns:T"/>
</xs:schema>