From cca763ca95bf3ea66a125025b4340f3d6a98012d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 5 Jan 2017 03:59:51 -0800 Subject: [PATCH] peer: avoid compactblock dos. --- lib/net/peer.js | 31 ++++++++++++-------- lib/net/pool.js | 61 +++++++++++++++++++++++++-------------- lib/primitives/invitem.js | 1 + 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/lib/net/peer.js b/lib/net/peer.js index cbd05075..7217f9d3 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -111,6 +111,7 @@ function Peer(pool) { this.compactMode = -1; this.compactWitness = false; this.compactBlocks = {}; + this.compactAmount = 0; this.lastMerkle = null; this.waitingTX = 0; this.syncSent = false; @@ -866,7 +867,7 @@ Peer.prototype.sendFeeRate = function sendFeeRate(rate) { Peer.prototype.destroy = function destroy() { var connected = this.connected; - var i, keys, cmd, entry, hash; + var i, keys, cmd, entry; if (this.destroyed) return; @@ -909,14 +910,8 @@ Peer.prototype.destroy = function destroy() { entry.reject(new Error('Peer was destroyed.')); } - keys = Object.keys(this.compactBlocks); - - for (i = 0; i < keys.length; i++) { - hash = keys[i]; - entry = this.compactBlocks[hash]; - delete this.compactBlocks[hash]; - entry.destroy(); - } + this.compactBlocks = {}; + this.compactAmount = 0; this.locker.destroy(); @@ -1200,9 +1195,11 @@ Peer.prototype.blockType = function blockType() { if (this.options.spv) return invTypes.FILTERED_BLOCK; - if (this.options.compact && this.compactMode !== -1) { - if (!this.options.witness || this.compactWitness) - return invTypes.CMPCT_BLOCK; + if (this.outbound) { + if (this.options.compact && this.compactMode !== -1) { + if (!this.options.witness || this.compactWitness) + return invTypes.CMPCT_BLOCK; + } } if (this.haveWitness) @@ -2584,7 +2581,14 @@ Peer.prototype.handleCmpctBlock = co(function* handleCmpctBlock(packet) { return; } + if (this.compactAmount >= 10) { + this.logger.warning('Compact block DoS attempt (%s).', this.hostname); + this.destroy(); + return; + } + this.compactBlocks[hash] = block; + this.compactAmount++; this.send(new packets.GetBlockTxnPacket(block.toRequest())); @@ -2652,10 +2656,13 @@ Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(packet) { if (!block) { this.logger.debug('Peer sent unsolicited blocktxn (%s).', this.hostname); + this.compactBlocks = {}; + this.compactAmount = 0; return; } delete this.compactBlocks[res.hash]; + this.compactAmount--; if (!block.fillMissing(res)) { this.increaseBan(100); diff --git a/lib/net/pool.js b/lib/net/pool.js index 5343f78d..93a5ed32 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -364,7 +364,7 @@ Pool.prototype._close = co(function* close() { for (i = 0; i < hashes.length; i++) { hash = hashes[i]; item = this.invMap[hash]; - item.finish(); + item.resolve(); } this.peers.destroy(); @@ -455,7 +455,7 @@ Pool.prototype.listen = function listen() { this.server = this.createServer(); this.server.on('connection', function(socket) { - self.handleInbound(socket); + self.handleSocket(socket); }); this.server.on('listening', function() { @@ -496,11 +496,11 @@ Pool.prototype.unlisten = function unlisten() { * @param {net.Socket} socket */ -Pool.prototype.handleInbound = function handleInbound(socket) { +Pool.prototype.handleSocket = function handleSocket(socket) { var host; if (!socket.remoteAddress) { - this.logger.debug('Ignoring disconnected leech.'); + this.logger.debug('Ignoring disconnected peer.'); socket.destroy(); return; } @@ -508,13 +508,13 @@ Pool.prototype.handleInbound = function handleInbound(socket) { host = IP.normalize(socket.remoteAddress); if (this.peers.inbound >= this.maxInbound) { - this.logger.debug('Ignoring leech: too many inbound (%s).', host); + this.logger.debug('Ignoring peer: too many inbound (%s).', host); socket.destroy(); return; } if (this.hosts.isBanned(host)) { - this.logger.debug('Ignoring banned leech (%s).', host); + this.logger.debug('Ignoring banned peer (%s).', host); socket.destroy(); return; } @@ -585,8 +585,6 @@ Pool.prototype.setLoader = function setLoader(peer) { peer.sync(); - this.fillOutbound(); - this.emit('loader', peer); }; @@ -1021,7 +1019,7 @@ Pool.prototype.handleBlock = co(function* handleBlock(peer, block) { Pool.prototype.handleTX = co(function* handleTX(peer, tx) { var hash = tx.hash('hex'); var requested = this.fulfill(peer, hash); - var i, missing; + var missing; if (!requested) { peer.invFilter.add(tx.hash()); @@ -2169,7 +2167,7 @@ BroadcastItem.prototype.refresh = function refresh() { this.timeout = setTimeout(function() { self.emit('timeout'); - self.finish(new Error('Timed out.')); + self.reject(new Error('Timed out.')); }, this.pool.invTimeout); }; @@ -2192,28 +2190,49 @@ BroadcastItem.prototype.announce = function announce() { }; /** - * Finish the broadcast, potentially with an error. - * @param {Error?} err + * Finish the broadcast. */ -BroadcastItem.prototype.finish = function finish(err) { - var i, job; - - assert(this.timeout, 'Already finished.'); +BroadcastItem.prototype.cleanup = function cleanup() { + assert(this.timeout != null, 'Already finished.'); assert(this.pool.invMap[this.hash], 'Already finished.'); clearTimeout(this.timeout); this.timeout = null; delete this.pool.invMap[this.hash]; +}; + +/** + * Finish the broadcast, return with an error. + * @param {Error} err + */ + +BroadcastItem.prototype.reject = function reject(err) { + var i, job; + + this.cleanup(); for (i = 0; i < this.jobs.length; i++) { job = this.jobs[i]; - if (err) { - job.reject(err); - continue; - } - job.resolve(); + job.reject(err); + } + + this.jobs.length = 0; +}; + +/** + * Finish the broadcast successfully. + */ + +BroadcastItem.prototype.resolve = function resolve() { + var i, job; + + this.cleanup(); + + for (i = 0; i < this.jobs.length; i++) { + job = this.jobs[i]; + job.resolve(false); } this.jobs.length = 0; diff --git a/lib/primitives/invitem.js b/lib/primitives/invitem.js index c87e7244..af1da64c 100644 --- a/lib/primitives/invitem.js +++ b/lib/primitives/invitem.js @@ -10,6 +10,7 @@ var constants = require('../protocol/constants'); var BufferReader = require('../utils/reader'); var StaticWriter = require('../utils/staticwriter'); +var util = require('../utils/util'); /** * Inv Item