diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4feab65..f5f551a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,10 +5,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Set up JDK 1.7 + - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: - java-version: 1.7 + java-version: 1.8 - name: Build with Maven run: mvn package --file pom.xml - name: Upload Artifacts diff --git a/.gitignore b/.gitignore index e8acb36..841137e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ hs_err_pid* # IntelliJ .idea/ + +# result of build +target/ diff --git a/README.md b/README.md index 6ddf3b3..94f4b20 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,4 @@ Supported fields for the `option` list are: * `nobigmath` — disables `BigInteger` and `BigDecimal`, raising [appropriate exceptions](https://github.com/DMOJ/java-sandbox-agent/blob/master/src/main/java/ca/dmoj/java/BigIntegerDisallowedException.java) if they are used * `unicode` — encodes `System.out` as UTF-8 instead of ASCII, sacrificing performance for Unicode support * `nobuf` — sets `System.out` as being line-buffered, for interactive problems +* `unsafe` — enables `sun.misc.Unsafe`, which is disabled by default diff --git a/pom.xml b/pom.xml index 9b666f4..d3d37ee 100644 --- a/pom.xml +++ b/pom.xml @@ -20,8 +20,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 @@ -39,4 +39,4 @@ - \ No newline at end of file + diff --git a/src/main/java/ca/dmoj/java/DisallowedClassRule.java b/src/main/java/ca/dmoj/java/DisallowedClassRule.java new file mode 100644 index 0000000..6caf0a3 --- /dev/null +++ b/src/main/java/ca/dmoj/java/DisallowedClassRule.java @@ -0,0 +1,23 @@ +package ca.dmoj.java; + +public class DisallowedClassRule { + protected interface ExceptionFactory { + Exception create(); + } + + protected String className; + protected ExceptionFactory exception; + + public DisallowedClassRule(String className, ExceptionFactory exception) { + this.className = className; + this.exception = exception; + } + + public String getClassName() { + return className; + } + + public Exception getException() { + return exception.create(); + } +} diff --git a/src/main/java/ca/dmoj/java/DisallowedClassesClassFileTransformer.java b/src/main/java/ca/dmoj/java/DisallowedClassesClassFileTransformer.java new file mode 100644 index 0000000..253a3aa --- /dev/null +++ b/src/main/java/ca/dmoj/java/DisallowedClassesClassFileTransformer.java @@ -0,0 +1,34 @@ +package ca.dmoj.java; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; + +public class DisallowedClassesClassFileTransformer implements ClassFileTransformer { + protected DisallowedClassRule[] rules; + + public DisallowedClassesClassFileTransformer(DisallowedClassRule... rules) { + this.rules = rules; + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) + throws IllegalClassFormatException { + if (className == null) return classfileBuffer; + + Exception disallowed = null; + for (DisallowedClassRule rule : rules) { + // If the class ever loaded it's because a submission used it + if (className.startsWith(rule.getClassName())) { + disallowed = rule.getException(); + break; + } + } + + if (disallowed != null) ExceptionHandler.dumpExceptionAndExit(disallowed); + + // Don't actually retransform anything + return classfileBuffer; + } +} diff --git a/src/main/java/ca/dmoj/java/ExceptionHandler.java b/src/main/java/ca/dmoj/java/ExceptionHandler.java new file mode 100644 index 0000000..8f8989b --- /dev/null +++ b/src/main/java/ca/dmoj/java/ExceptionHandler.java @@ -0,0 +1,9 @@ +package ca.dmoj.java; + +class ExceptionHandler { + public static void dumpExceptionAndExit(Throwable exception) { + System.err.print("7257b50d-e37a-4664-b1a5-b1340b4206c0: "); + exception.printStackTrace(); + System.exit(1); + } +} diff --git a/src/main/java/ca/dmoj/java/SubmissionAgent.java b/src/main/java/ca/dmoj/java/SubmissionAgent.java index cbd09d1..363e439 100644 --- a/src/main/java/ca/dmoj/java/SubmissionAgent.java +++ b/src/main/java/ca/dmoj/java/SubmissionAgent.java @@ -1,19 +1,19 @@ package ca.dmoj.java; import java.io.*; -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; -import java.security.ProtectionDomain; +import java.util.ArrayList; public class SubmissionAgent { public static void premain(String argv, Instrumentation inst) throws UnsupportedEncodingException { + boolean unsafe = false; boolean unicode = false; boolean noBigMath = false; boolean noBuf = false; if (argv != null) { for (String opt : argv.split(",")) { + if (opt.equals("unsafe")) unsafe = true; if (opt.equals("unicode")) unicode = true; if (opt.equals("nobigmath")) noBigMath = true; if (opt.equals("nobuf")) noBuf = true; @@ -22,30 +22,17 @@ public static void premain(String argv, Instrumentation inst) throws Unsupported final Thread selfThread = Thread.currentThread(); + ArrayList disallowedClassRules = new ArrayList<>(); + if (!unsafe) { + disallowedClassRules.add(new DisallowedClassRule("sun/reflect/Unsafe", UnsafeDisallowedException::new)); + disallowedClassRules.add(new DisallowedClassRule("sun/misc/Unsafe", UnsafeDisallowedException::new)); + } if (noBigMath) { - inst.addTransformer(new ClassFileTransformer() { - @Override - public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, - ProtectionDomain protectionDomain, byte[] classfileBuffer) - throws IllegalClassFormatException { - if (className == null) return classfileBuffer; - - RuntimeException disallowed = null; - // If the class ever loaded it's because a submission used it - if (className.startsWith("java/math/BigInteger") || - className.startsWith("java/math/MutableBigInteger")) { - disallowed = new BigIntegerDisallowedException(); - } else if (className.startsWith("java/math/BigDecimal")) { - disallowed = new BigDecimalDisallowedException(); - } - - if (disallowed != null) dumpExceptionAndExit(disallowed); - - // Don't actually retransform anything - return classfileBuffer; - } - }); + disallowedClassRules.add(new DisallowedClassRule("java/math/BigInteger", BigIntegerDisallowedException::new)); + disallowedClassRules.add(new DisallowedClassRule("java/math/MutableBigInteger", BigIntegerDisallowedException::new)); + disallowedClassRules.add(new DisallowedClassRule("java/math/BigDecimal", BigDecimalDisallowedException::new)); } + inst.addTransformer(new DisallowedClassesClassFileTransformer(disallowedClassRules.toArray(new DisallowedClassRule[0]))); if (noBuf) { // Create output PrintStream set to autoflush: @@ -61,7 +48,7 @@ public byte[] transform(ClassLoader loader, String className, Class classBein selfThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable error) { - dumpExceptionAndExit(error); + ExceptionHandler.dumpExceptionAndExit(error); } }); @@ -75,10 +62,4 @@ public void run() { } })); } - - private static void dumpExceptionAndExit(Throwable exception) { - System.err.print("7257b50d-e37a-4664-b1a5-b1340b4206c0: "); - exception.printStackTrace(); - System.exit(1); - } } diff --git a/src/main/java/ca/dmoj/java/UnsafeDisallowedException.java b/src/main/java/ca/dmoj/java/UnsafeDisallowedException.java new file mode 100644 index 0000000..665c4df --- /dev/null +++ b/src/main/java/ca/dmoj/java/UnsafeDisallowedException.java @@ -0,0 +1,4 @@ +package ca.dmoj.java; + +public class UnsafeDisallowedException extends RuntimeException { +}