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 }