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 common/src/main/java/org/tron/core/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class Constant {
public static final String LOCAL_HOST = "127.0.0.1";

// JSON parsing (DoS protection)
public static final int MAX_NESTING_DEPTH = 100;
public static final int MAX_NESTING_DEPTH = 20;
public static final int MAX_TOKEN_COUNT = 100_000;

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,22 @@
package org.tron.common.application;

import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.ConnectionLimit;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.SizeLimitHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.tron.core.config.args.Args;

@Slf4j(topic = "rpc")
Expand Down Expand Up @@ -72,6 +82,7 @@ protected void initServer() {
if (maxHttpConnectNumber > 0) {
this.apiServer.addBean(new ConnectionLimit(maxHttpConnectNumber, this.apiServer));
}
this.apiServer.setErrorHandler(new OversizedRequestErrorHandler());
}

protected ServletContextHandler initContextHandler() {
Expand All @@ -88,4 +99,30 @@ protected ServletContextHandler initContextHandler() {
protected void addFilter(ServletContextHandler context) {

}

/**
* For oversized requests (the 413 thrown by SizeLimitHandler during dispatch) logs the
* detail server-side and returns the short, uniform bad-message page, instead of the
* default error page that leaks the exception stack and internal request sizes. All
* other errors keep Jetty's default handling.
*/
private static final class OversizedRequestErrorHandler extends ErrorHandler {

@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
if (response.getStatus() == HttpStatus.PAYLOAD_TOO_LARGE_413) {
Throwable cause = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
logger.info("Reject oversized request, uri: {}, detail: {}",
request.getRequestURI(), cause == null ? "413" : cause.getMessage());
baseRequest.setHandled(true);
ByteBuffer body = badMessageError(HttpStatus.PAYLOAD_TOO_LARGE_413,
HttpStatus.getMessage(HttpStatus.PAYLOAD_TOO_LARGE_413),
baseRequest.getResponse().getHttpFields());
response.getOutputStream().write(BufferUtil.toArray(body));
return;
}
super.handle(target, baseRequest, request, response);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ private void processMessage(PeerConnection peer, byte[] data) {
}
}

private boolean checkInvRateLimit(PeerConnection peer, InventoryMessage msg) {
private boolean checkInvRateLimit(PeerConnection peer, InventoryMessage msg)
throws P2pException {
InventoryType invType = msg.getInventoryType();
int currentSize = msg.getInventory().getIdsCount();
MessageStatistics stats = peer.getPeerStatistics().messageStatistics;
Expand All @@ -237,6 +238,9 @@ private boolean checkInvRateLimit(PeerConnection peer, InventoryMessage msg) {
peer.getInetAddress(), count, currentSize, maxBlockInvIn10s);
return false;
}
} else {
throw new P2pException(P2pException.TypeEnum.BAD_MESSAGE,
"unknown inventory type: " + msg.getInventory().getTypeValue());
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,13 @@ private void check(PeerConnection peer, FetchInvDataMessage fetchInvDataMsg,
"FetchInvData contains duplicate hashes, size: " + hashList.size());
}

MessageTypes type = fetchInvDataMsg.getInvMessageType();
InventoryType invType = fetchInvDataMsg.getInventoryType();
if (invType != InventoryType.TRX && invType != InventoryType.BLOCK) {
throw new P2pException(TypeEnum.BAD_MESSAGE,
"unknown inventory type: " + fetchInvDataMsg.getInventory().getTypeValue());
}

if (type == MessageTypes.TRX) {
if (invType == InventoryType.TRX) {
for (Sha256Hash hash : fetchInvDataMsg.getHashList()) {
if (peer.getAdvInvSpread().getIfPresent(new Item(hash, InventoryType.TRX)) == null) {
throw new P2pException(TypeEnum.BAD_MESSAGE, "not spread inv: " + hash);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ private boolean check(PeerConnection peer, InventoryMessage inventoryMessage)
}

InventoryType type = inventoryMessage.getInventoryType();
if (type != InventoryType.TRX && type != InventoryType.BLOCK) {
throw new P2pException(TypeEnum.BAD_MESSAGE,
"unknown inventory type: " + inventoryMessage.getInventory().getTypeValue());
}
int size = hashList.size();

if (peer.isNeedSyncFromPeer() || peer.isNeedSyncFromUs()) {
Expand Down
79 changes: 52 additions & 27 deletions framework/src/main/java/org/tron/core/services/http/JsonFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
import org.tron.common.utils.ByteArray;
import org.tron.common.utils.Commons;
import org.tron.common.utils.StringUtil;
import org.tron.core.Constant;
import org.tron.json.JSON;
import org.tron.protos.contract.BalanceContract;

Expand Down Expand Up @@ -291,6 +292,7 @@ public static void merge(CharSequence input,
tokenizer.consume("{"); // Needs to happen when the object starts.
while (!tokenizer.tryConsume("}")) { // Continue till the object is done
mergeField(tokenizer, extensionRegistry, builder, selfType);
tokenizer.tryConsume(",");
}
// Test to make sure the tokenizer has reached the end of the stream.
if (!tokenizer.atEnd()) {
Expand Down Expand Up @@ -556,8 +558,9 @@ protected static StringBuilder toStringBuilder(Readable input) throws IOExceptio
}

/**
* Parse a single field from {@code tokenizer} and merge it into {@code builder}. If a ',' is
* detected after the field ends, the next field will be parsed automatically
* Parse a single field from {@code tokenizer} and merge it into {@code builder}. Exactly one
* field is consumed; the caller ({@code merge} / {@code handleObject}) consumes any trailing
* ',' and loops over the remaining fields.
*/
protected static void mergeField(Tokenizer tokenizer,
ExtensionRegistry extensionRegistry, Message.Builder builder,
Expand Down Expand Up @@ -628,11 +631,6 @@ protected static void mergeField(Tokenizer tokenizer,
handleValue(tokenizer, extensionRegistry, builder, field, extension, unknown, selfType);
}
}

if (tokenizer.tryConsume(",")) {
// Continue with the next field
mergeField(tokenizer, extensionRegistry, builder, selfType);
}
}

private static void handleMissingField(Tokenizer tokenizer,
Expand All @@ -642,18 +640,28 @@ private static void handleMissingField(Tokenizer tokenizer,
if ("{".equals(tokenizer.currentToken())) {
// Message structure
tokenizer.consume("{");
do {
tokenizer.consumeIdentifier();
handleMissingField(tokenizer, extensionRegistry, builder);
} while (tokenizer.tryConsume(","));
tokenizer.consume("}");
tokenizer.enterRecursion();
try {
do {
tokenizer.consumeIdentifier();
handleMissingField(tokenizer, extensionRegistry, builder);
} while (tokenizer.tryConsume(","));
tokenizer.consume("}");
} finally {
tokenizer.exitRecursion();
}
} else if ("[".equals(tokenizer.currentToken())) {
// Collection
tokenizer.consume("[");
do {
handleMissingField(tokenizer, extensionRegistry, builder);
} while (tokenizer.tryConsume(","));
tokenizer.consume("]");
tokenizer.enterRecursion();
try {
do {
handleMissingField(tokenizer, extensionRegistry, builder);
} while (tokenizer.tryConsume(","));
tokenizer.consume("]");
} finally {
tokenizer.exitRecursion();
}
} else { //if (!",".equals(tokenizer.currentToken)){
// Primitive value
if ("null".equals(tokenizer.currentToken())) {
Expand Down Expand Up @@ -807,20 +815,25 @@ private static Object handleObject(Tokenizer tokenizer,
}

tokenizer.consume("{");
String endToken = "}";
tokenizer.enterRecursion();
try {
String endToken = "}";

while (!tokenizer.tryConsume(endToken)) {
if (tokenizer.atEnd()) {
throw tokenizer.parseException("Expected \"" + endToken + "\".");
}
mergeField(tokenizer, extensionRegistry, subBuilder, selfType);
if (tokenizer.tryConsume(",")) {
// there are more fields in the object, so continue
continue;
while (!tokenizer.tryConsume(endToken)) {
if (tokenizer.atEnd()) {
throw tokenizer.parseException("Expected \"" + endToken + "\".");
}
mergeField(tokenizer, extensionRegistry, subBuilder, selfType);
if (tokenizer.tryConsume(",")) {
// there are more fields in the object, so continue
continue;
}
}
}

return subBuilder.build();
return subBuilder.build();
} finally {
tokenizer.exitRecursion();
}
}

/**
Expand Down Expand Up @@ -1290,6 +1303,18 @@ protected static class Tokenizer {
// errors *after* consuming).
private int previousLine = 0;
private int previousColumn = 0;
private int currentDepth = 0;

public void enterRecursion() throws ParseException {
if (currentDepth >= Constant.MAX_NESTING_DEPTH) {
throw parseException("Hit recursion limit.");
}
++currentDepth;
}

public void exitRecursion() {
--currentDepth;
}

/**
* Construct a tokenizer that parses tokens from the given text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand Down Expand Up @@ -114,7 +115,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
return;
}
} catch (JsonProcessingException e) {
writeJsonRpcError(resp, JsonRpcError.PARSE_ERROR, "JSON parse error", null, false);
if (e instanceof StreamConstraintsException) {
writeJsonRpcError(resp, JsonRpcError.PARSE_ERROR, e.getMessage(), null, false);
} else {
writeJsonRpcError(resp, JsonRpcError.PARSE_ERROR, "JSON parse error", null, false);
}
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,7 @@ private TransactionJson buildCreateSmartContractTransaction(byte[] ownerAddress,
String abiStr = "{" + "\"entrys\":" + args.getAbi() + "}";
try {
JsonFormat.merge(abiStr, abiBuilder, args.isVisible());
} catch (StackOverflowError e) {
} catch (Exception e) {
throw new JsonRpcInvalidParamsException("invalid abi");
}
}
Expand Down Expand Up @@ -1548,7 +1548,8 @@ public LogFilterElement[] getLogs(FilterRequest fr) throws JsonRpcInvalidParamsE
}

@Override
public LogFilterElement[] getFilterLogs(String filterId) throws ExecutionException,
public LogFilterElement[] getFilterLogs(String filterId) throws
JsonRpcInvalidParamsException, ExecutionException,
InterruptedException, BadItemException, ItemNotFoundException,
JsonRpcMethodNotFoundException, JsonRpcTooManyResultException {
disableInPBFT("eth_getFilterLogs");
Expand All @@ -1568,6 +1569,10 @@ public LogFilterElement[] getFilterLogs(String filterId) throws ExecutionExcepti
LogFilterWrapper logFilterWrapper = eventFilter2Result.get(filterId).getLogFilterWrapper();
long currentMaxBlockNum = wallet.getNowBlock().getBlockHeader().getRawData().getNumber();

// re-check the block range against the current head: the filter was created without the cap
// (eth_newFilter), so enforce it here to prevent an unbounded scan.
logFilterWrapper.validateBlockRange(currentMaxBlockNum);

return getLogsByLogFilterWrapper(logFilterWrapper, currentMaxBlockNum);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,23 @@ public LogFilterWrapper(FilterRequest fr, long currentMaxBlockNum, Wallet wallet
throw new JsonRpcInvalidParamsException("please verify: fromBlock <= toBlock");
}
}

// till now, it needs to check block range for eth_getLogs
int maxBlockRange = Args.getInstance().getJsonRpcMaxBlockRange();
if (checkBlockRange && maxBlockRange > 0
&& min(toBlockSrc, currentMaxBlockNum) - fromBlockSrc > maxBlockRange) {
throw new JsonRpcInvalidParamsException("exceed max block range: " + maxBlockRange);
}
}

this.fromBlock = fromBlockSrc;
this.toBlock = toBlockSrc;

// eth_getLogs enforces the block range at construction time. eth_newFilter creates the
// wrapper with checkBlockRange=false (no creation-time gate); eth_getFilterLogs re-runs this
// check against the current head before scanning so the cap cannot be bypassed.
if (checkBlockRange) {
validateBlockRange(currentMaxBlockNum);
}
}

public void validateBlockRange(long currentMaxBlockNum) throws JsonRpcInvalidParamsException {
int maxBlockRange = Args.getInstance().getJsonRpcMaxBlockRange();
if (maxBlockRange > 0 && min(toBlock, currentMaxBlockNum) - fromBlock > maxBlockRange) {
throw new JsonRpcInvalidParamsException("exceed max block range: " + maxBlockRange);
}
}
}
Loading
Loading