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
53 lines
1.6 KiB
Go
53 lines
1.6 KiB
Go
package vaultik
|
|
|
|
import "errors"
|
|
|
|
// blobHashHexLen is the length of a blob hash written as lowercase hex: a
|
|
// SHA-256 digest is 32 bytes, so 64 characters. Remote snapshot keys are
|
|
// SHA-256 hashes too and share this exact form.
|
|
const blobHashHexLen = 64
|
|
|
|
// shortHashLen is how many leading characters of a hash appear in log and
|
|
// error text.
|
|
const shortHashLen = 16
|
|
|
|
// errInvalidBlobHash reports a value used as a blob hash that is not
|
|
// exactly 64 lowercase hex characters. Restore, verify and prune read
|
|
// these values back from the destination, which is not trusted, so each
|
|
// one is checked before it is used to build a path or drive a read.
|
|
var errInvalidBlobHash = errors.New(
|
|
"blob hash is not 64 lowercase hex characters")
|
|
|
|
// isBlobHash reports whether s is exactly 64 lowercase hex characters.
|
|
// Every real blob hash and remote snapshot key has this form.
|
|
//
|
|
// The check is a plain function, not a method on types.BlobHash: the
|
|
// packer stores "temp-placeholder-{uuid}" as the hash of an unfinished
|
|
// blob in the local index, so the type itself must keep accepting values
|
|
// that are not hashes.
|
|
func isBlobHash(s string) bool {
|
|
if len(s) != blobHashHexLen {
|
|
return false
|
|
}
|
|
|
|
for _, r := range s {
|
|
if (r < '0' || r > '9') && (r < 'a' || r > 'f') {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// shortHash returns the leading part of a hash for log and error text. It
|
|
// never panics: a string shorter than the prefix is returned whole. A hash
|
|
// read from the destination may be malformed, and formatting one for a
|
|
// message must not crash the command.
|
|
func shortHash(s string) string {
|
|
if len(s) <= shortHashLen {
|
|
return s
|
|
}
|
|
|
|
return s[:shortHashLen]
|
|
}
|