Restore files at 0600 and make the blob hash check unskippable (closes #163)
Regular files are now created with O_EXCL at mode 0600 and given their stored mode only after the content is written and closed, so a file whose stored mode is restrictive is never briefly readable by other local users mid-restore. A file whose write or close fails is removed rather than left partial, and a chmod failure is a user-visible warning instead of a debug line. hashVerifyReader.Close now errors when closed before EOF, so a short read or early close can never obtain a blob whose hash was not verified; downloadBlobToCache drops the cache entry on any such failure. verifyFile (--verify) now rejects a restored file with bytes past its last chunk. Tests cover each behaviour under umask 022. Model: opus-4-8
This commit was merged in pull request #182.
This commit is contained in:
@@ -18,6 +18,13 @@ import (
|
||||
// not match the expected double-SHA-256 hash.
|
||||
var errBlobHashMismatch = errors.New("blob hash mismatch")
|
||||
|
||||
// errBlobNotFullyRead is returned when the verifying reader is closed
|
||||
// before its plaintext reached EOF. The hash can only be checked once
|
||||
// the whole stream has been read, so an early or short-read close must
|
||||
// fail rather than silently skip verification.
|
||||
var errBlobNotFullyRead = errors.New(
|
||||
"blob closed before fully read; hash not verified")
|
||||
|
||||
// hashVerifyReader wraps a blobgen.Reader and verifies the double-SHA-256 hash
|
||||
// of decrypted plaintext when Close is called. It reuses the hash that
|
||||
// blobgen.Reader already computes internally via its TeeReader, avoiding
|
||||
@@ -38,21 +45,26 @@ func (h *hashVerifyReader) Read(p []byte) (int, error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close verifies the hash (if the stream was fully read) and closes underlying readers.
|
||||
// Close closes the underlying readers and verifies the blob hash. The
|
||||
// hash check cannot be skipped: closing before the plaintext reached
|
||||
// EOF (a short read or an early close) is an error, so a caller can
|
||||
// never obtain unverified blob bytes.
|
||||
func (h *hashVerifyReader) Close() error {
|
||||
readerErr := h.reader.Close()
|
||||
fetcherErr := h.fetcher.Close()
|
||||
|
||||
if h.done {
|
||||
firstHash := h.reader.Sum256()
|
||||
secondHasher := sha256.New()
|
||||
secondHasher.Write(firstHash)
|
||||
if !h.done {
|
||||
return errBlobNotFullyRead
|
||||
}
|
||||
|
||||
actualHashHex := hex.EncodeToString(secondHasher.Sum(nil))
|
||||
if actualHashHex != h.blobHash {
|
||||
return fmt.Errorf("%w: expected %s, got %s",
|
||||
errBlobHashMismatch, h.blobHash[:16], actualHashHex[:16])
|
||||
}
|
||||
firstHash := h.reader.Sum256()
|
||||
secondHasher := sha256.New()
|
||||
secondHasher.Write(firstHash)
|
||||
|
||||
actualHashHex := hex.EncodeToString(secondHasher.Sum(nil))
|
||||
if actualHashHex != h.blobHash {
|
||||
return fmt.Errorf("%w: expected %s, got %s",
|
||||
errBlobHashMismatch, h.blobHash[:16], actualHashHex[:16])
|
||||
}
|
||||
|
||||
if readerErr != nil {
|
||||
|
||||
Reference in New Issue
Block a user