diff --git a/common/src/main/java/bisq/common/util/ExtraDataMapValidator.java b/common/src/main/java/bisq/common/util/ExtraDataMapValidator.java new file mode 100644 index 0000000000..d0f10bc5ba --- /dev/null +++ b/common/src/main/java/bisq/common/util/ExtraDataMapValidator.java @@ -0,0 +1,75 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.common.util; + +import java.util.HashMap; +import java.util.Map; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Validator for extraDataMap fields used in network payloads. + * Ensures that we don't get the network attacked by huge data inserted there. + */ +@Slf4j +public class ExtraDataMapValidator { + // ExtraDataMap is only used for exceptional cases to not break backward compatibility. + // We don't expect many entries there. + public final static int MAX_SIZE = 10; + public final static int MAX_KEY_LENGTH = 100; + public final static int MAX_VALUE_LENGTH = 100000; // 100 kb + + public static Map getValidatedExtraDataMap(@Nullable Map extraDataMap) { + return getValidatedExtraDataMap(extraDataMap, MAX_SIZE, MAX_KEY_LENGTH, MAX_VALUE_LENGTH); + } + + public static Map getValidatedExtraDataMap(@Nullable Map extraDataMap, int maxSize, int maxKeyLength, int maxValueLength) { + if (extraDataMap == null) + return null; + + try { + checkArgument(extraDataMap.entrySet().size() <= maxSize, "Size of map must not exceed " + maxSize); + extraDataMap.forEach((key, value) -> { + checkArgument(key.length() <= maxKeyLength, "Length of key must not exceed " + maxKeyLength); + checkArgument(value.length() <= maxValueLength, "Length of value must not exceed " + maxValueLength); + }); + return extraDataMap; + } catch (Throwable t) { + return new HashMap<>(); + } + } + + public static void validate(@Nullable Map extraDataMap) { + validate(extraDataMap, MAX_SIZE, MAX_KEY_LENGTH, MAX_VALUE_LENGTH); + } + + public static void validate(@Nullable Map extraDataMap, int maxSize, int maxKeyLength, int maxValueLength) { + if (extraDataMap == null) + return; + + checkArgument(extraDataMap.entrySet().size() <= maxSize, "Size of map must not exceed " + maxSize); + extraDataMap.forEach((key, value) -> { + checkArgument(key.length() <= maxKeyLength, "Length of key must not exceed " + maxKeyLength); + checkArgument(value.length() <= maxValueLength, "Length of value must not exceed " + maxValueLength); + }); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java index e16542887f..4b1035a25e 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java @@ -17,12 +17,15 @@ package bisq.core.dao.governance.blindvote; +import bisq.core.btc.wallet.Restrictions; import bisq.core.dao.governance.period.PeriodService; import bisq.core.dao.governance.proposal.ProposalValidationException; import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.model.blockchain.Tx; import bisq.core.dao.state.model.governance.DaoPhase; +import bisq.common.util.ExtraDataMapValidator; + import javax.inject.Inject; import java.util.Optional; @@ -58,12 +61,20 @@ public class BlindVoteValidator { checkNotNull(blindVote.getEncryptedVotes(), "encryptedProposalList must not be null"); checkArgument(blindVote.getEncryptedVotes().length > 0, "encryptedProposalList must not be empty"); - checkNotNull(blindVote.getTxId(), "txId must not be null"); - checkArgument(!blindVote.getTxId().isEmpty(), "txId must not be empty"); - checkArgument(blindVote.getStake() > 0, "stake must be positive"); + checkArgument(blindVote.getEncryptedVotes().length <= 100000, + "encryptedProposalList must not exceed 100kb"); + + checkNotNull(blindVote.getTxId(), "Tx ID must not be null"); + checkArgument(blindVote.getTxId().length() == 64, "Tx ID must be 64 chars"); + checkArgument(blindVote.getStake() >= Restrictions.getMinNonDustOutput().value, "Stake must be at least MinNonDustOutput"); + checkNotNull(blindVote.getEncryptedMeritList(), "getEncryptedMeritList must not be null"); checkArgument(blindVote.getEncryptedMeritList().length > 0, "getEncryptedMeritList must not be empty"); + checkArgument(blindVote.getEncryptedMeritList().length <= 100000, + "getEncryptedMeritList must not exceed 100kb"); + + ExtraDataMapValidator.validate(blindVote.getExtraDataMap()); } catch (Throwable e) { log.warn(e.toString()); throw new ProposalValidationException(e); diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java index d303246d94..e0f0118b1c 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java @@ -28,6 +28,8 @@ import bisq.core.dao.state.model.governance.DaoPhase; import bisq.core.dao.state.model.governance.Proposal; import bisq.core.dao.state.model.governance.ReimbursementProposal; +import bisq.common.util.ExtraDataMapValidator; + import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -66,6 +68,8 @@ public abstract class ProposalValidator implements ConsensusCritical { checkArgument(proposal.getLink().length() <= 200, "Link must not exceed 200 chars"); if (proposal.getTxId() != null) checkArgument(proposal.getTxId().length() == 64, "Tx ID must be 64 chars"); + + ExtraDataMapValidator.validate(proposal.getExtraDataMap()); } catch (Throwable throwable) { throw new ProposalValidationException(throwable); } diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java index 8ba1639092..38b993f1e6 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java @@ -28,6 +28,7 @@ import bisq.common.app.Capabilities; import bisq.common.app.Capability; import bisq.common.crypto.Sig; import bisq.common.proto.persistable.PersistablePayload; +import bisq.common.util.ExtraDataMapValidator; import io.bisq.generated.protobuffer.PB; @@ -88,7 +89,7 @@ public class TempProposalPayload implements LazyProcessedPayload, ProtectedStora @Nullable Map extraDataMap) { this.proposal = proposal; this.ownerPubKeyEncoded = ownerPubPubKeyEncoded; - this.extraDataMap = extraDataMap; + this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); ownerPubKey = Sig.getPublicKeyFromBytes(ownerPubKeyEncoded); } diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java index fd53adc226..395b29c959 100644 --- a/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java +++ b/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java @@ -26,6 +26,7 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.common.proto.ProtobufferRuntimeException; import bisq.common.proto.network.NetworkPayload; import bisq.common.proto.persistable.PersistablePayload; +import bisq.common.util.ExtraDataMapValidator; import io.bisq.generated.protobuffer.PB; @@ -64,13 +65,13 @@ public abstract class Proposal implements PersistablePayload, NetworkPayload, Co byte version, long creationDate, @Nullable String txId, - @SuppressWarnings("NullableProblems") Map extraDataMap) { + @Nullable Map extraDataMap) { this.name = name; this.link = link; this.version = version; this.creationDate = creationDate; this.txId = txId; - this.extraDataMap = extraDataMap; + this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); }