Skip to content

Commit 01441dc

Browse files
authored
fix(net): restrict admission signature length (#6782)
1 parent 0c13536 commit 01441dc

8 files changed

Lines changed: 201 additions & 1 deletion

File tree

common/src/main/java/org/tron/core/Constant.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public class Constant {
1919
public static final long MAXIMUM_TIME_UNTIL_EXPIRATION = 24 * 60 * 60 * 1_000L; //one day
2020
public static final long TRANSACTION_DEFAULT_EXPIRATION_TIME = 60 * 1_000L; //60 seconds
2121
public static final long TRANSACTION_FEE_POOL_PERIOD = 1; //1 blocks
22-
public static final long PER_SIGN_LENGTH = 65L;
22+
public static final int PER_SIGN_LENGTH = 65;
23+
public static final int MAX_PER_SIGN_LENGTH = 68;
2324
public static final long MAX_CONTRACT_RESULT_SIZE = 2L;
2425

2526
// Smart contract / Energy

crypto/src/main/java/org/tron/common/crypto/SignUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.tron.common.crypto;
22

3+
import static org.tron.core.Constant.MAX_PER_SIGN_LENGTH;
4+
import static org.tron.core.Constant.PER_SIGN_LENGTH;
5+
36
import java.security.SecureRandom;
47
import java.security.SignatureException;
58
import org.tron.common.crypto.ECKey.ECDSASignature;
@@ -8,6 +11,21 @@
811

912
public class SignUtils {
1013

14+
/**
15+
* Strict signature-length check for admission entry-points (RPC broadcast,
16+
* P2P transaction ingress, peer hello handshake). Accepts only sizes in
17+
* [{@link org.tron.core.Constant#PER_SIGN_LENGTH PER_SIGN_LENGTH},
18+
* {@link org.tron.core.Constant#MAX_PER_SIGN_LENGTH MAX_PER_SIGN_LENGTH}].
19+
*
20+
* <p>Consensus paths (e.g. {@code TransactionCapsule.checkWeight}) intentionally
21+
* keep the looser {@code size < 65} check to remain compatible with historical
22+
* on-chain signatures that carry trailing padding bytes; do not call this
23+
* helper from those paths.
24+
*/
25+
public static boolean isValidLength(int size) {
26+
return size >= PER_SIGN_LENGTH && size <= MAX_PER_SIGN_LENGTH;
27+
}
28+
1129
public static SignInterface getGeneratedRandomSign(
1230
SecureRandom secureRandom, boolean isECKeyCryptoEngine) {
1331
if (isECKeyCryptoEngine) {

framework/src/main/java/org/tron/core/Wallet.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,16 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) {
505505
trx.setTime(System.currentTimeMillis());
506506
Sha256Hash txID = trx.getTransactionId();
507507
try {
508+
for (ByteString sig : signedTransaction.getSignatureList()) {
509+
if (!SignUtils.isValidLength(sig.size())) {
510+
String info = "Signature size is " + sig.size();
511+
logger.warn("Broadcast transaction {} has failed, {}.", txID, info);
512+
return builder.setResult(false).setCode(response_code.SIGERROR)
513+
.setMessage(ByteString.copyFromUtf8("Validate signature error: " + info))
514+
.build();
515+
}
516+
}
517+
508518
if (tronNetDelegate.isBlockUnsolidified()) {
509519
logger.warn("Broadcast transaction {} has failed, block unsolidified.", txID);
510520
return builder.setResult(false).setCode(response_code.BLOCK_UNSOLIDIFIED)

framework/src/main/java/org/tron/core/net/messagehandler/TransactionsMsgHandler.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.tron.core.net.messagehandler;
22

3+
import com.google.protobuf.ByteString;
34
import java.util.HashSet;
45
import java.util.List;
56
import java.util.Set;
@@ -13,6 +14,7 @@
1314
import lombok.extern.slf4j.Slf4j;
1415
import org.springframework.beans.factory.annotation.Autowired;
1516
import org.springframework.stereotype.Component;
17+
import org.tron.common.crypto.SignUtils;
1618
import org.tron.common.es.ExecutorServiceManager;
1719
import org.tron.common.utils.Sha256Hash;
1820
import org.tron.core.ChainBaseManager;
@@ -142,6 +144,12 @@ private void check(PeerConnection peer, TransactionsMessage msg) throws P2pExcep
142144
throw new P2pException(TypeEnum.BAD_TRX,
143145
"tx " + item.getHash() + " contract size should be greater than 0");
144146
}
147+
for (ByteString sig : trx.getSignatureList()) {
148+
if (!SignUtils.isValidLength(sig.size())) {
149+
throw new P2pException(TypeEnum.BAD_TRX,
150+
"tx " + item.getHash() + " signature size is " + sig.size());
151+
}
152+
}
145153
}
146154
}
147155

framework/src/main/java/org/tron/core/net/service/relay/RelayService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ public boolean checkHelloMessage(HelloMessage message, Channel channel) {
150150
return false;
151151
}
152152

153+
if (!SignUtils.isValidLength(msg.getSignature().size())) {
154+
logger.warn("HelloMessage from {}, signature size is {}.",
155+
channel.getInetAddress(), msg.getSignature().size());
156+
return false;
157+
}
158+
153159
boolean flag;
154160
try {
155161
Sha256Hash hash = Sha256Hash.of(CommonParameter

framework/src/test/java/org/tron/core/WalletMockTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,54 @@ public void testCreateTransactionCapsuleWithoutValidateWithTimeout()
164164
}
165165

166166

167+
@Test
168+
public void testBroadcastTxInvalidSigLength() throws Exception {
169+
Wallet wallet = new Wallet();
170+
TronNetDelegate tronNetDelegateMock = mock(TronNetDelegate.class);
171+
Field field = wallet.getClass().getDeclaredField("tronNetDelegate");
172+
field.setAccessible(true);
173+
field.set(wallet, tronNetDelegateMock);
174+
175+
// signature shorter than 65 bytes → SIGERROR
176+
Protocol.Transaction shortSig = Protocol.Transaction.newBuilder()
177+
.addSignature(ByteString.copyFrom(new byte[64]))
178+
.build();
179+
GrpcAPI.Return ret = wallet.broadcastTransaction(shortSig);
180+
assertEquals(GrpcAPI.Return.response_code.SIGERROR, ret.getCode());
181+
182+
// signature longer than 68 bytes → SIGERROR
183+
Protocol.Transaction longSig = Protocol.Transaction.newBuilder()
184+
.addSignature(ByteString.copyFrom(new byte[69]))
185+
.build();
186+
ret = wallet.broadcastTransaction(longSig);
187+
assertEquals(GrpcAPI.Return.response_code.SIGERROR, ret.getCode());
188+
189+
// empty signature → SIGERROR
190+
Protocol.Transaction emptySig = Protocol.Transaction.newBuilder()
191+
.addSignature(ByteString.EMPTY)
192+
.build();
193+
ret = wallet.broadcastTransaction(emptySig);
194+
assertEquals(GrpcAPI.Return.response_code.SIGERROR, ret.getCode());
195+
196+
// tronNetDelegate must not be consulted because the request is rejected up front
197+
Mockito.verify(tronNetDelegateMock, Mockito.never()).isBlockUnsolidified();
198+
199+
// 65-byte signature passes the length check and proceeds to downstream logic
200+
when(tronNetDelegateMock.isBlockUnsolidified()).thenReturn(true);
201+
Protocol.Transaction validSig = Protocol.Transaction.newBuilder()
202+
.addSignature(ByteString.copyFrom(new byte[65]))
203+
.build();
204+
ret = wallet.broadcastTransaction(validSig);
205+
assertEquals(GrpcAPI.Return.response_code.BLOCK_UNSOLIDIFIED, ret.getCode());
206+
207+
// 68-byte signature (upper bound) also passes the length check
208+
Protocol.Transaction paddedSig = Protocol.Transaction.newBuilder()
209+
.addSignature(ByteString.copyFrom(new byte[68]))
210+
.build();
211+
ret = wallet.broadcastTransaction(paddedSig);
212+
assertEquals(GrpcAPI.Return.response_code.BLOCK_UNSOLIDIFIED, ret.getCode());
213+
}
214+
167215
@Test
168216
public void testBroadcastTransactionBlockUnsolidified() throws Exception {
169217
Wallet wallet = new Wallet();

framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,91 @@ public void testDuplicateTransactionRejected() throws Exception {
337337
}
338338
}
339339

340+
@Test
341+
public void testInvalidSigLength() throws Exception {
342+
TransactionsMsgHandler handler = new TransactionsMsgHandler();
343+
handler.init();
344+
try {
345+
PeerConnection peer = Mockito.mock(PeerConnection.class);
346+
347+
BalanceContract.TransferContract transferContract = BalanceContract.TransferContract
348+
.newBuilder()
349+
.setAmount(10)
350+
.setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString("121212a9cf")))
351+
.setToAddress(ByteString.copyFrom(ByteArray.fromHexString("232323a9cf")))
352+
.build();
353+
354+
// signature shorter than 65 bytes → BAD_TRX
355+
Protocol.Transaction shortSigTrx = Protocol.Transaction.newBuilder()
356+
.setRawData(Protocol.Transaction.raw.newBuilder()
357+
.addContract(Protocol.Transaction.Contract.newBuilder()
358+
.setType(Protocol.Transaction.Contract.ContractType.TransferContract)
359+
.setParameter(Any.pack(transferContract)).build())
360+
.build())
361+
.addSignature(ByteString.copyFrom(new byte[64]))
362+
.build();
363+
364+
List<Protocol.Transaction> shortList = new ArrayList<>();
365+
shortList.add(shortSigTrx);
366+
stubAdvInvRequest(peer, new TransactionsMessage(shortList));
367+
P2pException shortEx = Assert.assertThrows(P2pException.class,
368+
() -> handler.processMessage(peer, new TransactionsMessage(shortList)));
369+
Assert.assertEquals(TypeEnum.BAD_TRX, shortEx.getType());
370+
371+
// signature longer than 68 bytes → BAD_TRX
372+
Protocol.Transaction longSigTrx = Protocol.Transaction.newBuilder()
373+
.setRawData(Protocol.Transaction.raw.newBuilder()
374+
.setRefBlockNum(1)
375+
.addContract(Protocol.Transaction.Contract.newBuilder()
376+
.setType(Protocol.Transaction.Contract.ContractType.TransferContract)
377+
.setParameter(Any.pack(transferContract)).build())
378+
.build())
379+
.addSignature(ByteString.copyFrom(new byte[69]))
380+
.build();
381+
382+
List<Protocol.Transaction> longList = new ArrayList<>();
383+
longList.add(longSigTrx);
384+
stubAdvInvRequest(peer, new TransactionsMessage(longList));
385+
P2pException longEx = Assert.assertThrows(P2pException.class,
386+
() -> handler.processMessage(peer, new TransactionsMessage(longList)));
387+
Assert.assertEquals(TypeEnum.BAD_TRX, longEx.getType());
388+
389+
// exactly 65 bytes → passes the length check (no P2pException from check)
390+
Protocol.Transaction validSigTrx = Protocol.Transaction.newBuilder()
391+
.setRawData(Protocol.Transaction.raw.newBuilder()
392+
.setRefBlockNum(2)
393+
.addContract(Protocol.Transaction.Contract.newBuilder()
394+
.setType(Protocol.Transaction.Contract.ContractType.TransferContract)
395+
.setParameter(Any.pack(transferContract)).build())
396+
.build())
397+
.addSignature(ByteString.copyFrom(new byte[65]))
398+
.build();
399+
400+
List<Protocol.Transaction> validList = new ArrayList<>();
401+
validList.add(validSigTrx);
402+
stubAdvInvRequest(peer, new TransactionsMessage(validList));
403+
handler.processMessage(peer, new TransactionsMessage(validList));
404+
405+
// 68 bytes (upper bound) also passes the length check
406+
Protocol.Transaction paddedSigTrx = Protocol.Transaction.newBuilder()
407+
.setRawData(Protocol.Transaction.raw.newBuilder()
408+
.setRefBlockNum(3)
409+
.addContract(Protocol.Transaction.Contract.newBuilder()
410+
.setType(Protocol.Transaction.Contract.ContractType.TransferContract)
411+
.setParameter(Any.pack(transferContract)).build())
412+
.build())
413+
.addSignature(ByteString.copyFrom(new byte[68]))
414+
.build();
415+
416+
List<Protocol.Transaction> paddedList = new ArrayList<>();
417+
paddedList.add(paddedSigTrx);
418+
stubAdvInvRequest(peer, new TransactionsMessage(paddedList));
419+
handler.processMessage(peer, new TransactionsMessage(paddedList));
420+
} finally {
421+
handler.close();
422+
}
423+
}
424+
340425
@Test
341426
public void testIsBusyWithCachedTransactions() throws Exception {
342427
TransactionsMsgHandler handler = new TransactionsMsgHandler();

framework/src/test/java/org/tron/core/net/services/RelayServiceTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,30 @@ private void testCheckHelloMessage() {
220220

221221
boolean res = service.checkHelloMessage(helloMessage, c1);
222222
Assert.assertTrue(res);
223+
224+
HelloMessage shortSigMsg = new HelloMessage(node, System.currentTimeMillis(),
225+
ChainBaseManager.getChainBaseManager());
226+
shortSigMsg.setHelloMessage(shortSigMsg.getHelloMessage().toBuilder()
227+
.setAddress(address)
228+
.setSignature(ByteString.copyFrom(new byte[64]))
229+
.build());
230+
Assert.assertFalse(service.checkHelloMessage(shortSigMsg, c1));
231+
232+
HelloMessage longSigMsg = new HelloMessage(node, System.currentTimeMillis(),
233+
ChainBaseManager.getChainBaseManager());
234+
longSigMsg.setHelloMessage(longSigMsg.getHelloMessage().toBuilder()
235+
.setAddress(address)
236+
.setSignature(ByteString.copyFrom(new byte[69]))
237+
.build());
238+
Assert.assertFalse(service.checkHelloMessage(longSigMsg, c1));
239+
240+
HelloMessage emptySigMsg = new HelloMessage(node, System.currentTimeMillis(),
241+
ChainBaseManager.getChainBaseManager());
242+
emptySigMsg.setHelloMessage(emptySigMsg.getHelloMessage().toBuilder()
243+
.setAddress(address)
244+
.setSignature(ByteString.EMPTY)
245+
.build());
246+
Assert.assertFalse(service.checkHelloMessage(emptySigMsg, c1));
223247
} catch (Exception e) {
224248
logger.info("", e);
225249
assert false;

0 commit comments

Comments
 (0)