Skip to content

Commit

Permalink
fix: ignore unwanted blocks (#194)
Browse files Browse the repository at this point in the history
This fixes a potential DoS attack, ipfs-bitswap was putting blocks
in the blockstore even if they weren't wanted.
  • Loading branch information
dirkmc authored and vmx committed May 30, 2019
1 parent 624e4df commit e8d722c
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 7 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dirty-chai": "^2.0.1",
"ipfs-repo": "~0.26.3",
"libp2p": "~0.24.2",
"libp2p-kad-dht": "~0.14.8",
"libp2p-kad-dht": "~0.15.0",
"libp2p-mplex": "~0.8.4",
"libp2p-secio": "~0.11.1",
"libp2p-tcp": "~0.13.0",
Expand Down
13 changes: 8 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,28 +87,31 @@ class Bitswap {
const blocks = Array.from(incoming.blocks.values())

// quickly send out cancels, reduces chances of duplicate block receives
const toCancel = blocks
const wanted = blocks
.filter((b) => this.wm.wantlist.contains(b.cid))
.map((b) => b.cid)

this.wm.cancelWants(toCancel)
this.wm.cancelWants(wanted)

each(
blocks,
(b, cb) => this._handleReceivedBlock(peerId, b, cb),
(b, cb) => {
const wasWanted = wanted.includes(b.cid)
this._handleReceivedBlock(peerId, b, wasWanted, cb)
},
callback
)
})
}

_handleReceivedBlock (peerId, block, callback) {
_handleReceivedBlock (peerId, block, wasWanted, callback) {
this._log('received block')

waterfall([
(cb) => this.blockstore.has(block.cid, cb),
(has, cb) => {
this._updateReceiveCounters(peerId.toB58String(), block, has)
if (has) {
if (has || !wasWanted) {
return nextTick(cb)
}

Expand Down
54 changes: 53 additions & 1 deletion test/bitswap-mock-internals.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ describe('bitswap with mocks', function () {
const b1 = blocks[0]
const b2 = blocks[1]

bs.wm.wantBlocks([b1.cid, b2.cid])

const msg = new Message(false)
msg.addBlock(b1)
msg.addBlock(b2)
Expand Down Expand Up @@ -140,14 +142,18 @@ describe('bitswap with mocks', function () {
map(_.range(5), (i, cb) => {
const msg = new Message(false)
msg.addBlock(blocks[i])
msg.addBlock(blocks[5 + 1])
msg.addBlock(blocks[i + 5])
cb(null, msg)
}, (err, messages) => {
expect(err).to.not.exist()
let i = 0
eachSeries(others, (other, cb) => {
const msg = messages[i]
i++

const cids = [...msg.blocks.values()].map(b => b.cid)
bs.wm.wantBlocks(cids)

bs._receiveMessage(other, msg, (err) => {
expect(err).to.not.exist()
storeHasBlocks(msg, repo.blocks, cb)
Expand All @@ -157,6 +163,52 @@ describe('bitswap with mocks', function () {
}
})
})

it('ignore unwanted blocks', (done) => {
const bs = new Bitswap(mockLibp2pNode(), repo.blocks)
bs.start((err) => {
expect(err).to.not.exist()

const other = ids[1]

const b1 = blocks[2]
const b2 = blocks[3]
const b3 = blocks[4]

bs.wm.wantBlocks([b2.cid])

const msg = new Message(false)
msg.addBlock(b1)
msg.addBlock(b2)
msg.addBlock(b3)

bs._receiveMessage(other, msg, (err) => {
expect(err).to.not.exist()

map([b1.cid, b2.cid, b3.cid], (cid, cb) => repo.blocks.has(cid, cb), (err, res) => {
expect(err).to.not.exist()

expect(res).to.eql([false, true, false])

const ledger = bs.ledgerForPeer(other)
expect(ledger.peer).to.equal(other.toPrint())
expect(ledger.value).to.equal(0)

// Note: Keeping track of received bytes for blocks affects the
// debt ratio, which in future may be used as part of fairness
// algorithms when prioritizing who to send blocks to.
// So we may want to revise whether we record received blocks from
// a peer even if we didn't ask for the blocks.
// For now keeping it liks this to match the go implementation:
// https://github.com/ipfs/go-bitswap/blob/acc22c283722c15436120ae522c8e8021d0b06f8/bitswap.go#L293
expect(ledger.sent).to.equal(0)
expect(ledger.recv).to.equal(144)
expect(ledger.exchanged).to.equal(3)
done()
})
})
})
})
})

describe('get', () => {
Expand Down
1 change: 1 addition & 0 deletions test/bitswap-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ describe('bitswap stats', () => {
statsComputeThrottleTimeout: 500 // fast update interval for tests
}))
bs = bitswaps[0]
bs.wm.wantBlocks(blocks.map(b => b.cid))
})

// start the first bitswap
Expand Down

0 comments on commit e8d722c

Please sign in to comment.