Validate blob hashes, offsets and lengths from the destination (closes #155)
A blob hash read back from the downloaded snapshot database or the store listing was trusted unchecked. A hostile remote could set a hash such as "aa/../../etc" and have a decrypted blob written outside the cache directory, or feed a short or negative value that panicked a command. blobDiskCache.path now refuses any key with a path separator, and ReadAt rejects a negative offset or length, bounding so a sum cannot overflow past the check. A new isBlobHash helper gates FetchBlob, shallow and deep verify, and restore: buildBlobIndexes rejects every hash from the snapshot database before any fetch. The blobs/ and metadata/ listings skip a non-conforming name, and short-hash prefixes in log and error text go through a panic-safe shortHash helper. Model: opus-4-8
This commit was merged in pull request #195.
This commit is contained in:
@@ -59,7 +59,7 @@ func (h *hashVerifyReader) Close() error {
|
||||
actualHashHex := hex.EncodeToString(blobgen.DoubleSHA256(h.reader.Sum256()))
|
||||
if actualHashHex != h.blobHash {
|
||||
return fmt.Errorf("%w: expected %s, got %s",
|
||||
errBlobHashMismatch, h.blobHash[:16], actualHashHex[:16])
|
||||
errBlobHashMismatch, shortHash(h.blobHash), shortHash(actualHashHex))
|
||||
}
|
||||
|
||||
if readerErr != nil {
|
||||
@@ -103,6 +103,13 @@ func (v *Vaultik) FetchAndDecryptBlob(
|
||||
func (v *Vaultik) FetchBlob(
|
||||
ctx context.Context, blobHash string, expectedSize int64,
|
||||
) (io.ReadCloser, int64, error) {
|
||||
// blobHash reaches here from the snapshot database, which is not
|
||||
// trusted. Reject a malformed hash before it is spliced into a storage
|
||||
// path (blobHash[:2]/blobHash[2:4]) or a fetch is attempted.
|
||||
if !isBlobHash(blobHash) {
|
||||
return nil, 0, fmt.Errorf("%w: %s", errInvalidBlobHash, shortHash(blobHash))
|
||||
}
|
||||
|
||||
blobPath := fmt.Sprintf("blobs/%s/%s/%s", blobHash[:2], blobHash[2:4], blobHash)
|
||||
|
||||
t0 := time.Now()
|
||||
@@ -110,7 +117,7 @@ func (v *Vaultik) FetchBlob(
|
||||
getDur := time.Since(t0)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("downloading blob %s: %w", blobHash[:16], err)
|
||||
return nil, 0, fmt.Errorf("downloading blob %s: %w", shortHash(blobHash), err)
|
||||
}
|
||||
|
||||
t0 = time.Now()
|
||||
@@ -120,11 +127,11 @@ func (v *Vaultik) FetchBlob(
|
||||
if err != nil {
|
||||
_ = rc.Close()
|
||||
|
||||
return nil, 0, fmt.Errorf("stat blob %s: %w", blobHash[:16], err)
|
||||
return nil, 0, fmt.Errorf("stat blob %s: %w", shortHash(blobHash), err)
|
||||
}
|
||||
|
||||
log.Debug("FetchBlob round-trips",
|
||||
"hash", blobHash[:16],
|
||||
"hash", shortHash(blobHash),
|
||||
"ms_storage_get", getDur.Milliseconds(),
|
||||
"ms_storage_stat", statDur.Milliseconds(),
|
||||
"expected_size", expectedSize,
|
||||
|
||||
Reference in New Issue
Block a user