muScript: limit Dynamic.deserialize to expected expressions to prevent DOS
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
a2ce8d8f64
commit
60fdea1457
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(')');
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue