| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | // Copyright (c) 2015-2021 MinIO, Inc.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-03-30 08:00:55 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2021-02-25 01:00:15 +08:00
										 |  |  | 	"crypto/sha256" | 
					
						
							| 
									
										
										
										
											2021-04-06 23:38:22 +08:00
										 |  |  | 	"encoding/hex" | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-04-06 23:38:22 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	"hash" | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/highwayhash" | 
					
						
							|  |  |  | 	"golang.org/x/crypto/blake2b" | 
					
						
							| 
									
										
										
										
											2021-09-30 07:40:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	xioutil "github.com/minio/minio/internal/ioutil" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/logger" | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // magic HH-256 key as HH-256 hash of the first 100 decimals of π as utf-8 string with a zero key.
 | 
					
						
							|  |  |  | var magicHighwayHash256Key = []byte("\x4b\xe7\x34\xfa\x8e\x23\x8a\xcd\x26\x3e\x83\xe6\xbb\x96\x85\x52\x04\x0f\x93\x5d\xa3\x9f\x44\x14\x97\xe0\x9d\x13\x22\xde\x36\xa0") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var bitrotAlgorithms = map[BitrotAlgorithm]string{ | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	SHA256:          "sha256", | 
					
						
							|  |  |  | 	BLAKE2b512:      "blake2b", | 
					
						
							|  |  |  | 	HighwayHash256:  "highwayhash256", | 
					
						
							|  |  |  | 	HighwayHash256S: "highwayhash256S", | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // New returns a new hash.Hash calculating the given bitrot algorithm.
 | 
					
						
							|  |  |  | func (a BitrotAlgorithm) New() hash.Hash { | 
					
						
							|  |  |  | 	switch a { | 
					
						
							|  |  |  | 	case SHA256: | 
					
						
							|  |  |  | 		return sha256.New() | 
					
						
							|  |  |  | 	case BLAKE2b512: | 
					
						
							|  |  |  | 		b2, _ := blake2b.New512(nil) // New512 never returns an error if the key is nil
 | 
					
						
							|  |  |  | 		return b2 | 
					
						
							|  |  |  | 	case HighwayHash256: | 
					
						
							|  |  |  | 		hh, _ := highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit
 | 
					
						
							|  |  |  | 		return hh | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	case HighwayHash256S: | 
					
						
							|  |  |  | 		hh, _ := highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit
 | 
					
						
							|  |  |  | 		return hh | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2020-04-10 00:30:02 +08:00
										 |  |  | 		logger.CriticalIf(GlobalContext, errors.New("Unsupported bitrot algorithm")) | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-24 14:35:37 +08:00
										 |  |  | // Available reports whether the given algorithm is available.
 | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | func (a BitrotAlgorithm) Available() bool { | 
					
						
							|  |  |  | 	_, ok := bitrotAlgorithms[a] | 
					
						
							|  |  |  | 	return ok | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // String returns the string identifier for a given bitrot algorithm.
 | 
					
						
							|  |  |  | // If the algorithm is not supported String panics.
 | 
					
						
							|  |  |  | func (a BitrotAlgorithm) String() string { | 
					
						
							|  |  |  | 	name, ok := bitrotAlgorithms[a] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2020-04-10 00:30:02 +08:00
										 |  |  | 		logger.CriticalIf(GlobalContext, errors.New("Unsupported bitrot algorithm")) | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return name | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewBitrotVerifier returns a new BitrotVerifier implementing the given algorithm.
 | 
					
						
							|  |  |  | func NewBitrotVerifier(algorithm BitrotAlgorithm, checksum []byte) *BitrotVerifier { | 
					
						
							|  |  |  | 	return &BitrotVerifier{algorithm, checksum} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // BitrotVerifier can be used to verify protected data.
 | 
					
						
							|  |  |  | type BitrotVerifier struct { | 
					
						
							|  |  |  | 	algorithm BitrotAlgorithm | 
					
						
							|  |  |  | 	sum       []byte | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // BitrotAlgorithmFromString returns a bitrot algorithm from the given string representation.
 | 
					
						
							|  |  |  | // It returns 0 if the string representation does not match any supported algorithm.
 | 
					
						
							|  |  |  | // The zero value of a bitrot algorithm is never supported.
 | 
					
						
							|  |  |  | func BitrotAlgorithmFromString(s string) (a BitrotAlgorithm) { | 
					
						
							|  |  |  | 	for alg, name := range bitrotAlgorithms { | 
					
						
							|  |  |  | 		if name == s { | 
					
						
							|  |  |  | 			return alg | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-17 23:32:28 +08:00
										 |  |  | func newBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer { | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	if algo == HighwayHash256S { | 
					
						
							| 
									
										
										
										
											2021-05-17 23:32:28 +08:00
										 |  |  | 		return newStreamingBitrotWriter(disk, volume, filePath, length, algo, shardSize) | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-05-01 07:27:31 +08:00
										 |  |  | 	return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize) | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | func newBitrotReader(disk StorageAPI, data []byte, bucket string, filePath string, tillOffset int64, algo BitrotAlgorithm, sum []byte, shardSize int64) io.ReaderAt { | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	if algo == HighwayHash256S { | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | 		return newStreamingBitrotReader(disk, data, bucket, filePath, tillOffset, algo, shardSize) | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	return newWholeBitrotReader(disk, bucket, filePath, algo, tillOffset, sum) | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | // Close all the readers.
 | 
					
						
							|  |  |  | func closeBitrotReaders(rs []io.ReaderAt) { | 
					
						
							|  |  |  | 	for _, r := range rs { | 
					
						
							| 
									
										
										
										
											2022-01-13 10:49:01 +08:00
										 |  |  | 		if r != nil { | 
					
						
							|  |  |  | 			if br, ok := r.(io.Closer); ok { | 
					
						
							|  |  |  | 				br.Close() | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | // Close all the writers.
 | 
					
						
							|  |  |  | func closeBitrotWriters(ws []io.Writer) { | 
					
						
							|  |  |  | 	for _, w := range ws { | 
					
						
							| 
									
										
										
										
											2022-01-13 10:49:01 +08:00
										 |  |  | 		if w != nil { | 
					
						
							|  |  |  | 			if bw, ok := w.(io.Closer); ok { | 
					
						
							|  |  |  | 				bw.Close() | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | // Returns hash sum for whole-bitrot, nil for streaming-bitrot.
 | 
					
						
							|  |  |  | func bitrotWriterSum(w io.Writer) []byte { | 
					
						
							|  |  |  | 	if bw, ok := w.(*wholeBitrotWriter); ok { | 
					
						
							|  |  |  | 		return bw.Sum(nil) | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-09-12 04:49:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Returns the size of the file with bitrot protection
 | 
					
						
							|  |  |  | func bitrotShardFileSize(size int64, shardSize int64, algo BitrotAlgorithm) int64 { | 
					
						
							|  |  |  | 	if algo != HighwayHash256S { | 
					
						
							|  |  |  | 		return size | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ceilFrac(size, shardSize)*int64(algo.New().Size()) + size | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-03-30 08:00:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // bitrotVerify a single stream of data.
 | 
					
						
							|  |  |  | func bitrotVerify(r io.Reader, wantSize, partSize int64, algo BitrotAlgorithm, want []byte, shardSize int64) error { | 
					
						
							|  |  |  | 	if algo != HighwayHash256S { | 
					
						
							|  |  |  | 		h := algo.New() | 
					
						
							|  |  |  | 		if n, err := io.Copy(h, r); err != nil || n != wantSize { | 
					
						
							|  |  |  | 			// Premature failure in reading the object, file is corrupt.
 | 
					
						
							|  |  |  | 			return errFileCorrupt | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !bytes.Equal(h.Sum(nil), want) { | 
					
						
							|  |  |  | 			return errFileCorrupt | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	h := algo.New() | 
					
						
							|  |  |  | 	hashBuf := make([]byte, h.Size()) | 
					
						
							|  |  |  | 	left := wantSize | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Calculate the size of the bitrot file and compare
 | 
					
						
							|  |  |  | 	// it with the actual file size.
 | 
					
						
							|  |  |  | 	if left != bitrotShardFileSize(partSize, shardSize, algo) { | 
					
						
							|  |  |  | 		return errFileCorrupt | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-30 07:40:28 +08:00
										 |  |  | 	bufp := xioutil.ODirectPoolSmall.Get().(*[]byte) | 
					
						
							|  |  |  | 	defer xioutil.ODirectPoolSmall.Put(bufp) | 
					
						
							| 
									
										
										
										
											2021-05-18 08:49:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-30 08:00:55 +08:00
										 |  |  | 	for left > 0 { | 
					
						
							|  |  |  | 		// Read expected hash...
 | 
					
						
							|  |  |  | 		h.Reset() | 
					
						
							|  |  |  | 		n, err := io.ReadFull(r, hashBuf) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// Read's failed for object with right size, file is corrupt.
 | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Subtract hash length..
 | 
					
						
							|  |  |  | 		left -= int64(n) | 
					
						
							|  |  |  | 		if left < shardSize { | 
					
						
							|  |  |  | 			shardSize = left | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-18 08:49:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		read, err := io.CopyBuffer(h, io.LimitReader(r, shardSize), *bufp) | 
					
						
							| 
									
										
										
										
											2021-03-30 08:00:55 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// Read's failed for object with right size, at different offsets.
 | 
					
						
							| 
									
										
										
										
											2021-05-18 08:49:48 +08:00
										 |  |  | 			return errFileCorrupt | 
					
						
							| 
									
										
										
										
											2021-03-30 08:00:55 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-18 08:49:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-30 08:00:55 +08:00
										 |  |  | 		left -= read | 
					
						
							| 
									
										
										
										
											2021-05-18 08:49:48 +08:00
										 |  |  | 		if !bytes.Equal(h.Sum(nil), hashBuf[:n]) { | 
					
						
							| 
									
										
										
										
											2021-03-30 08:00:55 +08:00
										 |  |  | 			return errFileCorrupt | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-04-06 23:38:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // bitrotSelfTest performs a self-test to ensure that bitrot
 | 
					
						
							|  |  |  | // algorithms compute correct checksums. If any algorithm
 | 
					
						
							|  |  |  | // produces an incorrect checksum it fails with a hard error.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // bitrotSelfTest tries to catch any issue in the bitrot implementation
 | 
					
						
							|  |  |  | // early instead of silently corrupting data.
 | 
					
						
							|  |  |  | func bitrotSelfTest() { | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	checksums := map[BitrotAlgorithm]string{ | 
					
						
							| 
									
										
										
										
											2021-04-06 23:38:22 +08:00
										 |  |  | 		SHA256:          "a7677ff19e0182e4d52e3a3db727804abc82a5818749336369552e54b838b004", | 
					
						
							|  |  |  | 		BLAKE2b512:      "e519b7d84b1c3c917985f544773a35cf265dcab10948be3550320d156bab612124a5ae2ae5a8c73c0eea360f68b0e28136f26e858756dbfe7375a7389f26c669", | 
					
						
							|  |  |  | 		HighwayHash256:  "39c0407ed3f01b18d22c85db4aeff11e060ca5f43131b0126731ca197cd42313", | 
					
						
							|  |  |  | 		HighwayHash256S: "39c0407ed3f01b18d22c85db4aeff11e060ca5f43131b0126731ca197cd42313", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for algorithm := range bitrotAlgorithms { | 
					
						
							|  |  |  | 		if !algorithm.Available() { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		checksum, err := hex.DecodeString(checksums[algorithm]) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			logger.Fatal(errSelfTestFailure, fmt.Sprintf("bitrot: failed to decode %v checksum %s for selftest: %v", algorithm, checksums[algorithm], err)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		var ( | 
					
						
							|  |  |  | 			hash = algorithm.New() | 
					
						
							|  |  |  | 			msg  = make([]byte, 0, hash.Size()*hash.BlockSize()) | 
					
						
							|  |  |  | 			sum  = make([]byte, 0, hash.Size()) | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		for i := 0; i < hash.Size()*hash.BlockSize(); i += hash.Size() { | 
					
						
							|  |  |  | 			hash.Write(msg) | 
					
						
							|  |  |  | 			sum = hash.Sum(sum[:0]) | 
					
						
							|  |  |  | 			msg = append(msg, sum...) | 
					
						
							|  |  |  | 			hash.Reset() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !bytes.Equal(sum, checksum) { | 
					
						
							|  |  |  | 			logger.Fatal(errSelfTestFailure, fmt.Sprintf("bitrot: %v selftest checksum mismatch: got %x - want %x", algorithm, sum, checksum)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |