You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
7.5 KiB
Java
178 lines
7.5 KiB
Java
package com.razz.dfashion.security;
|
|
|
|
import com.code_intelligence.jazzer.junit.FuzzTest;
|
|
import com.google.gson.JsonParseException;
|
|
import com.razz.dfashion.bbmodel.BbModelParser;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.io.StringReader;
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
/**
|
|
* Security corpus + fuzz harness for {@link BbModelParser}. The parser runs only on the
|
|
* author's local disk, but still handles untrusted JSON (the author could have downloaded
|
|
* a malicious .bbmodel), so every rejection rule matters.
|
|
*
|
|
* <p>Fuzz inputs are fed as UTF-8 byte arrays. Legitimate rejections are any subtype of
|
|
* {@link JsonParseException} (including {@code BadBbmodelException}). A {@link StackOverflowError},
|
|
* {@link NullPointerException}, or any non-JsonParseException is a bug Jazzer should surface.
|
|
*/
|
|
class BbModelParserSecurityTest {
|
|
|
|
@Test
|
|
void acceptsMinimalValidModel() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[]," +
|
|
"\"groups\":[]," +
|
|
"\"outliner\":[]" +
|
|
"}";
|
|
assertDoesNotThrow(() -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsOldFormatVersion() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"4.5\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[],\"groups\":[],\"outliner\":[]" +
|
|
"}";
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsResolutionOverCap() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":" + (BbModelParser.MAX_RESOLUTION + 1) + ",\"height\":64}," +
|
|
"\"elements\":[],\"groups\":[],\"outliner\":[]" +
|
|
"}";
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsNanFloat() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[{\"type\":\"cube\",\"uuid\":\"a\",\"name\":\"n\"," +
|
|
"\"from\":[\"NaN\",0,0],\"to\":[1,1,1],\"origin\":[0,0,0],\"rotation\":[0,0,0]," +
|
|
"\"inflate\":0,\"faces\":{}}]," +
|
|
"\"groups\":[],\"outliner\":[\"a\"]" +
|
|
"}";
|
|
// GSON parses NaN-as-string into Float.NaN (depending on lenient mode); post-parse
|
|
// validation rejects non-finite floats. Either way, a JsonParseException is expected.
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsInfinityFloat() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[{\"type\":\"cube\",\"uuid\":\"a\",\"name\":\"n\"," +
|
|
"\"from\":[0,0,0],\"to\":[1,1,1],\"origin\":[0,0,0]," +
|
|
"\"rotation\":[\"Infinity\",0,0]," +
|
|
"\"inflate\":0,\"faces\":{}}]," +
|
|
"\"groups\":[],\"outliner\":[\"a\"]" +
|
|
"}";
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsCoordOutOfRange() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[{\"type\":\"cube\",\"uuid\":\"a\",\"name\":\"n\"," +
|
|
"\"from\":[1e9,0,0],\"to\":[1,1,1],\"origin\":[0,0,0],\"rotation\":[0,0,0]," +
|
|
"\"inflate\":0,\"faces\":{}}]," +
|
|
"\"groups\":[],\"outliner\":[\"a\"]" +
|
|
"}";
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsDuplicateElementUuid() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[" +
|
|
" {\"type\":\"cube\",\"uuid\":\"dup\",\"name\":\"n\"," +
|
|
" \"from\":[0,0,0],\"to\":[1,1,1],\"origin\":[0,0,0],\"rotation\":[0,0,0],\"inflate\":0,\"faces\":{}}," +
|
|
" {\"type\":\"cube\",\"uuid\":\"dup\",\"name\":\"n\"," +
|
|
" \"from\":[0,0,0],\"to\":[1,1,1],\"origin\":[0,0,0],\"rotation\":[0,0,0],\"inflate\":0,\"faces\":{}}" +
|
|
"]," +
|
|
"\"groups\":[],\"outliner\":[\"dup\"]" +
|
|
"}";
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsOutlinerRefToUnknownElement() {
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[]," +
|
|
"\"groups\":[]," +
|
|
"\"outliner\":[\"ghost-uuid\"]" +
|
|
"}";
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsOutlinerOverDepthCap() {
|
|
// Build a chain of groups deeper than MAX_OUTLINER_DEPTH. Each group is a GroupRef
|
|
// whose single child is the next GroupRef.
|
|
int depth = BbModelParser.MAX_OUTLINER_DEPTH + 5;
|
|
StringBuilder groups = new StringBuilder();
|
|
StringBuilder outliner = new StringBuilder();
|
|
for (int i = 0; i < depth; i++) {
|
|
if (i > 0) groups.append(',');
|
|
groups.append("{\"uuid\":\"g").append(i).append("\",\"name\":\"g\"," +
|
|
"\"origin\":[0,0,0],\"rotation\":[0,0,0]}");
|
|
}
|
|
// Outliner is a nested object tree referencing g0 → g1 → ... → g(depth-1).
|
|
for (int i = 0; i < depth; i++) {
|
|
outliner.append("{\"uuid\":\"g").append(i).append("\",\"children\":[");
|
|
}
|
|
for (int i = 0; i < depth; i++) outliner.append("]}");
|
|
|
|
String json = "{" +
|
|
"\"meta\":{\"format_version\":\"5.0\"}," +
|
|
"\"resolution\":{\"width\":64,\"height\":64}," +
|
|
"\"elements\":[]," +
|
|
"\"groups\":[" + groups + "]," +
|
|
"\"outliner\":[" + outliner + "]" +
|
|
"}";
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader(json)));
|
|
}
|
|
|
|
@Test
|
|
void rejectsMalformedJson() {
|
|
assertThrows(JsonParseException.class, () -> BbModelParser.parse(new StringReader("{ not json")));
|
|
}
|
|
|
|
@Test
|
|
void rejectsEmpty() {
|
|
assertThrows(Exception.class, () -> BbModelParser.parse(new StringReader("")));
|
|
}
|
|
|
|
// ---- fuzz harness ----
|
|
|
|
/**
|
|
* Fuzz the JSON parser with random UTF-8. Legit rejection = anything JsonParseException-ish.
|
|
* Other exceptions point at missing defense in the parser.
|
|
*/
|
|
@FuzzTest(maxDuration = "30s")
|
|
void fuzzParse(byte[] input) {
|
|
try {
|
|
BbModelParser.parse(new StringReader(new String(input, java.nio.charset.StandardCharsets.UTF_8)));
|
|
} catch (JsonParseException | IllegalStateException | NumberFormatException expected) {
|
|
// All legitimate rejection paths from GSON + validator.
|
|
}
|
|
}
|
|
} |