Skip to content

Commit e8d722c

Browse files
dirkmcvmx
authored andcommitted
fix: ignore unwanted blocks (#194)
This fixes a potential DoS attack, ipfs-bitswap was putting blocks in the blockstore even if they weren't wanted.
1 parent 624e4df commit e8d722c

File tree

4 files changed

+63
-7
lines changed

4 files changed

+63
-7
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"dirty-chai": "^2.0.1",
5050
"ipfs-repo": "~0.26.3",
5151
"libp2p": "~0.24.2",
52-
"libp2p-kad-dht": "~0.14.8",
52+
"libp2p-kad-dht": "~0.15.0",
5353
"libp2p-mplex": "~0.8.4",
5454
"libp2p-secio": "~0.11.1",
5555
"libp2p-tcp": "~0.13.0",

src/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,31 @@ class Bitswap {
8787
const blocks = Array.from(incoming.blocks.values())
8888

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

94-
this.wm.cancelWants(toCancel)
94+
this.wm.cancelWants(wanted)
9595

9696
each(
9797
blocks,
98-
(b, cb) => this._handleReceivedBlock(peerId, b, cb),
98+
(b, cb) => {
99+
const wasWanted = wanted.includes(b.cid)
100+
this._handleReceivedBlock(peerId, b, wasWanted, cb)
101+
},
99102
callback
100103
)
101104
})
102105
}
103106

104-
_handleReceivedBlock (peerId, block, callback) {
107+
_handleReceivedBlock (peerId, block, wasWanted, callback) {
105108
this._log('received block')
106109

107110
waterfall([
108111
(cb) => this.blockstore.has(block.cid, cb),
109112
(has, cb) => {
110113
this._updateReceiveCounters(peerId.toB58String(), block, has)
111-
if (has) {
114+
if (has || !wasWanted) {
112115
return nextTick(cb)
113116
}
114117

test/bitswap-mock-internals.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ describe('bitswap with mocks', function () {
6464
const b1 = blocks[0]
6565
const b2 = blocks[1]
6666

67+
bs.wm.wantBlocks([b1.cid, b2.cid])
68+
6769
const msg = new Message(false)
6870
msg.addBlock(b1)
6971
msg.addBlock(b2)
@@ -140,14 +142,18 @@ describe('bitswap with mocks', function () {
140142
map(_.range(5), (i, cb) => {
141143
const msg = new Message(false)
142144
msg.addBlock(blocks[i])
143-
msg.addBlock(blocks[5 + 1])
145+
msg.addBlock(blocks[i + 5])
144146
cb(null, msg)
145147
}, (err, messages) => {
146148
expect(err).to.not.exist()
147149
let i = 0
148150
eachSeries(others, (other, cb) => {
149151
const msg = messages[i]
150152
i++
153+
154+
const cids = [...msg.blocks.values()].map(b => b.cid)
155+
bs.wm.wantBlocks(cids)
156+
151157
bs._receiveMessage(other, msg, (err) => {
152158
expect(err).to.not.exist()
153159
storeHasBlocks(msg, repo.blocks, cb)
@@ -157,6 +163,52 @@ describe('bitswap with mocks', function () {
157163
}
158164
})
159165
})
166+
167+
it('ignore unwanted blocks', (done) => {
168+
const bs = new Bitswap(mockLibp2pNode(), repo.blocks)
169+
bs.start((err) => {
170+
expect(err).to.not.exist()
171+
172+
const other = ids[1]
173+
174+
const b1 = blocks[2]
175+
const b2 = blocks[3]
176+
const b3 = blocks[4]
177+
178+
bs.wm.wantBlocks([b2.cid])
179+
180+
const msg = new Message(false)
181+
msg.addBlock(b1)
182+
msg.addBlock(b2)
183+
msg.addBlock(b3)
184+
185+
bs._receiveMessage(other, msg, (err) => {
186+
expect(err).to.not.exist()
187+
188+
map([b1.cid, b2.cid, b3.cid], (cid, cb) => repo.blocks.has(cid, cb), (err, res) => {
189+
expect(err).to.not.exist()
190+
191+
expect(res).to.eql([false, true, false])
192+
193+
const ledger = bs.ledgerForPeer(other)
194+
expect(ledger.peer).to.equal(other.toPrint())
195+
expect(ledger.value).to.equal(0)
196+
197+
// Note: Keeping track of received bytes for blocks affects the
198+
// debt ratio, which in future may be used as part of fairness
199+
// algorithms when prioritizing who to send blocks to.
200+
// So we may want to revise whether we record received blocks from
201+
// a peer even if we didn't ask for the blocks.
202+
// For now keeping it liks this to match the go implementation:
203+
// https://github.com/ipfs/go-bitswap/blob/acc22c283722c15436120ae522c8e8021d0b06f8/bitswap.go#L293
204+
expect(ledger.sent).to.equal(0)
205+
expect(ledger.recv).to.equal(144)
206+
expect(ledger.exchanged).to.equal(3)
207+
done()
208+
})
209+
})
210+
})
211+
})
160212
})
161213

162214
describe('get', () => {

test/bitswap-stats.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ describe('bitswap stats', () => {
8787
statsComputeThrottleTimeout: 500 // fast update interval for tests
8888
}))
8989
bs = bitswaps[0]
90+
bs.wm.wantBlocks(blocks.map(b => b.cid))
9091
})
9192

9293
// start the first bitswap

0 commit comments

Comments
 (0)