undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8303623
|
||||
* @modules jdk.compiler/com.sun.tools.javac.code
|
||||
* @summary Compiler should disallow non-standard UTF-8 string encodings
|
||||
*/
|
||||
|
||||
import com.sun.tools.javac.code.Source;
|
||||
import com.sun.tools.javac.Main;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class InvalidModifiedUtf8Test {
|
||||
|
||||
//
|
||||
// What this test does (repeatedly):
|
||||
// 1. Compile a Java source file for ClassX normally
|
||||
// 2. Modify the UTF-8 inside the ClassX classfile so that it is
|
||||
// still valid structurally but uses a non-standard way of
|
||||
// encoding some character (according to "Modified UTF-8").
|
||||
// 3. Compile a Java source file for RefClass that references ClassX
|
||||
// 4. Verify that the compiler gives a "bad UTF-8" error
|
||||
//
|
||||
|
||||
// We change c3 a8 -> c3 e8 (illegal second byte not of the form 0x10xxxxxx)
|
||||
private static final String SOURCE_0 = """
|
||||
interface CLASSNAME {
|
||||
void ABC\u00e8(); // encodes to: 41 42 43 c3 a8
|
||||
}
|
||||
""";
|
||||
|
||||
// We change e1 80 80 -> e1 80 40 (illegal third byte not of the form 0x10xxxxxx)
|
||||
private static final String SOURCE_1 = """
|
||||
interface CLASSNAME {
|
||||
void ABC\u1000(); // encodes to: 41 42 43 e1 80 80
|
||||
}
|
||||
""";
|
||||
|
||||
// We change c4 80 -> c1 81 (illegal two-byte encoding for one-byte value)
|
||||
private static final String SOURCE_2 = """
|
||||
interface CLASSNAME {
|
||||
void ABC\u0100(); // encodes to: 41 42 43 c4 00
|
||||
}
|
||||
""";
|
||||
|
||||
// We change e1 80 80 -> e0 84 80 (illegal three-byte encoding for two-byte value)
|
||||
private static final String SOURCE_3 = """
|
||||
interface CLASSNAME {
|
||||
void ABC\u1000(); // encodes to: 41 42 43 e1 80 80
|
||||
}
|
||||
""";
|
||||
|
||||
// We change 44 -> 00 (illegal one-byte encoding of 0x0000)
|
||||
private static final String SOURCE_4 = """
|
||||
interface CLASSNAME {
|
||||
void ABCD(); // encodes to: 41 42 43 44
|
||||
}
|
||||
""";
|
||||
|
||||
// We change 43 44 -> e1 80 (illegal truncated three-byte encoding)
|
||||
private static final String SOURCE_5 = """
|
||||
interface CLASSNAME {
|
||||
void ABCD(); // encodes to: 41 42 43 44
|
||||
}
|
||||
""";
|
||||
|
||||
// This is the source file that references one of the above
|
||||
private static final String REF_SOURCE = """
|
||||
interface RefClass extends CLASSNAME {
|
||||
}
|
||||
""";
|
||||
|
||||
private static TestCase[] TEST_CASES = new TestCase[] {
|
||||
new TestCase(0, SOURCE_0, "414243c3a8", "414243c3e8"),
|
||||
new TestCase(1, SOURCE_1, "414243e18080", "414243e18040"),
|
||||
new TestCase(2, SOURCE_2, "414243c480", "414243c181"),
|
||||
new TestCase(3, SOURCE_3, "414243e18080", "414243e08480"),
|
||||
new TestCase(4, SOURCE_4, "41424344", "41424300"),
|
||||
new TestCase(5, SOURCE_5, "41424344", "4142e180"),
|
||||
};
|
||||
|
||||
public static String bytes2string(byte[] array) {
|
||||
char[] buf = new char[array.length * 2];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
int value = array[i] & 0xff;
|
||||
buf[i * 2] = Character.forDigit(value >> 4, 16);
|
||||
buf[i * 2 + 1] = Character.forDigit(value & 0xf, 16);
|
||||
}
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
public static byte[] string2bytes(String string) {
|
||||
byte[] buf = new byte[string.length() / 2];
|
||||
for (int i = 0; i < string.length(); i += 2) {
|
||||
int value = Integer.parseInt(string.substring(i, i + 2), 16);
|
||||
buf[i / 2] = (byte)value;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void createSourceFile(String content, File file) throws IOException {
|
||||
System.err.println("creating: " + file);
|
||||
try (PrintStream output = new PrintStream(new FileOutputStream(file))) {
|
||||
output.println(content);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeFile(File file, byte[] content) throws IOException {
|
||||
System.err.println("writing: " + file);
|
||||
try (FileOutputStream output = new FileOutputStream(file)) {
|
||||
Files.write(file.toPath(), content);
|
||||
}
|
||||
}
|
||||
|
||||
private static void compileRefClass(File file, boolean expectSuccess, String expectedError) {
|
||||
final StringWriter diags = new StringWriter();
|
||||
final String[] params = new String[] {
|
||||
"-classpath",
|
||||
".",
|
||||
"-XDrawDiagnostics",
|
||||
file.toString()
|
||||
};
|
||||
System.err.println("compiling: " + file);
|
||||
int ret = Main.compile(params, new PrintWriter(diags, true));
|
||||
System.err.println("exit value: " + ret);
|
||||
String output = diags.toString().trim();
|
||||
if (!output.isEmpty())
|
||||
System.err.println("output:\n" + output);
|
||||
else
|
||||
System.err.println("no output");
|
||||
if (!expectSuccess && ret == 0)
|
||||
throw new AssertionError("compilation succeeded, but expected failure");
|
||||
else if (expectSuccess && ret != 0)
|
||||
throw new AssertionError("compilation failed, but expected success");
|
||||
if (expectedError != null && !diags.toString().contains(expectedError))
|
||||
throw new AssertionError("expected output \"" + expectedError + "\" not found");
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
|
||||
// Create source files
|
||||
for (TestCase test : TEST_CASES)
|
||||
test.createSourceFile();
|
||||
|
||||
// Compile source files
|
||||
for (TestCase test : TEST_CASES) {
|
||||
int ret = Main.compile(new String[] { test.sourceFile().toString() });
|
||||
if (ret != 0)
|
||||
throw new AssertionError("compilation of " + test.sourceFile() + " failed");
|
||||
}
|
||||
|
||||
// We should get warnings in JDK 21 and errors in any later release
|
||||
final boolean expectSuccess = Source.DEFAULT.compareTo(Source.JDK21) <= 0;
|
||||
|
||||
// Now compile REF_SOURCE against each classfile without and then with the modification.
|
||||
// When compiling without the modification, everything should be normal.
|
||||
// When compiling with the modification, an error should be generated.
|
||||
for (TestCase test : TEST_CASES) {
|
||||
System.err.println("==== TEST " + test.index() + " ====");
|
||||
|
||||
// Create reference source file
|
||||
final File refSource = new File("RefClass.java");
|
||||
createSourceFile(REF_SOURCE.replaceAll("CLASSNAME", test.className()), refSource);
|
||||
|
||||
// Do a normal compilation
|
||||
compileRefClass(refSource, true, null);
|
||||
|
||||
// Now corrupt the class file
|
||||
System.err.println("modifying: " + test.classFile());
|
||||
final File classFile = test.classFile();
|
||||
final byte[] data1 = Files.readAllBytes(classFile.toPath());
|
||||
final byte[] data2 = test.modify(data1);
|
||||
writeFile(classFile, data2);
|
||||
|
||||
// Do a corrupt compilation
|
||||
compileRefClass(refSource, expectSuccess, "compiler.misc.bad.utf8.byte.sequence.at");
|
||||
}
|
||||
}
|
||||
|
||||
// TestCase
|
||||
|
||||
static class TestCase {
|
||||
|
||||
final int index;
|
||||
final String source;
|
||||
final String match;
|
||||
final String replace;
|
||||
|
||||
TestCase(int index, String source, String match, String replace) {
|
||||
this.index = index;
|
||||
this.source = source.replaceAll("CLASSNAME", className());
|
||||
this.match = match;
|
||||
this.replace = replace;
|
||||
}
|
||||
|
||||
byte[] modify(byte[] input) {
|
||||
final byte[] output = string2bytes(bytes2string(input).replaceAll(match, replace));
|
||||
if (Arrays.equals(output, input))
|
||||
throw new AssertionError("modification of " + classFile() + " failed");
|
||||
return output;
|
||||
}
|
||||
|
||||
int index() {
|
||||
return index;
|
||||
}
|
||||
|
||||
String className() {
|
||||
return "Class" + index;
|
||||
}
|
||||
|
||||
File sourceFile() {
|
||||
return new File(className() + ".java");
|
||||
}
|
||||
|
||||
File classFile() {
|
||||
return new File(className() + ".class");
|
||||
}
|
||||
|
||||
void createSourceFile() throws IOException {
|
||||
InvalidModifiedUtf8Test.createSourceFile(source, sourceFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue