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: 1 addition & 1 deletion benchmark/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-parent</artifactId>
<version>2.0.61.android8</version>
<version>2.0.63.android8</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-parent</artifactId>
<version>2.0.61.android8</version>
<version>2.0.63.android8</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
public abstract class JSONReader
implements Closeable {
static final int MAX_EXP = 2047;
static final int MAX_NUMBER_DIGITS = 10_000;

static final byte JSON_TYPE_INT = 1;
static final byte JSON_TYPE_DEC = 2;
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderJSONB.java
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ public Object readAny() {
}
case BC_BIGINT: {
int len = readInt32Value();
checkBigintLen(len, offset, end);
byte[] bytes = new byte[len];
System.arraycopy(this.bytes, offset, bytes, 0, len);
offset += len;
Expand Down Expand Up @@ -656,6 +657,9 @@ public Object readAny() {
}
case BC_BINARY: {
int len = readLength();
if (len < 0 || len > end - offset) {
throw new JSONException("BC_BINARY length out of range: " + len);
}
byte[] binary = Arrays.copyOfRange(this.bytes, offset, offset + len);
offset += len;
return binary;
Expand Down Expand Up @@ -3224,6 +3228,7 @@ private String readStringTypeNotMatch() {
return Long.toString(int64Value);
case BC_BIGINT: {
int len = readInt32Value();
checkBigintLen(len, offset, end);
byte[] bytes = new byte[len];
System.arraycopy(this.bytes, offset, bytes, 0, len);
offset += len;
Expand Down Expand Up @@ -3650,12 +3655,21 @@ public byte[] readBinary() {
}

int len = readLength();
if (len < 0 || len > end - offset) {
throw new JSONException("BC_BINARY length out of range: " + len);
}
byte[] bytes = new byte[len];
System.arraycopy(this.bytes, offset, bytes, 0, len);
offset += len;
return bytes;
}

static void checkBigintLen(int len, int offset, int end) {
if (len < 0 || len > end - offset) {
throw new JSONException("BC_BIGINT length out of range: " + len + ", available: " + (end - offset));
}
}

@Override
public Integer readInt32() {
final byte[] bytes = this.bytes;
Expand Down Expand Up @@ -4108,6 +4122,7 @@ public Number readNumber() {
}
case BC_BIGINT: {
int len = readInt32Value();
checkBigintLen(len, offset, end);
byte[] bytes = new byte[len];
System.arraycopy(this.bytes, offset, bytes, 0, len);
offset += len;
Expand Down Expand Up @@ -4342,6 +4357,7 @@ public BigInteger readBigInteger() {
);
} else if (type == BC_BIGINT) {
int len = readInt32Value();
checkBigintLen(len, offset, end);
byte[] bytes = new byte[len];
System.arraycopy(this.bytes, offset, bytes, 0, len);
offset += len;
Expand Down Expand Up @@ -4403,6 +4419,9 @@ private BigInteger readBigInteger0(byte type) {
}
case BC_BINARY: {
int len = readInt32Value();
if (len < 0 || len > end - offset) {
throw new JSONException("BC_BINARY length out of range: " + len);
}
byte[] buf = new byte[len];
System.arraycopy(this.bytes, offset, buf, 0, len);
offset += len;
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
Original file line number Diff line number Diff line change
Expand Up @@ -3631,6 +3631,9 @@ public void readNumber0() {
if (intOverflow) {
int numStart = negative ? start : start - 1;
int numDigits = scale > 0 ? offset - 2 - numStart : offset - 1 - numStart;
if (numDigits > MAX_NUMBER_DIGITS) {
throw new JSONException("Number literal too long: " + numDigits + " digits (max " + MAX_NUMBER_DIGITS + ")");
}
if (numDigits > 38) {
valueType = JSON_TYPE_BIG_DEC;
if (negative) {
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
Original file line number Diff line number Diff line change
Expand Up @@ -3330,6 +3330,9 @@ public final void readNumber0() {
if (intOverflow) {
int numStart = negative ? start : start - 1;
int numDigits = scale > 0 ? offset - 2 - numStart : offset - 1 - numStart;
if (numDigits > MAX_NUMBER_DIGITS) {
throw new JSONException("Number literal too long: " + numDigits + " digits (max " + MAX_NUMBER_DIGITS + ")");
}
if (numDigits > 38) {
valueType = JSON_TYPE_BIG_DEC;
if (negative) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2233,7 +2233,7 @@ public void writeFloat(float[] values) {
boolean writeAsString = (context.features & Feature.WriteNonStringValueAsString.mask) != 0;

int off = this.off;
int minCapacity = off + values.length * (writeAsString ? 16 : 18) + 1;
int minCapacity = off + values.length * 18 + 2;
if (minCapacity >= chars.length) {
ensureCapacity(minCapacity);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2257,7 +2257,7 @@ public void writeFloat(float[] values) {
boolean writeAsString = (context.features & JSONWriter.Feature.WriteNonStringValueAsString.mask) != 0;

int off = this.off;
int minCapacity = off + values.length * (writeAsString ? 16 : 18) + 1;
int minCapacity = off + values.length * 18 + 2;
if (minCapacity >= bytes.length) {
ensureCapacity(minCapacity);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class ContextAutoTypeBeforeHandler
static final Class CLASS_UNMODIFIABLE_SET = Collections.unmodifiableSet(Collections.emptySet()).getClass();
static final Class CLASS_UNMODIFIABLE_COLLECTION = Collections.unmodifiableCollection(Collections.emptyList()).getClass();
final long[] acceptHashCodes;
final Set<String> acceptNameSet;
final ConcurrentMap<Integer, ConcurrentHashMap<Long, Class>> tclHashCaches = new ConcurrentHashMap<>();
final Map<Long, Class> classCache = new ConcurrentHashMap<>(16, 0.75f, 1);

Expand Down Expand Up @@ -203,6 +204,7 @@ public ContextAutoTypeBeforeHandler(boolean includeBasic, String... acceptNames)
}

long[] array = new long[nameSet.size()];
Set<String> normalizedNames = new HashSet<>(nameSet.size());

int index = 0;
for (String name : nameSet) {
Expand All @@ -217,13 +219,15 @@ public ContextAutoTypeBeforeHandler(boolean includeBasic, String... acceptNames)
}

array[index++] = hashCode;
normalizedNames.add(normalizeAcceptName(name));
}

if (index != array.length) {
array = Arrays.copyOf(array, index);
}
Arrays.sort(array);
this.acceptHashCodes = array;
this.acceptNameSet = Collections.unmodifiableSet(normalizedNames);
}

public Class<?> apply(long typeNameHash, Class<?> expectClass, long features) {
Expand All @@ -245,6 +249,10 @@ public Class<?> apply(String typeName, Class<?> expectClass, long features) {
typeName = "Object";
}

if (hasIllegalTypeNameChars(typeName)) {
return null;
}

long hash = MAGIC_HASH_CODE;
for (int i = 0, typeNameLength = typeName.length(); i < typeNameLength; ++i) {
char ch = typeName.charAt(i);
Expand All @@ -255,6 +263,9 @@ public Class<?> apply(String typeName, Class<?> expectClass, long features) {
hash *= MAGIC_PRIME;

if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
if (!acceptNameSet.contains(normalizeAcceptName(typeName.substring(0, i + 1)))) {
continue;
}
long typeNameHash = Fnv.hashCode64(typeName);
Class clazz = apply(typeNameHash, expectClass, features);

Expand All @@ -269,6 +280,13 @@ public Class<?> apply(String typeName, Class<?> expectClass, long features) {
}

if (clazz != null) {
// matching an accept prefix is not an opt-in for gadget base types, only an
// accept entry naming the type in full is; keep scanning for such an entry.
// checked after the cache read so that a cached class is checked too
if (i + 1 < typeNameLength && isAutoTypeDenyClass(clazz)) {
continue;
}

return clazz;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
import com.alibaba.fastjson2.function.impl.*;
import com.alibaba.fastjson2.util.*;

import javax.sql.DataSource;
import javax.sql.RowSet;

import java.io.Closeable;
import java.io.File;
import java.io.Serializable;
Expand All @@ -32,9 +29,19 @@
import static com.alibaba.fastjson2.util.BeanUtils.*;
import static com.alibaba.fastjson2.util.Fnv.MAGIC_HASH_CODE;
import static com.alibaba.fastjson2.util.Fnv.MAGIC_PRIME;
import static com.alibaba.fastjson2.util.TypeUtils.hasIllegalTypeNameChars;
import static com.alibaba.fastjson2.util.TypeUtils.isAutoTypeDenyClass;
import static com.alibaba.fastjson2.util.TypeUtils.loadClass;
import static com.alibaba.fastjson2.util.TypeUtils.normalizeAcceptName;

public class ObjectReaderProvider {
/**
* Always accepted, it is the map implementation fastjson 1.x substitutes for {@code HashMap}
* when hash collision protection is enabled.
*/
static final String ANTI_COLLISION_HASH_MAP = "com.alibaba.fastjson.util.AntiCollisionHashMap";
static final long ANTI_COLLISION_HASH_MAP_HASH = -6293031534589903644L; // Fnv.hashCode64(ANTI_COLLISION_HASH_MAP)

static ObjectReaderCachePair readerCache;

private static final class ObjectReaderCachePair {
Expand Down Expand Up @@ -62,6 +69,13 @@ public ObjectReaderCachePair(long hashCode, ObjectReader reader) {
private long[] denyHashCodes;
private long[] acceptHashCodes;

/**
* The accept names behind {@link #acceptHashCodes}, normalized the same way the rolling hash in
* {@link #checkAutoType} normalizes a type name. A hash match is only honored when the matched
* prefix text is in this set, so a hash collision alone cannot whitelist a type name.
*/
private volatile Set<String> acceptNameSet = Collections.emptySet();

private AutoTypeBeforeHandler autoTypeBeforeHandler;
private Consumer<Class> autoTypeHandler;

Expand Down Expand Up @@ -231,7 +245,8 @@ public ObjectReaderCachePair(long hashCode, ObjectReader reader) {
9144212112462101475L
};

acceptHashCodes = new long[]{-6293031534589903644L};
acceptHashCodes = new long[]{ANTI_COLLISION_HASH_MAP_HASH};
acceptNameSet = Collections.singleton(ANTI_COLLISION_HASH_MAP);

hashCache.put(ObjectArrayReader.TYPE_HASH_CODE, ObjectArrayReader.INSTANCE);
final long STRING_CLASS_NAME_HASH = -4834614249632438472L; // Fnv.hashCode64(String.class.getName());
Expand All @@ -246,7 +261,17 @@ public void registerIfAbsent(long hashCode, ObjectReader objectReader) {

public void addAutoTypeAccept(String name) {
if (name != null && name.length() != 0) {
long hash = Fnv.hashCode64(name);
String acceptName = normalizeAcceptName(name);

// publish the name before the hash, so that a reader seeing the new hash array is
// guaranteed to see the name it verifies against rather than transiently rejecting
if (!this.acceptNameSet.contains(acceptName)) {
Set<String> names = new HashSet<>(this.acceptNameSet);
names.add(acceptName);
this.acceptNameSet = Collections.unmodifiableSet(names);
}

long hash = Fnv.hashCode64(acceptName);
if (Arrays.binarySearch(this.acceptHashCodes, hash) < 0) {
long[] hashCodes = new long[this.acceptHashCodes.length + 1];
hashCodes[hashCodes.length - 1] = hash;
Expand Down Expand Up @@ -694,6 +719,13 @@ public Class<?> checkAutoType(String typeName, Class<?> expectClass, long featur
throw new JSONException("autoType is not support. " + typeName);
}

// treat it as unresolvable rather than an error, the same as a type name that fails to
// load: JSON-LD uses @type for an IRI, and reporting that as unresolved leaves the
// ErrorOnNotSupportAutoType feature in charge of whether the caller sees an exception
if (hasIllegalTypeNameChars(typeName)) {
return null;
}

if (typeName.charAt(0) == '[') {
String componentTypeName = typeName.substring(1);
checkAutoType(componentTypeName, null, features); // blacklist check for componentType
Expand All @@ -707,6 +739,10 @@ public Class<?> checkAutoType(String typeName, Class<?> expectClass, long featur
boolean autoTypeSupport = (features & JSONReader.Feature.SupportAutoType.mask) != 0;
Class<?> clazz;

// set when an accept prefix matched a deny class, so that the rejection below can tell a
// misconfigured accept list apart from a type name that was never accepted at all
boolean denyPrefixOnly = false;

if (autoTypeSupport) {
long hash = MAGIC_HASH_CODE;
for (int i = 0; i < typeNameLength; ++i) {
Expand All @@ -717,8 +753,18 @@ public Class<?> checkAutoType(String typeName, Class<?> expectClass, long featur
hash ^= ch;
hash *= MAGIC_PRIME;
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
if (!acceptNameSet.contains(normalizeAcceptName(typeName.substring(0, i + 1)))) {
continue;
}
clazz = loadClass(typeName);
if (clazz != null) {
// matching an accept prefix is not an opt-in for gadget base types, only an
// accept entry naming the type in full is; keep scanning for such an entry
if (i + 1 < typeNameLength && isAutoTypeDenyClass(clazz)) {
denyPrefixOnly = true;
continue;
}

if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
Expand Down Expand Up @@ -749,8 +795,16 @@ public Class<?> checkAutoType(String typeName, Class<?> expectClass, long featur

// white list
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
if (!acceptNameSet.contains(normalizeAcceptName(typeName.substring(0, i + 1)))) {
continue;
}
clazz = loadClass(typeName);

// see the SupportAutoType branch above
if (clazz != null && i + 1 < typeNameLength && isAutoTypeDenyClass(clazz)) {
continue;
}

if (clazz != null && expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
Expand Down Expand Up @@ -783,8 +837,11 @@ public Class<?> checkAutoType(String typeName, Class<?> expectClass, long featur
clazz = loadClass(typeName);

if (clazz != null) {
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz) || RowSet.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
if (isAutoTypeDenyClass(clazz)) {
throw new JSONException(denyPrefixOnly
? "autoType is not support, an accept prefix does not cover it, "
+ "add the type name in full to accept. " + typeName
: "autoType is not support. " + typeName);
}

if (expectClass != null) {
Expand Down
Loading
Loading