Skip to content
This repository was archived by the owner on Jul 3, 2019. It is now read-only.

Commit f8e0f25

Browse files
authored
fix(index): additional bucket entry verification with checksum (#72)
1 parent bec7042 commit f8e0f25

7 files changed

+37
-17
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ cacache.get.byDigest(cachePath, tarballSha512).then(data => {
7676
* Extraction by key or by content address (shasum, etc)
7777
* Multi-hash support - safely host sha1, sha512, etc, in a single cache
7878
* Automatic content deduplication
79-
* Fault tolerance and consistency guarantees for both insertion and extraction
79+
* Fault tolerance (immune to corruption, partial writes, etc)
80+
* Consistency guarantees on read and write (full data verification)
8081
* Lockless, high-concurrency cache access
8182
* Streaming support
8283
* Promise support
84+
* Pretty darn fast
8385
* Arbitrary metadata storage
8486
* Garbage collection and additional offline verification
8587

lib/entry-index.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function insert (cache, key, digest, opts) {
4949
//
5050
// Thanks to @isaacs for the whiteboarding session that ended up with this.
5151
return appendFileAsync(
52-
bucket, `\n${stringified.length}\t${stringified}`
52+
bucket, `\n${hashEntry(stringified)}\t${stringified}`
5353
).then(() => entry)
5454
}).then(entry => (
5555
fixOwner.chownr(bucket, opts.uid, opts.gid).then(() => (
@@ -140,9 +140,11 @@ function bucketEntries (cache, bucket, filter) {
140140
).then(data => {
141141
let entries = []
142142
data.split('\n').forEach(entry => {
143+
if (!entry) { return }
143144
const pieces = entry.split('\t')
144-
if (!pieces[1] || pieces[1].length !== parseInt(pieces[0], 10)) {
145-
// Length is no good! Corruption ahoy!
145+
if (!pieces[1] || hashEntry(pieces[1]) !== pieces[0]) {
146+
// Hash is no good! Corruption or malice? Doesn't matter!
147+
// EJECT EJECT
146148
return
147149
}
148150
let obj
@@ -175,9 +177,18 @@ function bucketPath (cache, key) {
175177

176178
module.exports._hashKey = hashKey
177179
function hashKey (key) {
180+
return hash(key, 'sha256')
181+
}
182+
183+
module.exports._hashEntry = hashEntry
184+
function hashEntry (str) {
185+
return hash(str, 'sha1')
186+
}
187+
188+
function hash (str, digest) {
178189
return crypto
179-
.createHash('sha256')
180-
.update(key)
190+
.createHash(digest)
191+
.update(str)
181192
.digest('hex')
182193
}
183194

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "6.1.2",
44
"cache-version": {
55
"content": "2",
6-
"index": "2"
6+
"index": "3"
77
},
88
"description": "General content-addressable cache system that maintains a filesystem registry of file data.",
99
"main": "index.js",

test/index.find.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ BB.promisifyAll(fs)
1212

1313
const CACHE = path.join(testDir, 'cache')
1414
const contentPath = require('../lib/content/path')
15-
const Dir = Tacks.Dir
1615
const index = require('../lib/entry-index')
1716

1817
test('index.find cache hit', function (t) {
@@ -178,7 +177,7 @@ test('index.find garbled data in index file', function (t) {
178177
})
179178
const fixture = new Tacks(CacheIndex({
180179
'whatever': '\n' +
181-
`${stringified.length}\t${stringified}` +
180+
`${index._hashEntry(stringified)}\t${stringified}` +
182181
'\n{"key": "' + key + '"\noway'
183182
}))
184183
fixture.create(CACHE)

test/index.insert.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test('basic insertion', function (t) {
3535
}).then(data => {
3636
t.equal(data[0], '\n', 'first entry starts with a \\n')
3737
const split = data.split('\t')
38-
t.equal(parseInt(split[0], 10), split[1].length, 'length header correct')
38+
t.equal(split[0].slice(1), index._hashEntry(split[1]), 'consistency header correct')
3939
const entry = JSON.parse(split[1])
4040
t.ok(entry.time, 'entry has a timestamp')
4141
t.deepEqual(entry, {

test/util/cache-index.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
'use strict'
22

3-
const bucketPath = require('../../lib/entry-index')._bucketPath
3+
const index = require('../../lib/entry-index')
44
const path = require('path')
55
const Tacks = require('tacks')
66

7+
const bucketPath = index._bucketPath
8+
const hashEntry = index._hashEntry
9+
710
const Dir = Tacks.Dir
811
const File = Tacks.File
912

@@ -28,7 +31,7 @@ function CacheIndex (entries, hashAlgorithm) {
2831
}
2932
serialised = '\n' + lines.map(line => {
3033
const stringified = JSON.stringify(line)
31-
return `${stringified.length}\t${stringified}`
34+
return `${hashEntry(stringified)}\t${stringified}`
3235
}).join('\n')
3336
}
3437
insertContent(tree, parts, serialised)

test/verify.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ test('removes corrupted index entries from buckets', t => {
4848
t.equal(stats.totalEntries, 1, 'only one entry counted')
4949
return fs.readFileAsync(BUCKET, 'utf8')
5050
}).then(bucketData => {
51-
// cleaned-up entries have different timestamps
52-
const newTime = bucketData.match(/"time":([0-9]+)/)[1]
53-
const target = BUCKETDATA.replace(/"time":[0-9]+/, `"time":${newTime}`)
54-
t.deepEqual(bucketData, target, 'bucket only contains good entry')
51+
const bucketEntry = JSON.parse(bucketData.split('\t')[1])
52+
const targetEntry = JSON.parse(BUCKETDATA.split('\t')[1])
53+
targetEntry.time = bucketEntry.time // different timestamps
54+
t.deepEqual(
55+
bucketEntry, targetEntry, 'bucket only contains good entry')
5556
})
5657
})
5758
})
@@ -75,7 +76,11 @@ test('removes shadowed index entries from buckets', t => {
7576
time: +(bucketData.match(/"time":([0-9]+)/)[1]),
7677
metadata: newEntry.metadata
7778
})
78-
t.equal(bucketData, `\n${stringified.length}\t${stringified}`)
79+
t.equal(
80+
bucketData,
81+
`\n${index._hashEntry(stringified)}\t${stringified}`,
82+
'only the most recent entry is still in the bucket'
83+
)
7984
})
8085
})
8186
})

0 commit comments

Comments
 (0)