Skip to content

Commit

Permalink
sandbox security handler
Browse files Browse the repository at this point in the history
  • Loading branch information
brachy84 committed Mar 15, 2024
1 parent 2025e7b commit b980d46
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 11 deletions.
10 changes: 10 additions & 0 deletions src/main/java/com/cleanroommc/groovyscript/GroovyScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public class GroovyScript {

public static final Logger LOGGER = LogManager.getLogger(ID);

private static File minecraftHome;
private static File scriptPath;
private static File runConfigFile;
private static File resourcesFile;
Expand Down Expand Up @@ -140,6 +141,7 @@ public void onRegisterItem(RegistryEvent.Register<Item> event) {

@ApiStatus.Internal
public static void initializeRunConfig(File minecraftHome) {
GroovyScript.minecraftHome = minecraftHome;
// If we are launching with the environment variable set to use the examples folder, use the examples folder for easy and consistent testing.
if (Boolean.parseBoolean(System.getProperty("groovyscript.use_examples_folder"))) {
scriptPath = new File(minecraftHome.getParentFile(), "examples");
Expand Down Expand Up @@ -208,6 +210,14 @@ public static String getScriptPath() {
return getScriptFile().getPath();
}

@NotNull
public static File getMinecraftHome() {
if (minecraftHome == null) {
throw new IllegalStateException("GroovyScript is not yet loaded!");
}
return minecraftHome;
}

@NotNull
public static File getScriptFile() {
if (scriptPath == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.cleanroommc.groovyscript.packmode.Packmode;
import com.cleanroommc.groovyscript.api.GroovyBlacklist;
import com.cleanroommc.groovyscript.registry.ReloadableRegistryManager;
import com.cleanroommc.groovyscript.sandbox.FileUtil;
import com.cleanroommc.groovyscript.sandbox.LoadStage;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Loader;
Expand Down Expand Up @@ -83,4 +84,12 @@ public static String getPackmode() {
public static boolean isPackmode(String packmode) {
return getPackmode().equalsIgnoreCase(packmode);
}

public static String getMinecraftHome() {
return GroovyScript.getMinecraftHome().getPath();
}

public static File file(String... parts) {
return new File(GroovyScript.getMinecraftHome(), FileUtil.makePath(parts));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,7 @@ protected void loadScripts(GroovyScriptEngine engine, Binding binding, Set<File>
}
if (shouldRunFile(scriptFile)) {
Script script = InvokerHelper.createScript(clazz, binding);
if (run) {
setCurrentScript(scriptFile.toString());
script.run();
setCurrentScript(null);
}
if (run) runScript(script);
}
}
}
Expand All @@ -172,15 +168,17 @@ protected void loadClassScripts(GroovyScriptEngine engine, Binding binding, Set<
if (clazz.getSuperclass() != Script.class && shouldRunFile(classFile)) {
executedClasses.add(classFile);
Script script = InvokerHelper.createScript(clazz, binding);
if (run) {
setCurrentScript(script.toString());
script.run();
setCurrentScript(null);
}
if (run) runScript(script);
}
}
}

protected void runScript(Script script){
setCurrentScript(script.toString());
script.run();
setCurrentScript(null);
}

public <T> T runClosure(Closure<T> closure, Object... args) {
startRunning();
T result = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import com.cleanroommc.groovyscript.helper.GroovyHelper;
import com.cleanroommc.groovyscript.helper.JsonHelper;
import com.cleanroommc.groovyscript.registry.ReloadableRegistryManager;
import com.cleanroommc.groovyscript.sandbox.security.SandboxSecurityManager;
import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptCompiler;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyClassLoader;
import groovy.lang.Script;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;
Expand All @@ -39,6 +41,8 @@

public class GroovyScriptSandbox extends GroovySandbox {

private static final SandboxSecurityManager securityManager = new SandboxSecurityManager();

private final File cacheRoot;
private final File scriptRoot;
private final ImportCustomizer importCustomizer = new ImportCustomizer();
Expand All @@ -61,10 +65,10 @@ public GroovyScriptSandbox(File scriptRoot, File cacheRoot) throws MalformedURLE
registerBinding("Mods", ModSupport.INSTANCE);
registerBinding("Log", GroovyLog.get());
registerBinding("EventManager", GroovyEventManager.INSTANCE);
registerBinding("MinecraftHome", GroovyScript.getMinecraftHome());

this.importCustomizer.addStaticStars(GroovyHelper.class.getName(), MathHelper.class.getName());
registerStaticImports(GroovyHelper.class, MathHelper.class);

this.importCustomizer.addImports("net.minecraft.world.World",
"net.minecraft.block.state.IBlockState",
"net.minecraft.block.Block",
Expand Down Expand Up @@ -162,6 +166,13 @@ public void run(LoadStage currentLoadStage) {
}
}

@Override
protected void runScript(Script script) {
securityManager.install();
super.runScript(script);
securityManager.uninstall();
}

@ApiStatus.Internal
@Override
public void load() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.cleanroommc.groovyscript.sandbox.security;

import com.cleanroommc.groovyscript.GroovyScript;
import sun.misc.Unsafe;

import java.io.FilePermission;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.Permission;

public class SandboxSecurityManager extends SecurityManager {

private static final Object securityFieldBase;
private static final long securityFieldOffset;
private static final Unsafe UNSAFE;
private final SecurityManager parent;

// cursed
static {
Object base = null;
long offset = 0;
Unsafe unsafe;
try {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (Unsafe) unsafeField.get(null);

Method getFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getFields.setAccessible(true);
for (Field field : (Field[]) getFields.invoke(System.class, false)) {
if (field.getName().equals("security")) {
offset = unsafe.staticFieldOffset(field);
base = unsafe.staticFieldBase(field);
break;
}
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
securityFieldBase = base;
securityFieldOffset = offset;
UNSAFE = unsafe;
}

public SandboxSecurityManager() {
this.parent = System.getSecurityManager();
if (this.parent == null) {
throw new NullPointerException();
}
}

public void install() {
UNSAFE.putObject(securityFieldBase, securityFieldOffset, this);
}

public void uninstall() {
System.setSecurityManager(this.parent);
}

public void checkFile(Permission perm) {
if (perm instanceof FilePermission filePerm) {
String path = filePerm.getName();
Class<?>[] classContext = getClassContext();
if (!path.startsWith(GroovyScript.getMinecraftHome().getPath())) {
for (Class<?> clazz : classContext) {
if (ClassLoader.class.isAssignableFrom(clazz)) {
// allow loading classes
return;
}
}
throw new SecurityException("Only files in minecraft home and sub directories can be accessed from scripts! Tried to access " + perm.getName());
}
}
}

@Override
public Object getSecurityContext() {
return parent.getSecurityContext();
}

@Override
public void checkPermission(Permission perm) {
parent.checkPermission(perm);
checkFile(perm);
}

@Override
public void checkPermission(Permission perm, Object context) {
parent.checkPermission(perm, context);
checkFile(perm);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cleanroommc.groovyscript.sandbox.transformer;

import com.cleanroommc.groovyscript.api.GroovyLog;
import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager;
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
import org.codehaus.groovy.ast.ClassHelper;
Expand All @@ -8,6 +9,7 @@
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.control.SourceUnit;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -49,6 +51,9 @@ private Expression transformInternal(Expression expr) {
if (expr instanceof MethodCallExpression) {
return checkValid((MethodCallExpression) expr);
}
if (expr instanceof ConstructorCallExpression cce && cce.getType().getName().equals(File.class.getName())) {
GroovyLog.get().warn("Detected `new File(...)` usage. Use `file(...)` instead!");
}
return expr;
}

Expand Down

0 comments on commit b980d46

Please sign in to comment.