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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ dependencies {
// todo once we fix the logging properties set this to compile
testCompile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25'

implementation 'com.github.I-Al-Istannen:JvmAgentUtils:de8d42398b'

testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junitVersion
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junitVersion
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jdk.jshell.SnippetEvent;
import org.togetherjava.discord.server.execution.JShellSessionManager;
import org.togetherjava.discord.server.execution.JShellWrapper;
import org.togetherjava.discord.server.io.input.InputSanitizerManager;
import org.togetherjava.discord.server.rendering.RendererManager;
import sx.blah.discord.api.events.EventSubscriber;
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;
Expand All @@ -22,12 +23,14 @@ public class EventHandler {
private JShellSessionManager jShellSessionManager;
private final String botPrefix;
private RendererManager rendererManager;
private InputSanitizerManager sanitizerManager;

@SuppressWarnings("WeakerAccess")
public EventHandler(Config config) {
this.jShellSessionManager = new JShellSessionManager(config);
this.botPrefix = config.getString("prefix");
this.rendererManager = new RendererManager();
this.sanitizerManager = new InputSanitizerManager();
}

@EventSubscriber
Expand All @@ -51,7 +54,7 @@ private String parseCommandFromMessage(String messageContent) {
return codeBlockMatcher.group(2);
}

return withoutPrefix;
return sanitizerManager.sanitize(withoutPrefix);
}

private void executeCommand(IUser user, JShellWrapper shell, String command, IChannel channel) {
Expand Down
11 changes: 4 additions & 7 deletions src/main/java/org/togetherjava/discord/server/JShellBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public static void main(String[] args) {
}

/**
*
* @throws Exception
*/
public void start() throws Exception {
Expand All @@ -37,21 +36,19 @@ public void start() throws Exception {
Path botConfigPath = botConfigPathString == null ? null
: Paths.get(botConfigPathString);

if(botConfigPath == null){
if (botConfigPath == null) {
Properties prop = new Properties();
prop.load(JShellBot.class.getResourceAsStream("/bot.properties"));
config = new Config(prop);
}
else{
} else {
config = new Config(botConfigPath);
}

if(config.getString("token") != null){
if (config.getString("token") != null) {
IDiscordClient client = BotUtils.buildDiscordClient(config.getString("token"));
client.getDispatcher().registerListener(new EventHandler(config));
client.login();
}
else{
} else {
log.error("Token not set or config file not found in");
exit(1);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.togetherjava.discord.server;

import java.security.Permission;
import java.util.Arrays;

public class JshellSecurityManager extends SecurityManager {

@Override
public void checkPermission(Permission perm) {
// allow all but Jshell to bypass this
if (!comesFromMe() && comesFromJshell()) {
super.checkPermission(perm);
}
}

private boolean comesFromJshell() {
return Arrays.stream(getClassContext())
.anyMatch(aClass -> aClass.getName().contains("REPL"));
}

private boolean comesFromMe() {
return Arrays.stream(getClassContext())
.skip(2)
.anyMatch(aClass -> aClass == getClassContext()[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.apache.logging.log4j.Logger;
import org.togetherjava.discord.server.Config;
import org.togetherjava.discord.server.io.StringOutputStream;
import org.togetherjava.discord.server.sandbox.AgentAttacher;
import org.togetherjava.discord.server.sandbox.FilteredExecutionControlProvider;
import org.togetherjava.discord.server.sandbox.Sandbox;

Expand Down Expand Up @@ -43,6 +44,11 @@ private JShell buildJShell(OutputStream outputStream, Config config) {
return JShell.builder()
.out(out)
.err(out)
.remoteVMOptions(
AgentAttacher.getCommandLineArgument(),
"-Djava.security.policy=="
+ getClass().getResource("/jshell.policy").toExternalForm()
)
.executionEngine(getExecutionControlProvider(config), Map.of())
.build();
} catch (UnsupportedEncodingException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.togetherjava.discord.server.io.input;

public interface InputSanitizer {

/**
* Sanizizes the input to Jshell so that errors in it might be accounted for.
*
* @param input the input to sanitize
* @return the resulting input
*/
String sanitize(String input);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.togetherjava.discord.server.io.input;

import java.util.ArrayList;
import java.util.List;

public class InputSanitizerManager {

private List<InputSanitizer> sanitizers;

public InputSanitizerManager() {
this.sanitizers = new ArrayList<>();

addDefaults();
}

private void addDefaults() {
addSanitizer(new UnicodeQuoteSanitizer());
}

/**
* Adds a new {@link InputSanitizer}
*
* @param sanitizer the sanitizer to add
*/
public void addSanitizer(InputSanitizer sanitizer) {
sanitizers.add(sanitizer);
}

/**
* Sanitizes a given input using all registered {@link InputSanitizer}s.
*
* @param input the input to sanitize
* @return the resulting input
*/
public String sanitize(String input) {
String result = input;
for (InputSanitizer sanitizer : sanitizers) {
result = sanitizer.sanitize(input);
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.togetherjava.discord.server.io.input;

public class UnicodeQuoteSanitizer implements InputSanitizer {

@Override
public String sanitize(String input) {
return input
.replace("“", "\"")
.replace("”", "\"");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public boolean isApplicable(Object param) {

@Override
public EmbedBuilder render(Object object, EmbedBuilder builder) {
RenderUtils.applyFailColor(builder);

Diag diag = (Diag) object;
return builder
.appendField("Is compilation error", String.valueOf(diag.isError()), true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ public boolean isApplicable(Object param) {

@Override
public EmbedBuilder render(Object object, EmbedBuilder builder) {
RenderUtils.applyFailColor(builder);

Throwable throwable = (Throwable) object;
builder
.appendField("Exception type", throwable.getClass().getSimpleName(), true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.togetherjava.discord.server.rendering;

import jdk.jshell.Snippet;
import org.togetherjava.discord.server.execution.JShellWrapper;
import sx.blah.discord.util.EmbedBuilder;

public class RejectedColorRenderer implements Renderer {
@Override
public boolean isApplicable(Object param) {
return param instanceof JShellWrapper.JShellResult;
}

@Override
public EmbedBuilder render(Object object, EmbedBuilder builder) {
JShellWrapper.JShellResult result = (JShellWrapper.JShellResult) object;

if (result.getEvents().stream().anyMatch(e -> e.status() == Snippet.Status.REJECTED)) {
RenderUtils.applyFailColor(builder);
}

return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public RendererManager() {
addRenderer(new ExceptionRenderer());
addRenderer(new StandardOutputRenderer());
addRenderer(new CompilationErrorRenderer());
addRenderer(new RejectedColorRenderer());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.togetherjava.discord.server.sandbox;

import me.ialistannen.jvmagentutils.instrumentation.JvmUtils;
import org.togetherjava.discord.server.JshellSecurityManager;

import java.nio.file.Path;

public class AgentAttacher {

private static final Path agentJar = JvmUtils.generateAgentJar(
AgentMain.class, AgentMain.class, JshellSecurityManager.class
);

/**
* Returns the command line argument that attaches the agent.
*
* @return the command line argument to start it
*/
public static String getCommandLineArgument() {
return "-javaagent:" + agentJar.toAbsolutePath();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.togetherjava.discord.server.sandbox;

import org.togetherjava.discord.server.JshellSecurityManager;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;

/**
* An agent that sets the security manager JShell uses.
*/
public class AgentMain implements ClassFileTransformer {

public static void premain(String args, Instrumentation inst) {
System.setSecurityManager(new JshellSecurityManager());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.togetherjava.discord.server.sandbox;

import java.security.*;
import java.security.AccessControlContext;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.util.function.Supplier;

public class Sandbox {
Expand All @@ -27,9 +30,10 @@ public Sandbox() {
* @return the result of running it
*/
public <T> T runInSandBox(Supplier<T> supplier) {
return AccessController.doPrivileged(
(PrivilegedAction<T>) supplier::get,
controlContext
);
return supplier.get();
// return AccessController.doPrivileged(
// (PrivilegedAction<T>) supplier::get,
// controlContext
// );
}
}
5 changes: 5 additions & 0 deletions src/main/resources/jshell.policy
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Restrict what Jshell can run
grant {
permission java.util.RuntimePermission "accessDeclaredMembers";
permission java.util.RuntimePermission "accessClassInPackage";
};