Skip to content

Commit 8f82a26

Browse files
committed
feat(common,framework): cap JSON parse depth and token count
1 parent 0116365 commit 8f82a26

7 files changed

Lines changed: 128 additions & 20 deletions

File tree

common/src/main/java/org/tron/common/parameter/CommonParameter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,12 @@ public class CommonParameter {
504504
public int pBFTHttpPort;
505505
@Getter
506506
@Setter
507+
public int maxNestingDepth = 100;
508+
@Getter
509+
@Setter
510+
public int maxTokenCount = 100_000;
511+
@Getter
512+
@Setter
507513
public long pBFTExpireNum; // clearParam: 20
508514
@Getter
509515
@Setter

common/src/main/java/org/tron/core/config/args/NodeConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ public static class HttpConfig {
197197
private int fullNodePort = 8090;
198198
private boolean solidityEnable = true;
199199
private int solidityPort = 8091;
200+
private int maxNestingDepth = 100;
201+
private int maxTokenCount = 100_000;
200202
// PBFT fields — handled manually (same naming issue as CommitteeConfig)
201203
// Default must match CommonParameter.pBFTHttpEnable = true
202204
@Getter(lombok.AccessLevel.NONE)

common/src/main/java/org/tron/json/JSON.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
@Deprecated
2121
public final class JSON {
2222

23-
public static final ObjectMapper MAPPER = JsonMapper.builder()
23+
static final ObjectMapper MAPPER = JsonMapper.builder()
2424
// Fastjson Feature.AllowUnQuotedFieldNames (default ON)
2525
.enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES)
2626
// Fastjson Feature.AllowSingleQuotes (default ON)

common/src/main/resources/reference.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ node {
269269
solidityPort = 8091
270270
PBFTEnable = true
271271
PBFTPort = 8092
272+
maxNestingDepth = 100
273+
maxTokenCount = 100000
272274
}
273275

274276
rpc {

framework/src/main/java/org/tron/core/config/args/Args.java

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,27 @@
22

33
import static java.lang.System.exit;
44
import static org.tron.common.math.Maths.max;
5-
import static org.tron.common.math.Maths.min;
65
import static org.tron.core.Constant.ADD_PRE_FIX_BYTE_MAINNET;
7-
import static org.tron.core.Constant.DEFAULT_PROPOSAL_EXPIRE_TIME;
8-
import static org.tron.core.Constant.DYNAMIC_ENERGY_INCREASE_FACTOR_RANGE;
9-
import static org.tron.core.Constant.DYNAMIC_ENERGY_MAX_FACTOR_RANGE;
106
import static org.tron.core.Constant.ENERGY_LIMIT_IN_CONSTANT_TX;
11-
import static org.tron.core.Constant.MAX_PROPOSAL_EXPIRE_TIME;
12-
import static org.tron.core.Constant.MIN_PROPOSAL_EXPIRE_TIME;
13-
import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCE_TIMEOUT_PERCENT;
14-
import static org.tron.core.config.Parameter.ChainConstant.MAX_ACTIVE_WITNESS_NUM;
15-
import static org.tron.core.exception.TronError.ErrCode.PARAMETER_INIT;
167

178
import com.beust.jcommander.JCommander;
189
import com.beust.jcommander.ParameterDescription;
10+
import com.fasterxml.jackson.core.StreamReadConstraints;
1911
import com.google.common.annotations.VisibleForTesting;
2012
import com.google.common.base.Strings;
2113
import com.typesafe.config.Config;
22-
import com.typesafe.config.ConfigObject;
23-
import io.grpc.internal.GrpcUtil;
24-
import io.grpc.netty.NettyServerBuilder;
2514
import java.io.File;
2615
import java.io.IOException;
2716
import java.io.InputStream;
28-
import java.net.InetAddress;
2917
import java.net.InetSocketAddress;
3018
import java.text.ParseException;
3119
import java.util.ArrayList;
3220
import java.util.Arrays;
3321
import java.util.Collections;
3422
import java.util.HashMap;
35-
import java.util.HashSet;
3623
import java.util.LinkedHashMap;
3724
import java.util.List;
3825
import java.util.Map;
39-
import java.util.Objects;
40-
import java.util.Optional;
4126
import java.util.Properties;
4227
import java.util.Set;
4328
import java.util.concurrent.BlockingQueue;
@@ -69,8 +54,6 @@
6954
import org.tron.core.Constant;
7055
import org.tron.core.Wallet;
7156
import org.tron.core.config.Configuration;
72-
import org.tron.core.config.Parameter.NetConstants;
73-
import org.tron.core.config.Parameter.NodeConstant;
7457
import org.tron.core.exception.TronError;
7558
import org.tron.core.store.AccountStore;
7659
import org.tron.p2p.P2pConfig;
@@ -573,6 +556,12 @@ private static void applyNodeConfig(NodeConfig nc) {
573556
PARAMETER.fullNodeHttpPort = http.getFullNodePort();
574557
PARAMETER.solidityHttpPort = http.getSolidityPort();
575558
PARAMETER.pBFTHttpPort = http.getPBFTPort();
559+
PARAMETER.maxNestingDepth = http.getMaxNestingDepth();
560+
PARAMETER.maxTokenCount = http.getMaxTokenCount();
561+
StreamReadConstraints.overrideDefaultStreamReadConstraints(StreamReadConstraints.builder()
562+
.maxNestingDepth(http.getMaxNestingDepth())
563+
.maxTokenCount(http.getMaxTokenCount())
564+
.build());
576565

577566
// ---- JSON-RPC sub-bean ----
578567
NodeConfig.JsonRpcConfig jsonrpc = nc.getJsonrpc();

framework/src/test/java/org/tron/core/config/args/ArgsTest.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package org.tron.core.config.args;
1717

18+
import com.fasterxml.jackson.core.JsonFactory;
19+
import com.fasterxml.jackson.core.StreamReadConstraints;
1820
import com.google.common.collect.Lists;
1921
import com.typesafe.config.Config;
2022
import com.typesafe.config.ConfigFactory;
@@ -38,7 +40,6 @@
3840
import org.tron.common.utils.DecodeUtil;
3941
import org.tron.common.utils.LocalWitnesses;
4042
import org.tron.common.utils.PublicMethod;
41-
import org.tron.core.config.Configuration;
4243

4344
@Slf4j
4445
public class ArgsTest {
@@ -404,6 +405,52 @@ public void testFetchBlockTimeoutClampedAboveMax() {
404405
Args.clearParam();
405406
}
406407

408+
409+
@Test
410+
public void testHttpJsonParseConstraints() {
411+
Map<String, String> override = new HashMap<>();
412+
override.put("storage.db.directory", "database");
413+
Config config = ConfigFactory.parseMap(override)
414+
.withFallback(ConfigFactory.defaultReference());
415+
Args.applyConfigParams(config);
416+
417+
Assert.assertEquals(100, Args.getInstance().getMaxNestingDepth());
418+
Assert.assertEquals(100_000, Args.getInstance().getMaxTokenCount());
419+
Args.clearParam();
420+
}
421+
422+
@Test
423+
public void testHttpJsonParseConstraintsApplied() {
424+
StreamReadConstraints original = StreamReadConstraints.defaults();
425+
try {
426+
Map<String, String> override = new HashMap<>();
427+
override.put("storage.db.directory", "database");
428+
Config config = ConfigFactory.parseMap(override)
429+
.withFallback(ConfigFactory.defaultReference());
430+
Args.applyConfigParams(config);
431+
Assert.assertEquals(100, Args.getInstance().getMaxNestingDepth());
432+
Assert.assertEquals(100_000, Args.getInstance().getMaxTokenCount());
433+
434+
StreamReadConstraints applied = new JsonFactory().streamReadConstraints();
435+
Assert.assertEquals(100, applied.getMaxNestingDepth());
436+
Assert.assertEquals(100_000, applied.getMaxTokenCount());
437+
438+
override.put("node.http.maxNestingDepth", "42");
439+
override.put("node.http.maxTokenCount", "12345");
440+
config = ConfigFactory.parseMap(override)
441+
.withFallback(ConfigFactory.defaultReference());
442+
Args.applyConfigParams(config);
443+
Assert.assertEquals(42, Args.getInstance().getMaxNestingDepth());
444+
Assert.assertEquals(12345, Args.getInstance().getMaxTokenCount());
445+
applied = new JsonFactory().streamReadConstraints();
446+
Assert.assertEquals(42, applied.getMaxNestingDepth());
447+
Assert.assertEquals(12345L, applied.getMaxTokenCount());
448+
} finally {
449+
StreamReadConstraints.overrideDefaultStreamReadConstraints(original);
450+
Args.clearParam();
451+
}
452+
}
453+
407454
@Test
408455
public void testFetchBlockTimeoutInRangeUnchanged() {
409456
Map<String, String> override = new HashMap<>();

framework/src/test/java/org/tron/json/JsonTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import static org.junit.Assert.assertThrows;
99
import static org.junit.Assert.assertTrue;
1010

11+
import com.fasterxml.jackson.core.StreamReadConstraints;
12+
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import com.fasterxml.jackson.databind.json.JsonMapper;
1115
import java.math.BigDecimal;
1216
import java.math.BigInteger;
1317
import java.util.Arrays;
@@ -361,6 +365,64 @@ public void testTypeUtilsCoercion() {
361365
assertEquals(7L, TypeUtils.longValue(new BigDecimal("7")));
362366
}
363367

368+
// ===========================================================================
369+
// StreamReadConstraints — verify the global override actually constrains
370+
// parsing. JSON.MAPPER is built once at class init (well before this test
371+
// runs in the test JVM), so we exercise the override on a freshly-built
372+
// mapper. In production, Args.setParam() runs before MAPPER is loaded, so
373+
// MAPPER inherits the configured limits.
374+
// ===========================================================================
375+
376+
@Test
377+
public void testNestingDepthOverride() {
378+
StringBuilder open = new StringBuilder();
379+
StringBuilder close = new StringBuilder();
380+
int depth = 20;
381+
for (int i = 0; i < depth; i++) {
382+
open.append("{\"a\":");
383+
close.append("}");
384+
}
385+
String deep = open + "1" + close;
386+
387+
assertNotNull(JSON.parseObject(deep));
388+
389+
StreamReadConstraints saved = StreamReadConstraints.defaults();
390+
try {
391+
StreamReadConstraints.overrideDefaultStreamReadConstraints(StreamReadConstraints.builder()
392+
.maxNestingDepth(10).maxTokenCount(100).build());
393+
ObjectMapper fresh = JsonMapper.builder().build();
394+
StreamConstraintsException e = assertThrows(StreamConstraintsException.class,
395+
() -> fresh.readTree(deep));
396+
assertTrue(e.getMessage().toLowerCase().contains("depth"));
397+
} finally {
398+
StreamReadConstraints.overrideDefaultStreamReadConstraints(saved);
399+
}
400+
}
401+
402+
@Test
403+
public void testTokenCountOverride() {
404+
StringBuilder sb = new StringBuilder("[0");
405+
for (int i = 1; i < 200; i++) {
406+
sb.append(',').append(i);
407+
}
408+
sb.append(']');
409+
String wide = sb.toString();
410+
411+
assertNotNull(JSON.parseArray(wide));
412+
413+
StreamReadConstraints saved = StreamReadConstraints.defaults();
414+
try {
415+
StreamReadConstraints.overrideDefaultStreamReadConstraints(StreamReadConstraints.builder()
416+
.maxNestingDepth(10).maxTokenCount(100).build());
417+
ObjectMapper fresh = JsonMapper.builder().build();
418+
StreamConstraintsException e = assertThrows(StreamConstraintsException.class,
419+
() -> fresh.readTree(wide));
420+
assertTrue(e.getMessage().toLowerCase().contains("token"));
421+
} finally {
422+
StreamReadConstraints.overrideDefaultStreamReadConstraints(saved);
423+
}
424+
}
425+
364426
public static class Pojo {
365427
public String name;
366428
}

0 commit comments

Comments
 (0)