Skip to content

Commit 0eef8cd

Browse files
authored
Merge pull request #24 from datachainlab/audit-202409
Audit-202409 Signed-off-by: Jun Kimura <[email protected]>
2 parents 54d2bac + 43f92a7 commit 0eef8cd

12 files changed

+133
-104
lines changed

contracts/AVRValidator.sol

+6-2
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ library AVRValidator {
200200
// find 'advisoryIDs' key and validate them
201201
checkpoint = consumeAdvisoryIdsReportJSON(report, checkpoint);
202202
validateAdvisories(report, checkpoint, allowedStatus.allowedAdvisories);
203+
} else {
204+
// NO-OP
205+
// Since other statuses do not have `advisoryIDs` field, skip the validation here
206+
// NOTE: In production, `allowedQuoteStatuses` should not allow these statuses
203207
}
204208
}
205209

@@ -259,13 +263,13 @@ library AVRValidator {
259263
}
260264
} else if (chr == CHAR_COMMA) {
261265
require(
262-
allowedAdvisories[string(report[lastStart:lastStart + offset - lastStart - 1])] == FLAG_ALLOWED,
266+
allowedAdvisories[string(report[lastStart:offset - 1])] == FLAG_ALLOWED,
263267
"disallowed advisory is included"
264268
);
265269
} else if (chr == CHAR_LIST_END) {
266270
if (offset - lastStart > 0) {
267271
require(
268-
allowedAdvisories[string(report[lastStart:lastStart + offset - lastStart - 1])] == FLAG_ALLOWED,
272+
allowedAdvisories[string(report[lastStart:offset - 1])] == FLAG_ALLOWED,
269273
"disallowed advisory is included"
270274
);
271275
}

contracts/Asn1Decode.sol

+20-13
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,33 @@ pragma solidity ^0.8.12;
2929
import "@ensdomains/ens-contracts/contracts/dnssec-oracle/BytesUtils.sol";
3030

3131
library NodePtr {
32-
// Unpack first byte index
32+
/// @dev Unpack first byte index
3333
function ixs(uint256 self) internal pure returns (uint256) {
3434
return uint80(self);
3535
}
36-
// Unpack first content byte index
3736

37+
/// @dev Unpack first content byte index
3838
function ixf(uint256 self) internal pure returns (uint256) {
3939
return uint80(self >> 80);
4040
}
41-
// Unpack last content byte index
4241

42+
/// @dev Unpack last content byte index
4343
function ixl(uint256 self) internal pure returns (uint256) {
4444
return uint80(self >> 160);
4545
}
46-
// Pack 3 uint80s into a uint256
4746

47+
/// @dev Pack 3 uint80s into a uint256
4848
function getPtr(uint256 _ixs, uint256 _ixf, uint256 _ixl) internal pure returns (uint256) {
49+
require(_ixs <= type(uint80).max);
50+
require(_ixf <= type(uint80).max);
51+
require(_ixl <= type(uint80).max);
4952
_ixs |= _ixf << 80;
5053
_ixs |= _ixl << 160;
5154
return _ixs;
5255
}
5356
}
5457

58+
// slither-disable-start dead-code
5559
library Asn1Decode {
5660
using NodePtr for uint256;
5761
using BytesUtils for bytes;
@@ -200,24 +204,27 @@ library Asn1Decode {
200204

201205
function readNodeLength(bytes memory der, uint256 ix) private pure returns (uint256) {
202206
uint256 length;
203-
uint80 ixFirstContentByte;
204-
uint80 ixLastContentByte;
205-
if ((der[ix + 1] & 0x80) == 0) {
206-
length = uint8(der[ix + 1]);
207-
ixFirstContentByte = uint80(ix + 2);
208-
ixLastContentByte = uint80(ixFirstContentByte + length - 1);
207+
uint256 ixFirstContentByte;
208+
uint256 ixLastContentByte;
209+
210+
uint8 b = uint8(der[ix + 1]);
211+
if ((b & 0x80) == 0) {
212+
length = b;
213+
ixFirstContentByte = ix + 2;
214+
ixLastContentByte = ixFirstContentByte + length - 1;
209215
} else {
210-
uint8 lengthbytesLength = uint8(der[ix + 1] & 0x7F);
216+
uint256 lengthbytesLength = uint256(b & 0x7F);
211217
if (lengthbytesLength == 1) {
212218
length = der.readUint8(ix + 2);
213219
} else if (lengthbytesLength == 2) {
214220
length = der.readUint16(ix + 2);
215221
} else {
216222
length = uint256(der.readBytesN(ix + 2, lengthbytesLength) >> (32 - lengthbytesLength) * 8);
217223
}
218-
ixFirstContentByte = uint80(ix + 2 + lengthbytesLength);
219-
ixLastContentByte = uint80(ixFirstContentByte + length - 1);
224+
ixFirstContentByte = ix + 2 + lengthbytesLength;
225+
ixLastContentByte = ixFirstContentByte + length - 1;
220226
}
221227
return NodePtr.getPtr(ix, ixFirstContentByte, ixLastContentByte);
222228
}
223229
}
230+
// slither-disable-end dead-code

contracts/ILCPClientErrors.sol

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ interface ILCPClientErrors {
88
error LCPClientClientStateInvalidKeyExpiration();
99
error LCPClientClientStateInvalidMrenclaveLength();
1010
error LCPClientClientStateUnexpectedMrenclave();
11-
error LCPClientClientStateEmptyOperators();
1211
error LCPClientClientStateInvalidOperatorAddress();
1312
error LCPClientClientStateInvalidOperatorAddressLength();
1413
error LCPClientClientStateInvalidOperatorsNonce();
1514
error LCPClientClientStateUnexpectedOperatorsNonce(uint64 expectedNonce);
15+
error LCPClientClientStateInvalidAllowedQuoteStatus();
16+
error LCPClientClientStateInvalidAllowedAdvisoryId();
1617

1718
error LCPClientOperatorsInvalidOrder(address prevOperator, address nextOperator);
1819
error LCPClientClientStateInvalidOperatorsThreshold();
@@ -34,6 +35,7 @@ interface ILCPClientErrors {
3435
error LCPClientUpdateStateEmittedStatesMustNotEmpty();
3536
error LCPClientUpdateStatePrevStateIdMustNotEmpty();
3637
error LCPClientUpdateStateUnexpectedPrevStateId();
38+
error LCPClientUpdateStateInconsistentConsensusState();
3739

3840
error LCPClientMisbehaviourPrevStatesMustNotEmpty();
3941

contracts/LCPClientBase.sol

+29-20
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,17 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors {
5757

5858
// --------------------- Storage fields ---------------------
5959

60+
/// @dev clientId => client storage
6061
mapping(string => ClientStorage) internal clientStorages;
6162

62-
// rootCA's public key parameters
63+
/// @dev RootCA's public key parameters
6364
AVRValidator.RSAParams internal verifiedRootCAParams;
64-
// keccak256(signingCert) => RSAParams of signing public key
65+
/// @dev keccak256(signingCert) => RSAParams of signing public key
6566
mapping(bytes32 => AVRValidator.RSAParams) internal verifiedSigningRSAParams;
6667

68+
/// @dev Reserved storage space to allow for layout changes in the future
69+
uint256[50] private __gap;
70+
6771
// --------------------- Constructor ---------------------
6872

6973
/// @custom:oz-upgrades-unsafe-allow constructor
@@ -100,7 +104,8 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors {
100104
/**
101105
* @dev initializeClient initializes a new client with the given state.
102106
* If succeeded, it returns heights at which the consensus state are stored.
103-
* The function must be only called by IBCHandler.
107+
* This function is guaranteed by the IBC contract to be called only once for each `clientId`.
108+
* @param clientId the client identifier which is unique within the IBC handler
104109
*/
105110
function initializeClient(
106111
string calldata clientId,
@@ -164,12 +169,18 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors {
164169

165170
// set allowed quote status and advisories
166171
for (uint256 i = 0; i < clientState.allowed_quote_statuses.length; i++) {
167-
clientStorage.allowedStatuses.allowedQuoteStatuses[clientState.allowed_quote_statuses[i]] =
168-
AVRValidator.FLAG_ALLOWED;
172+
string memory allowedQuoteStatus = clientState.allowed_quote_statuses[i];
173+
if (bytes(allowedQuoteStatus).length == 0) {
174+
revert LCPClientClientStateInvalidAllowedQuoteStatus();
175+
}
176+
clientStorage.allowedStatuses.allowedQuoteStatuses[allowedQuoteStatus] = AVRValidator.FLAG_ALLOWED;
169177
}
170178
for (uint256 i = 0; i < clientState.allowed_advisory_ids.length; i++) {
171-
clientStorage.allowedStatuses.allowedAdvisories[clientState.allowed_advisory_ids[i]] =
172-
AVRValidator.FLAG_ALLOWED;
179+
string memory allowedAdvisoryId = clientState.allowed_advisory_ids[i];
180+
if (bytes(allowedAdvisoryId).length == 0) {
181+
revert LCPClientClientStateInvalidAllowedAdvisoryId();
182+
}
183+
clientStorage.allowedStatuses.allowedAdvisories[allowedAdvisoryId] = AVRValidator.FLAG_ALLOWED;
173184
}
174185

175186
return clientState.latest_height;
@@ -433,16 +444,22 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors {
433444

434445
LCPCommitment.validationContextEval(pmsg.context, block.timestamp * 1e9);
435446

436-
uint128 latestHeight = clientState.latest_height.toUint128();
437447
uint128 postHeight = pmsg.postHeight.toUint128();
438-
if (latestHeight < postHeight) {
439-
clientState.latest_height = pmsg.postHeight;
440-
}
441-
442448
consensusState = clientStorage.consensusStates[postHeight];
449+
if (consensusState.stateId != bytes32(0)) {
450+
if (consensusState.stateId != pmsg.postStateId || consensusState.timestamp != uint64(pmsg.timestamp)) {
451+
revert LCPClientUpdateStateInconsistentConsensusState();
452+
}
453+
// return empty heights if the consensus state is already stored
454+
return heights;
455+
}
443456
consensusState.stateId = pmsg.postStateId;
444457
consensusState.timestamp = uint64(pmsg.timestamp);
445458

459+
uint128 latestHeight = clientState.latest_height.toUint128();
460+
if (latestHeight < postHeight) {
461+
clientState.latest_height = pmsg.postHeight;
462+
}
446463
heights = new Height.Data[](1);
447464
heights[0] = pmsg.postHeight;
448465
return heights;
@@ -639,14 +656,6 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors {
639656
}
640657
}
641658

642-
function verifyECDSASignature(bytes32 commitment, bytes memory signature, address signer)
643-
internal
644-
pure
645-
returns (bool)
646-
{
647-
return verifyECDSASignature(commitment, signature) == signer;
648-
}
649-
650659
function verifyECDSASignature(bytes32 commitment, bytes memory signature) internal pure returns (address) {
651660
if (uint8(signature[64]) < 27) {
652661
signature[64] = bytes1(uint8(signature[64]) + 27);

contracts/LCPCommitment.sol

-32
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,6 @@ library LCPCommitment {
4444
bytes state;
4545
}
4646

47-
function parseUpdateStateProxyMessage(bytes calldata messageBytes)
48-
internal
49-
pure
50-
returns (UpdateStateProxyMessage memory)
51-
{
52-
HeaderedProxyMessage memory hm = abi.decode(messageBytes, (HeaderedProxyMessage));
53-
// MSB first
54-
// 0-1: version
55-
// 2-3: message type
56-
// 4-31: reserved
57-
if (hm.header != LCP_MESSAGE_HEADER_UPDATE_STATE) {
58-
revert LCPCommitmentUnexpectedProxyMessageHeader();
59-
}
60-
return abi.decode(hm.message, (UpdateStateProxyMessage));
61-
}
62-
6347
struct MisbehaviourProxyMessage {
6448
PrevState[] prevStates;
6549
bytes context;
@@ -71,22 +55,6 @@ library LCPCommitment {
7155
bytes32 stateId;
7256
}
7357

74-
function parseMisbehaviourProxyMessage(bytes calldata messageBytes)
75-
internal
76-
pure
77-
returns (MisbehaviourProxyMessage memory)
78-
{
79-
HeaderedProxyMessage memory hm = abi.decode(messageBytes, (HeaderedProxyMessage));
80-
// MSB first
81-
// 0-1: version
82-
// 2-3: message type
83-
// 4-31: reserved
84-
if (hm.header != LCP_MESSAGE_HEADER_MISBEHAVIOUR) {
85-
revert LCPCommitmentUnexpectedProxyMessageHeader();
86-
}
87-
return abi.decode(hm.message, (MisbehaviourProxyMessage));
88-
}
89-
9058
struct ValidationContext {
9159
bytes32 header;
9260
bytes context;

contracts/LCPOperator.sol

+2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ library LCPOperator {
2121
// chainTypeSalt(CHAIN_TYPE_EVM, hex"")
2222
bytes32 internal constant CHAIN_TYPE_EVM_SALT = keccak256(abi.encodePacked(CHAIN_TYPE_EVM, hex""));
2323

24+
// slither-disable-next-line dead-code
2425
function chainTypeSalt(ChainType chainType, bytes memory args) internal pure returns (bytes32) {
2526
return keccak256(abi.encodePacked(chainType, args));
2627
}
2728

29+
// slither-disable-next-line dead-code
2830
function domainSeparatorUniversal() internal pure returns (bytes32) {
2931
return keccak256(
3032
abi.encode(

contracts/LCPUtils.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ library LCPUtils {
1717
pure
1818
returns (bytes memory bz, uint256 pos)
1919
{
20-
pos = BytesUtils.find(src, offset, src.length, needle);
20+
pos = BytesUtils.find(src, offset, src.length - offset, needle);
2121
if (pos == type(uint256).max) {
2222
revert LCPUtilsReadBytesUntilNotFound();
2323
}

scripts/gencert.sh

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/bin/env bash
22
set -eu
33

4-
out_certs_dir=./test/.tmp/testcerts
54
signing_rsa_bits=2048
65
signing_exponent=65537
76

@@ -41,23 +40,24 @@ function gen_ecdsa_signing_cert() {
4140
}
4241

4342
function usage() {
44-
echo "Usage: $0 {gen_rsa_root_cert|gen_rsa_signing_cert|gen_ecdsa_root_cert|gen_ecdsa_signing_cert}"
43+
echo "Usage: $0 <out_certs_dir> {gen_rsa_root_cert|gen_rsa_signing_cert|gen_ecdsa_root_cert|gen_ecdsa_signing_cert}"
4544
exit 1
4645
}
4746

48-
if [ $# -eq 0 ]; then
47+
if [ $# -lt 2 ]; then
4948
usage
5049
fi
5150

51+
out_certs_dir=$1
5252
mkdir -p ${out_certs_dir}
5353

54-
case "$1" in
54+
case "$2" in
5555
"gen_rsa_root_cert")
5656
gen_rsa_root_cert
5757
;;
5858
"gen_rsa_signing_cert")
59-
signing_rsa_bits=$2
60-
signing_exponent=$3
59+
signing_rsa_bits=$3
60+
signing_exponent=$4
6161
gen_rsa_signing_cert
6262
;;
6363
"gen_ecdsa_root_cert")

slither.config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,deprecated-standards,erc20-indexed,function-init-state,pragma,reentrancy-unlimited-gas,immutable-states,var-read-using-this",
2+
"detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,deprecated-standards,erc20-indexed,function-init-state,pragma,reentrancy-unlimited-gas,immutable-states,var-read-using-this,dead-code",
33
"filter_paths": "(test/|node_modules/|contracts/proto/)"
44
}

0 commit comments

Comments
 (0)