2
2
mirror of https://github.com/octoleo/restic.git synced 2024-05-28 14:40:49 +00:00

debug: store repaired and correct blobs

This commit is contained in:
Michael Eischer 2020-10-09 14:33:07 +02:00
parent b3c3121622
commit 84491ff40b

View File

@ -50,11 +50,13 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
var tryRepair bool var tryRepair bool
var repairByte bool var repairByte bool
var extractPack bool
func init() { func init() {
cmdRoot.AddCommand(cmdDebug) cmdRoot.AddCommand(cmdDebug)
cmdDebug.AddCommand(cmdDebugDump) cmdDebug.AddCommand(cmdDebugDump)
cmdDebug.AddCommand(cmdDebugExamine) cmdDebug.AddCommand(cmdDebugExamine)
cmdDebugExamine.Flags().BoolVar(&extractPack, "extract-pack", false, "write blobs to the current directory")
cmdDebugExamine.Flags().BoolVar(&tryRepair, "try-repair", false, "try to repair broken blobs with single bit flips") cmdDebugExamine.Flags().BoolVar(&tryRepair, "try-repair", false, "try to repair broken blobs with single bit flips")
cmdDebugExamine.Flags().BoolVar(&repairByte, "repair-byte", false, "try to repair broken blobs by trying bytes") cmdDebugExamine.Flags().BoolVar(&repairByte, "repair-byte", false, "try to repair broken blobs by trying bytes")
} }
@ -190,7 +192,7 @@ var cmdDebugExamine = &cobra.Command{
}, },
} }
func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, bytewise bool) { func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, bytewise bool) []byte {
if bytewise { if bytewise {
fmt.Printf(" trying to repair blob by finding a broken byte\n") fmt.Printf(" trying to repair blob by finding a broken byte\n")
} else { } else {
@ -200,9 +202,12 @@ func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, by
ch := make(chan int) ch := make(chan int)
var wg errgroup.Group var wg errgroup.Group
done := make(chan struct{}) done := make(chan struct{})
var fixed []byte
var found bool
fmt.Printf(" spinning up %d worker functions\n", runtime.NumCPU()) workers := runtime.GOMAXPROCS(0)
for i := 0; i < runtime.NumCPU(); i++ { fmt.Printf(" spinning up %d worker functions\n", runtime.GOMAXPROCS(0))
for i := 0; i < workers; i++ {
wg.Go(func() error { wg.Go(func() error {
// make a local copy of the buffer // make a local copy of the buffer
buf := make([]byte, len(input)) buf := make([]byte, len(input))
@ -210,9 +215,10 @@ func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, by
for { for {
select { select {
case <-done: case i, ok := <-ch:
return nil if !ok {
case i := <-ch: return nil
}
if bytewise { if bytewise {
for j := 0; j < 255; j++ { for j := 0; j < 255; j++ {
// flip bits // flip bits
@ -225,6 +231,8 @@ func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, by
fmt.Printf(" blob could be repaired by XORing byte %v with 0x%02x\n", i, j) fmt.Printf(" blob could be repaired by XORing byte %v with 0x%02x\n", i, j)
fmt.Printf(" hash is %v\n", restic.Hash(plaintext)) fmt.Printf(" hash is %v\n", restic.Hash(plaintext))
close(done) close(done)
found = true
fixed = plaintext
return nil return nil
} }
@ -243,6 +251,8 @@ func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, by
fmt.Printf(" blob could be repaired by flipping bit %v in byte %v\n", j, i) fmt.Printf(" blob could be repaired by flipping bit %v in byte %v\n", j, i)
fmt.Printf(" hash is %v\n", restic.Hash(plaintext)) fmt.Printf(" hash is %v\n", restic.Hash(plaintext))
close(done) close(done)
found = true
fixed = plaintext
return nil return nil
} }
@ -278,40 +288,20 @@ outer:
info = time.Now() info = time.Now()
} }
} }
close(ch)
var found bool
select {
case <-done:
found = true
default:
close(done)
}
wg.Wait() wg.Wait()
if !found { if !found {
fmt.Printf("\n blob could not be repaired by single bit flip\n") fmt.Printf("\n blob could not be repaired by single bit flip\n")
} }
} return fixed
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
} }
func decryptUnsigned(ctx context.Context, k *crypto.Key, buf []byte) []byte { func decryptUnsigned(ctx context.Context, k *crypto.Key, buf []byte) []byte {
// strip signature at the end // strip signature at the end
l := len(buf) l := len(buf)
nonce, ct := buf[:16], buf[16:l-16] nonce, ct := buf[:16], buf[16:l-16]
dst := make([]byte, 0, len(ct)) out := make([]byte, len(ct))
ret, out := sliceForAppend(dst, len(ct))
c, err := aes.NewCipher(k.EncryptionKey[:]) c, err := aes.NewCipher(k.EncryptionKey[:])
if err != nil { if err != nil {
@ -320,7 +310,7 @@ func decryptUnsigned(ctx context.Context, k *crypto.Key, buf []byte) []byte {
e := cipher.NewCTR(c, nonce) e := cipher.NewCTR(c, nonce)
e.XORKeyStream(out, ct) e.XORKeyStream(out, ct)
return ret return out
} }
func loadBlobs(ctx context.Context, repo restic.Repository, pack string, list []restic.Blob) error { func loadBlobs(ctx context.Context, repo restic.Repository, pack string, list []restic.Blob) error {
@ -351,42 +341,72 @@ func loadBlobs(ctx context.Context, repo restic.Repository, pack string, list []
plaintext, err = key.Open(plaintext[:0], nonce, plaintext, nil) plaintext, err = key.Open(plaintext[:0], nonce, plaintext, nil)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "error decrypting blob: %v\n", err) fmt.Fprintf(os.Stderr, "error decrypting blob: %v\n", err)
var plain []byte
if tryRepair || repairByte { if tryRepair || repairByte {
tryRepairWithBitflip(ctx, key, buf, repairByte) plain = tryRepairWithBitflip(ctx, key, buf, repairByte)
} }
plain := decryptUnsigned(ctx, key, buf) var prefix string
filename := fmt.Sprintf("%s.bin", blob.ID.String()) if plain != nil {
f, err := os.Create(filename) id := restic.Hash(plain)
if !id.Equal(blob.ID) {
fmt.Printf(" successfully repaired blob (length %v), hash is %v, ID does not match, wanted %v\n", len(plain), id, blob.ID)
prefix = "repaired-wrong-hash-"
} else {
prefix = "repaired-"
}
} else {
plain = decryptUnsigned(ctx, key, buf)
prefix = "damaged-"
}
err = storePlainBlob(blob.ID, prefix, plain)
if err != nil { if err != nil {
return err return err
} }
_, err = f.Write(plain)
if err != nil {
_ = f.Close()
return err
}
err = f.Close()
if err != nil {
return err
}
fmt.Printf("decrypt of blob %v stored at %v\n", blob.ID.Str(), filename)
continue continue
} }
id := restic.Hash(plaintext) id := restic.Hash(plaintext)
var prefix string
if !id.Equal(blob.ID) { if !id.Equal(blob.ID) {
fmt.Printf(" successfully decrypted blob (length %v), hash is %v, ID does not match, wanted %v\n", len(plaintext), id, blob.ID) fmt.Printf(" successfully decrypted blob (length %v), hash is %v, ID does not match, wanted %v\n", len(plaintext), id, blob.ID)
prefix = "wrong-hash-"
} else { } else {
fmt.Printf(" successfully decrypted blob (length %v), hash is %v, ID matches\n", len(plaintext), id) fmt.Printf(" successfully decrypted blob (length %v), hash is %v, ID matches\n", len(plaintext), id)
prefix = "correct-"
}
if extractPack {
err = storePlainBlob(id, prefix, plaintext)
if err != nil {
return err
}
} }
} }
return nil return nil
} }
func storePlainBlob(id restic.ID, prefix string, plain []byte) error {
filename := fmt.Sprintf("%s%s.bin", prefix, id)
f, err := os.Create(filename)
if err != nil {
return err
}
_, err = f.Write(plain)
if err != nil {
_ = f.Close()
return err
}
err = f.Close()
if err != nil {
return err
}
fmt.Printf("decrypt of blob %v stored at %v\n", id, filename)
return nil
}
func runDebugExamine(gopts GlobalOptions, args []string) error { func runDebugExamine(gopts GlobalOptions, args []string) error {
repo, err := OpenRepository(gopts) repo, err := OpenRepository(gopts)
if err != nil { if err != nil {