mirror of
https://github.com/octoleo/restic.git
synced 2024-11-24 21:57:41 +00:00
pack: verify integrity of pack file header
This commit is contained in:
parent
d8916bc3d9
commit
75e72d826c
@ -1,6 +1,7 @@
|
|||||||
package pack
|
package pack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -74,7 +75,7 @@ func (p *Packer) Finalize() error {
|
|||||||
p.m.Lock()
|
p.m.Lock()
|
||||||
defer p.m.Unlock()
|
defer p.m.Unlock()
|
||||||
|
|
||||||
header, err := p.makeHeader()
|
header, err := makeHeader(p.blobs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -83,6 +84,11 @@ func (p *Packer) Finalize() error {
|
|||||||
nonce := crypto.NewRandomNonce()
|
nonce := crypto.NewRandomNonce()
|
||||||
encryptedHeader = append(encryptedHeader, nonce...)
|
encryptedHeader = append(encryptedHeader, nonce...)
|
||||||
encryptedHeader = p.k.Seal(encryptedHeader, nonce, header, nil)
|
encryptedHeader = p.k.Seal(encryptedHeader, nonce, header, nil)
|
||||||
|
encryptedHeader = binary.LittleEndian.AppendUint32(encryptedHeader, uint32(len(encryptedHeader)))
|
||||||
|
|
||||||
|
if err := verifyHeader(p.k, encryptedHeader, p.blobs); err != nil {
|
||||||
|
return fmt.Errorf("detected data corruption while writing pack-file header: %w\nCorrupted data is either caused by hardware problems or bugs in restic. Please open an issue at https://github.com/restic/restic/issues/new/choose for further troubleshooting", err)
|
||||||
|
}
|
||||||
|
|
||||||
// append the header
|
// append the header
|
||||||
n, err := p.wr.Write(encryptedHeader)
|
n, err := p.wr.Write(encryptedHeader)
|
||||||
@ -90,18 +96,33 @@ func (p *Packer) Finalize() error {
|
|||||||
return errors.Wrap(err, "Write")
|
return errors.Wrap(err, "Write")
|
||||||
}
|
}
|
||||||
|
|
||||||
hdrBytes := len(encryptedHeader)
|
if n != len(encryptedHeader) {
|
||||||
if n != hdrBytes {
|
|
||||||
return errors.New("wrong number of bytes written")
|
return errors.New("wrong number of bytes written")
|
||||||
}
|
}
|
||||||
|
p.bytes += uint(len(encryptedHeader))
|
||||||
|
|
||||||
// write length
|
return nil
|
||||||
err = binary.Write(p.wr, binary.LittleEndian, uint32(hdrBytes))
|
}
|
||||||
|
|
||||||
|
func verifyHeader(k *crypto.Key, header []byte, expected []restic.Blob) error {
|
||||||
|
// do not offer a way to skip the pack header verification, as pack headers are usually small enough
|
||||||
|
// to not result in a significant performance impact
|
||||||
|
|
||||||
|
decoded, hdrSize, err := List(k, bytes.NewReader(header), int64(len(header)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "binary.Write")
|
return fmt.Errorf("header decoding failed: %w", err)
|
||||||
|
}
|
||||||
|
if hdrSize != uint32(len(header)) {
|
||||||
|
return fmt.Errorf("unexpected header size %v instead of %v", hdrSize, len(header))
|
||||||
|
}
|
||||||
|
if len(decoded) != len(expected) {
|
||||||
|
return fmt.Errorf("pack header size mismatch")
|
||||||
|
}
|
||||||
|
for i := 0; i < len(decoded); i++ {
|
||||||
|
if decoded[i] != expected[i] {
|
||||||
|
return fmt.Errorf("pack header entry mismatch got %v instead of %v", decoded[i], expected[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p.bytes += uint(hdrBytes + binary.Size(uint32(0)))
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,10 +132,10 @@ func (p *Packer) HeaderOverhead() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// makeHeader constructs the header for p.
|
// makeHeader constructs the header for p.
|
||||||
func (p *Packer) makeHeader() ([]byte, error) {
|
func makeHeader(blobs []restic.Blob) ([]byte, error) {
|
||||||
buf := make([]byte, 0, len(p.blobs)*int(entrySize))
|
buf := make([]byte, 0, len(blobs)*int(entrySize))
|
||||||
|
|
||||||
for _, b := range p.blobs {
|
for _, b := range blobs {
|
||||||
switch {
|
switch {
|
||||||
case b.Type == restic.DataBlob && b.UncompressedLength == 0:
|
case b.Type == restic.DataBlob && b.UncompressedLength == 0:
|
||||||
buf = append(buf, 0)
|
buf = append(buf, 0)
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
@ -177,3 +178,60 @@ func TestReadRecords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnpackedVerification(t *testing.T) {
|
||||||
|
// create random keys
|
||||||
|
k := crypto.NewRandomKey()
|
||||||
|
blobs := []restic.Blob{
|
||||||
|
{
|
||||||
|
BlobHandle: restic.NewRandomBlobHandle(),
|
||||||
|
Length: 42,
|
||||||
|
Offset: 0,
|
||||||
|
UncompressedLength: 2 * 42,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type DamageType string
|
||||||
|
const (
|
||||||
|
damageData DamageType = "data"
|
||||||
|
damageCiphertext DamageType = "ciphertext"
|
||||||
|
damageLength DamageType = "length"
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
damage DamageType
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{damageData, "pack header entry mismatch"},
|
||||||
|
{damageCiphertext, "ciphertext verification failed"},
|
||||||
|
{damageLength, "header decoding failed"},
|
||||||
|
} {
|
||||||
|
header, err := makeHeader(blobs)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
if test.damage == damageData {
|
||||||
|
header[8] ^= 0x42
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedHeader := make([]byte, 0, crypto.CiphertextLength(len(header)))
|
||||||
|
nonce := crypto.NewRandomNonce()
|
||||||
|
encryptedHeader = append(encryptedHeader, nonce...)
|
||||||
|
encryptedHeader = k.Seal(encryptedHeader, nonce, header, nil)
|
||||||
|
encryptedHeader = binary.LittleEndian.AppendUint32(encryptedHeader, uint32(len(encryptedHeader)))
|
||||||
|
|
||||||
|
if test.damage == damageCiphertext {
|
||||||
|
encryptedHeader[8] ^= 0x42
|
||||||
|
}
|
||||||
|
if test.damage == damageLength {
|
||||||
|
encryptedHeader[len(encryptedHeader)-1] ^= 0x42
|
||||||
|
}
|
||||||
|
|
||||||
|
err = verifyHeader(k, encryptedHeader, blobs)
|
||||||
|
if test.msg == "" {
|
||||||
|
rtest.Assert(t, err == nil, "expected no error, got %v", err)
|
||||||
|
} else {
|
||||||
|
rtest.Assert(t, strings.Contains(err.Error(), test.msg), "expected error to contain %q, got %q", test.msg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user