muScript: limit Dynamic.deserialize to expected expressions to prevent DOS
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-03-12 16:44:57 +01:00
parent a2ce8d8f64
commit 60fdea1457
Signed by: Johannes
GPG Key ID: E76429612C2929F4
7 changed files with 76 additions and 16 deletions

View File

@ -17,8 +17,8 @@ import java.util.stream.Stream;
@CanThrow
@UncheckedDynamic
public class Call extends DynamicExpr {
private final DynamicExpr left;
private final List<Arg> args;
public final DynamicExpr left;
public final List<Arg> args;
public Call(int chStart, int chEnd, DynamicExpr left, List<Arg> args) {
super(Order.Call, chStart, chEnd);

View File

@ -12,7 +12,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
public class ObjectLiteral extends DynamicExpr {
private final Map<String, DynamicExpr> content;
public final Map<String, DynamicExpr> content;
public ObjectLiteral(int chStart, int chEnd, Map<String, DynamicExpr> content) {
super(Order.Primary, chStart, chEnd);

View File

@ -10,8 +10,8 @@ import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import java.io.IOException;
public class Concatenate extends StringExpr {
private final StringExpr left;
private final StringExpr right;
public final StringExpr left;
public final StringExpr right;
public Concatenate(int chStart, int chEnd, StringExpr left, StringExpr right) {
super(Order.Concat, chStart, chEnd);

View File

@ -2,10 +2,10 @@ package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.StandardLib;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Parser;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DContainer;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.additional.*;
import java.io.IOException;
@ -15,8 +15,14 @@ import java.io.IOException;
* @param <T> the type represented
*/
public sealed interface Dynamic<T> permits DBool, DNumber, DString, DObject, DList, DCallable, DNull, DContainer {
/**
* Deserialize simply dynamic values. DOES NOT SUPPORT CALLABLES!
* Use Parser.parse() if you truly need to deserialize them, but be aware that that might enable DOS attacks
*/
static Dynamic<?> deserialize(String source) {
return Parser.parse(source).asDynamicExpr().get(StandardLib.createScope());
DynamicExpr expr = Parser.parse(source).asDynamicExpr();
if (!DirectPreconditionVisitor.visit(expr)) throw new IllegalArgumentException("This expression does not directly express a dynamic and may not be loaded this way");
return expr.get(StandardLib.createScope());
}
static String serialize(Dynamic<?> dynamic) {

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.compiler.Decompilable;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import org.jetbrains.annotations.Nullable;
@ -54,7 +55,7 @@ public record DEnum(Map<String, Dynamic<?>> values, @Nullable DEnumEntry value)
writer.append("enum(");
DObject.super.serialize(writer);
if (value != null) {
writer.append(", ").append(value.value);
writer.append(", ").append(Decompilable.enquote(value.value));
}
writer.append(')');
}

View File

@ -12,7 +12,7 @@ import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
public class DFinal<T> {
public class DFinal {
public static DBool of(boolean b) {
return new FBool(b);
}
@ -41,7 +41,13 @@ public class DFinal<T> {
return new FCallable(b, new LazySupplier<>(serialized));
}
private record FBool(boolean value) implements DBool {
/**
* Marks all DFinal implementation classes. Use this in instanceof checks
*/
public sealed interface FImpl {
}
private record FBool(boolean value) implements DBool, FImpl {
@Override
public Boolean getValue() {
return value;
@ -53,7 +59,7 @@ public class DFinal<T> {
}
}
private record FNumber(double value) implements DNumber {
private record FNumber(double value) implements DNumber, FImpl {
@Override
public Double getValue() {
return value;
@ -65,7 +71,7 @@ public class DFinal<T> {
}
}
private record FString(String value) implements DString {
private record FString(String value) implements DString, FImpl {
@Override
public String getValue() {
return value;
@ -77,7 +83,7 @@ public class DFinal<T> {
}
}
private record FObject(Map<String, Dynamic<?>> value) implements DObject {
private record FObject(Map<String, Dynamic<?>> value) implements DObject, FImpl {
@Override
public Map<String, Dynamic<?>> getValue() {
return value;
@ -89,7 +95,7 @@ public class DFinal<T> {
}
}
private record FList(List<Dynamic<?>> value) implements DList {
private record FList(List<Dynamic<?>> value) implements DList, FImpl {
@Override
public List<Dynamic<?>> getValue() {
return value;
@ -101,7 +107,7 @@ public class DFinal<T> {
}
}
private record FCallable(Function<DList, Dynamic<?>> value, Supplier<String> string) implements DCallable {
private record FCallable(Function<DList, Dynamic<?>> value, Supplier<String> string) implements DCallable, FImpl {
@Override
public void serialize(ExprWriter writer) throws IOException {
writer.append(toString());

View File

@ -0,0 +1,47 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.ast.literal.*;
import io.gitlab.jfronny.muscript.ast.string.Concatenate;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.util.Set;
public class DirectPreconditionVisitor {
public static boolean visit(Expr<?> expr) {
//TODO replace this with switch pattern matching
if (expr instanceof BoolLiteral) return true;
if (expr instanceof NullLiteral) return true;
if (expr instanceof NumberLiteral) return true;
if (expr instanceof StringLiteral) return true;
if (expr instanceof DynamicLiteral<?> e) return visit(e.value);
if (expr instanceof DynamicCoerce e) return visit(e.inner);
if (expr instanceof ObjectLiteral e) return e.content.values().stream().allMatch(DirectPreconditionVisitor::visit);
if (expr instanceof Concatenate e) return visit(e.left) && visit(e.right);
// if (expr instanceof BoolUnpack e) return visit(e.inner);
// if (expr instanceof NumberUnpack e) return visit(e.inner);
// if (expr instanceof StringUnpack e) return visit(e.inner);
if (expr instanceof Bind e) return visitCallable(e.callable) && visit(e.parameter);
if (expr instanceof Call e) return visitCallable(e.left) && e.args.stream().allMatch(a -> !a.variadic() && visit(a.expr()));
return false; // unknown expression
}
public static boolean visit(Dynamic<?> dynamic) {
if (dynamic instanceof DNull) return true;
if (dynamic instanceof DFinal.FImpl) {
if (dynamic instanceof DBool) return true;
if (dynamic instanceof DNumber) return true;
if (dynamic instanceof DString) return true;
if (dynamic instanceof DObject ob) return ob.getValue().values().stream().allMatch(DirectPreconditionVisitor::visit);
if (dynamic instanceof DList ob) return ob.getValue().stream().allMatch(DirectPreconditionVisitor::visit);
}
return false;
}
private static final Set<String> allowedVariables = Set.of("date", "time", "enum", "listOf");
private static boolean visitCallable(DynamicExpr expr) {
if (expr instanceof Variable v) return allowedVariables.contains(v.name);
return false;
}
}