github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/bitrot.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/hex"
    23  	"errors"
    24  	"fmt"
    25  	"hash"
    26  	"io"
    27  
    28  	"github.com/minio/highwayhash"
    29  	"github.com/minio/minio/internal/hash/sha256"
    30  	"golang.org/x/crypto/blake2b"
    31  
    32  	xioutil "github.com/minio/minio/internal/ioutil"
    33  	"github.com/minio/minio/internal/logger"
    34  )
    35  
    36  // magic HH-256 key as HH-256 hash of the first 100 decimals of π as utf-8 string with a zero key.
    37  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")
    38  
    39  var bitrotAlgorithms = map[BitrotAlgorithm]string{
    40  	SHA256:          "sha256",
    41  	BLAKE2b512:      "blake2b",
    42  	HighwayHash256:  "highwayhash256",
    43  	HighwayHash256S: "highwayhash256S",
    44  }
    45  
    46  // New returns a new hash.Hash calculating the given bitrot algorithm.
    47  func (a BitrotAlgorithm) New() hash.Hash {
    48  	switch a {
    49  	case SHA256:
    50  		return sha256.New()
    51  	case BLAKE2b512:
    52  		b2, _ := blake2b.New512(nil) // New512 never returns an error if the key is nil
    53  		return b2
    54  	case HighwayHash256:
    55  		hh, _ := highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit
    56  		return hh
    57  	case HighwayHash256S:
    58  		hh, _ := highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit
    59  		return hh
    60  	default:
    61  		logger.CriticalIf(GlobalContext, errors.New("Unsupported bitrot algorithm"))
    62  		return nil
    63  	}
    64  }
    65  
    66  // Available reports whether the given algorithm is available.
    67  func (a BitrotAlgorithm) Available() bool {
    68  	_, ok := bitrotAlgorithms[a]
    69  	return ok
    70  }
    71  
    72  // String returns the string identifier for a given bitrot algorithm.
    73  // If the algorithm is not supported String panics.
    74  func (a BitrotAlgorithm) String() string {
    75  	name, ok := bitrotAlgorithms[a]
    76  	if !ok {
    77  		logger.CriticalIf(GlobalContext, errors.New("Unsupported bitrot algorithm"))
    78  	}
    79  	return name
    80  }
    81  
    82  // NewBitrotVerifier returns a new BitrotVerifier implementing the given algorithm.
    83  func NewBitrotVerifier(algorithm BitrotAlgorithm, checksum []byte) *BitrotVerifier {
    84  	return &BitrotVerifier{algorithm, checksum}
    85  }
    86  
    87  // BitrotVerifier can be used to verify protected data.
    88  type BitrotVerifier struct {
    89  	algorithm BitrotAlgorithm
    90  	sum       []byte
    91  }
    92  
    93  // BitrotAlgorithmFromString returns a bitrot algorithm from the given string representation.
    94  // It returns 0 if the string representation does not match any supported algorithm.
    95  // The zero value of a bitrot algorithm is never supported.
    96  func BitrotAlgorithmFromString(s string) (a BitrotAlgorithm) {
    97  	for alg, name := range bitrotAlgorithms {
    98  		if name == s {
    99  			return alg
   100  		}
   101  	}
   102  	return
   103  }
   104  
   105  func newBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
   106  	if algo == HighwayHash256S {
   107  		return newStreamingBitrotWriter(disk, origvolume, volume, filePath, length, algo, shardSize)
   108  	}
   109  	return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize)
   110  }
   111  
   112  func newBitrotReader(disk StorageAPI, data []byte, bucket string, filePath string, tillOffset int64, algo BitrotAlgorithm, sum []byte, shardSize int64) io.ReaderAt {
   113  	if algo == HighwayHash256S {
   114  		return newStreamingBitrotReader(disk, data, bucket, filePath, tillOffset, algo, shardSize)
   115  	}
   116  	return newWholeBitrotReader(disk, bucket, filePath, algo, tillOffset, sum)
   117  }
   118  
   119  // Close all the readers.
   120  func closeBitrotReaders(rs []io.ReaderAt) {
   121  	for _, r := range rs {
   122  		if r != nil {
   123  			if br, ok := r.(io.Closer); ok {
   124  				br.Close()
   125  			}
   126  		}
   127  	}
   128  }
   129  
   130  // Close all the writers.
   131  func closeBitrotWriters(ws []io.Writer) {
   132  	for _, w := range ws {
   133  		if w != nil {
   134  			if bw, ok := w.(io.Closer); ok {
   135  				bw.Close()
   136  			}
   137  		}
   138  	}
   139  }
   140  
   141  // Returns hash sum for whole-bitrot, nil for streaming-bitrot.
   142  func bitrotWriterSum(w io.Writer) []byte {
   143  	if bw, ok := w.(*wholeBitrotWriter); ok {
   144  		return bw.Sum(nil)
   145  	}
   146  	return nil
   147  }
   148  
   149  // Returns the size of the file with bitrot protection
   150  func bitrotShardFileSize(size int64, shardSize int64, algo BitrotAlgorithm) int64 {
   151  	if algo != HighwayHash256S {
   152  		return size
   153  	}
   154  	return ceilFrac(size, shardSize)*int64(algo.New().Size()) + size
   155  }
   156  
   157  // bitrotVerify a single stream of data.
   158  func bitrotVerify(r io.Reader, wantSize, partSize int64, algo BitrotAlgorithm, want []byte, shardSize int64) error {
   159  	if algo != HighwayHash256S {
   160  		h := algo.New()
   161  		if n, err := io.Copy(h, r); err != nil || n != wantSize {
   162  			// Premature failure in reading the object, file is corrupt.
   163  			return errFileCorrupt
   164  		}
   165  		if !bytes.Equal(h.Sum(nil), want) {
   166  			return errFileCorrupt
   167  		}
   168  		return nil
   169  	}
   170  
   171  	h := algo.New()
   172  	hashBuf := make([]byte, h.Size())
   173  	left := wantSize
   174  
   175  	// Calculate the size of the bitrot file and compare
   176  	// it with the actual file size.
   177  	if left != bitrotShardFileSize(partSize, shardSize, algo) {
   178  		return errFileCorrupt
   179  	}
   180  
   181  	bufp := xioutil.ODirectPoolSmall.Get().(*[]byte)
   182  	defer xioutil.ODirectPoolSmall.Put(bufp)
   183  
   184  	for left > 0 {
   185  		// Read expected hash...
   186  		h.Reset()
   187  		n, err := io.ReadFull(r, hashBuf)
   188  		if err != nil {
   189  			// Read's failed for object with right size, file is corrupt.
   190  			return err
   191  		}
   192  		// Subtract hash length..
   193  		left -= int64(n)
   194  		if left < shardSize {
   195  			shardSize = left
   196  		}
   197  
   198  		read, err := io.CopyBuffer(h, io.LimitReader(r, shardSize), *bufp)
   199  		if err != nil {
   200  			// Read's failed for object with right size, at different offsets.
   201  			return errFileCorrupt
   202  		}
   203  
   204  		left -= read
   205  		if !bytes.Equal(h.Sum(nil), hashBuf[:n]) {
   206  			return errFileCorrupt
   207  		}
   208  	}
   209  	return nil
   210  }
   211  
   212  // bitrotSelfTest performs a self-test to ensure that bitrot
   213  // algorithms compute correct checksums. If any algorithm
   214  // produces an incorrect checksum it fails with a hard error.
   215  //
   216  // bitrotSelfTest tries to catch any issue in the bitrot implementation
   217  // early instead of silently corrupting data.
   218  func bitrotSelfTest() {
   219  	checksums := map[BitrotAlgorithm]string{
   220  		SHA256:          "a7677ff19e0182e4d52e3a3db727804abc82a5818749336369552e54b838b004",
   221  		BLAKE2b512:      "e519b7d84b1c3c917985f544773a35cf265dcab10948be3550320d156bab612124a5ae2ae5a8c73c0eea360f68b0e28136f26e858756dbfe7375a7389f26c669",
   222  		HighwayHash256:  "39c0407ed3f01b18d22c85db4aeff11e060ca5f43131b0126731ca197cd42313",
   223  		HighwayHash256S: "39c0407ed3f01b18d22c85db4aeff11e060ca5f43131b0126731ca197cd42313",
   224  	}
   225  	for algorithm := range bitrotAlgorithms {
   226  		if !algorithm.Available() {
   227  			continue
   228  		}
   229  
   230  		checksum, err := hex.DecodeString(checksums[algorithm])
   231  		if err != nil {
   232  			logger.Fatal(errSelfTestFailure, fmt.Sprintf("bitrot: failed to decode %v checksum %s for selftest: %v", algorithm, checksums[algorithm], err))
   233  		}
   234  		var (
   235  			hash = algorithm.New()
   236  			msg  = make([]byte, 0, hash.Size()*hash.BlockSize())
   237  			sum  = make([]byte, 0, hash.Size())
   238  		)
   239  		for i := 0; i < hash.Size()*hash.BlockSize(); i += hash.Size() {
   240  			hash.Write(msg)
   241  			sum = hash.Sum(sum[:0])
   242  			msg = append(msg, sum...)
   243  			hash.Reset()
   244  		}
   245  		if !bytes.Equal(sum, checksum) {
   246  			logger.Fatal(errSelfTestFailure, fmt.Sprintf("bitrot: %v selftest checksum mismatch: got %x - want %x", algorithm, sum, checksum))
   247  		}
   248  	}
   249  }