github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/pkg/state/statefile/statefile.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package statefile defines the state file data stream. 16 // 17 // This package currently does not include any details regarding the state 18 // encoding itself, only details regarding state metadata and data layout. 19 // 20 // The file format is defined as follows. 21 // 22 // /------------------------------------------------------\ 23 // | header (8-bytes) | 24 // +------------------------------------------------------+ 25 // | metadata length (8-bytes) | 26 // +------------------------------------------------------+ 27 // | metadata | 28 // +------------------------------------------------------+ 29 // | data | 30 // \------------------------------------------------------/ 31 // 32 // First, it includes a 8-byte magic header which is the following 33 // sequence of bytes [0x67, 0x56, 0x69, 0x73, 0x6f, 0x72, 0x53, 0x46] 34 // 35 // This header is followed by an 8-byte length N (big endian), and an 36 // ASCII-encoded JSON map that is exactly N bytes long. 37 // 38 // This map includes only strings for keys and strings for values. Keys in the 39 // map that begin with "_" are for internal use only. They may be read, but may 40 // not be provided by the user. In the future, this metadata may contain some 41 // information relating to the state encoding itself. 42 // 43 // After the map, the remainder of the file is the state data. 44 package statefile 45 46 import ( 47 "bytes" 48 "compress/flate" 49 "crypto/hmac" 50 "crypto/sha256" 51 "encoding/binary" 52 "encoding/json" 53 "fmt" 54 "hash" 55 "io" 56 "strings" 57 "time" 58 59 "github.com/MerlinKodo/gvisor/pkg/compressio" 60 "github.com/MerlinKodo/gvisor/pkg/state/wire" 61 ) 62 63 // keySize is the AES-256 key length. 64 const keySize = 32 65 66 // compressionChunkSize is the chunk size for compression. 67 const compressionChunkSize = 1024 * 1024 68 69 // maxMetadataSize is the size limit of metadata section. 70 const maxMetadataSize = 16 * 1024 * 1024 71 72 // magicHeader is the byte sequence beginning each file. 73 var magicHeader = []byte("\x67\x56\x69\x73\x6f\x72\x53\x46") 74 75 // ErrBadMagic is returned if the header does not match. 76 var ErrBadMagic = fmt.Errorf("bad magic header") 77 78 // ErrMetadataMissing is returned if the state file is missing mandatory metadata. 79 var ErrMetadataMissing = fmt.Errorf("missing metadata") 80 81 // ErrInvalidMetadataLength is returned if the metadata length is too large. 82 var ErrInvalidMetadataLength = fmt.Errorf("metadata length invalid, maximum size is %d", maxMetadataSize) 83 84 // ErrMetadataInvalid is returned if passed metadata is invalid. 85 var ErrMetadataInvalid = fmt.Errorf("metadata invalid, can't start with _") 86 87 // ErrInvalidFlags is returned if passed flags set is invalid. 88 var ErrInvalidFlags = fmt.Errorf("flags set is invalid") 89 90 const ( 91 compressionKey = "compression" 92 ) 93 94 // CompressionLevel is the image compression level. 95 type CompressionLevel string 96 97 const ( 98 // CompressionLevelFlateBestSpeed represents flate algorithm in best-speed mode. 99 CompressionLevelFlateBestSpeed = CompressionLevel("flate-best-speed") 100 // CompressionLevelNone represents the absence of any compression on an image. 101 CompressionLevelNone = CompressionLevel("none") 102 ) 103 104 // Options is statefile options. 105 type Options struct { 106 // Compression is an image compression type/level. 107 Compression CompressionLevel 108 } 109 110 // WriteToMetadata save options to the metadata storage. Method returns the 111 // reference to the original metadata map to allow to be used in the chain calls. 112 func (o Options) WriteToMetadata(metadata map[string]string) map[string]string { 113 metadata[compressionKey] = string(o.Compression) 114 return metadata 115 } 116 117 // CompressionLevelFromString parses a string into the CompressionLevel. 118 func CompressionLevelFromString(val string) (CompressionLevel, error) { 119 switch val { 120 case string(CompressionLevelFlateBestSpeed): 121 return CompressionLevelFlateBestSpeed, nil 122 case string(CompressionLevelNone): 123 return CompressionLevelNone, nil 124 default: 125 return CompressionLevelNone, ErrInvalidFlags 126 } 127 } 128 129 // CompressionLevelFromMetadata returns image compression type stored in the metadata. 130 // If the metadata doesn't contain compression information the default behavior 131 // is the "flate-best-speed" state because the default behavior used to be to always 132 // compress. If the parameter is missing it will be set to default. 133 func CompressionLevelFromMetadata(metadata map[string]string) (CompressionLevel, error) { 134 var err error 135 136 compression := CompressionLevelFlateBestSpeed 137 138 if val, ok := metadata[compressionKey]; ok { 139 if compression, err = CompressionLevelFromString(val); err != nil { 140 return CompressionLevelNone, err 141 } 142 } else { 143 metadata[compressionKey] = string(compression) 144 } 145 146 return compression, nil 147 } 148 149 // WriteCloser is an io.Closer and wire.Writer. 150 type WriteCloser interface { 151 wire.Writer 152 io.Closer 153 } 154 155 func writeMetadataLen(w io.Writer, val uint64) error { 156 var buf [8]byte 157 binary.BigEndian.PutUint64(buf[:], val) 158 _, err := w.Write(buf[:]) 159 return err 160 } 161 162 // NewWriter returns a state data writer for a statefile. 163 // 164 // Note that the returned WriteCloser must be closed. 165 func NewWriter(w io.Writer, key []byte, metadata map[string]string) (WriteCloser, error) { 166 if metadata == nil { 167 metadata = make(map[string]string) 168 } 169 for k := range metadata { 170 if strings.HasPrefix(k, "_") { 171 return nil, ErrMetadataInvalid 172 } 173 } 174 175 // Create our HMAC function. 176 h := hmac.New(sha256.New, key) 177 mw := io.MultiWriter(w, h) 178 179 // First, write the header. 180 if _, err := mw.Write(magicHeader); err != nil { 181 return nil, err 182 } 183 184 // Generate a timestamp, for convenience only. 185 metadata["_timestamp"] = time.Now().UTC().String() 186 defer delete(metadata, "_timestamp") 187 188 // Save compression state 189 compression, err := CompressionLevelFromMetadata(metadata) 190 if err != nil { 191 return nil, err 192 } 193 194 // Write the metadata. 195 b, err := json.Marshal(metadata) 196 if err != nil { 197 return nil, err 198 } 199 200 if len(b) > maxMetadataSize { 201 return nil, ErrInvalidMetadataLength 202 } 203 204 // Metadata length. 205 if err := writeMetadataLen(mw, uint64(len(b))); err != nil { 206 return nil, err 207 } 208 // Metadata bytes; io.MultiWriter will return a short write error if 209 // any of the writers returns < n. 210 if _, err := mw.Write(b); err != nil { 211 return nil, err 212 } 213 // Write the current hash. 214 cur := h.Sum(nil) 215 for done := 0; done < len(cur); { 216 n, err := mw.Write(cur[done:]) 217 done += n 218 if err != nil { 219 return nil, err 220 } 221 } 222 223 // Wrap in compression. When using "best compression" mode, there is usually 224 // only a little gain in file size reduction, which translate to even smaller 225 // gain in restore latency reduction, while inccuring much more CPU usage at 226 // save time. 227 if compression == CompressionLevelFlateBestSpeed { 228 return compressio.NewWriter(w, key, compressionChunkSize, flate.BestSpeed) 229 } 230 231 return compressio.NewSimpleWriter(w, key) 232 } 233 234 // MetadataUnsafe reads out the metadata from a state file without verifying any 235 // HMAC. This function shouldn't be called for untrusted input files. 236 func MetadataUnsafe(r io.Reader) (map[string]string, error) { 237 return metadata(r, nil) 238 } 239 240 func readMetadataLen(r io.Reader) (uint64, error) { 241 var buf [8]byte 242 if _, err := io.ReadFull(r, buf[:]); err != nil { 243 return 0, err 244 } 245 return binary.BigEndian.Uint64(buf[:]), nil 246 } 247 248 // metadata validates the magic header and reads out the metadata from a state 249 // data stream. 250 func metadata(r io.Reader, h hash.Hash) (map[string]string, error) { 251 if h != nil { 252 r = io.TeeReader(r, h) 253 } 254 255 // Read and validate magic header. 256 b := make([]byte, len(magicHeader)) 257 if _, err := r.Read(b); err != nil { 258 return nil, err 259 } 260 if !bytes.Equal(b, magicHeader) { 261 return nil, ErrBadMagic 262 } 263 264 // Read and validate metadata. 265 b, err := func() (b []byte, err error) { 266 defer func() { 267 if r := recover(); r != nil { 268 b = nil 269 err = fmt.Errorf("%v", r) 270 } 271 }() 272 273 metadataLen, err := readMetadataLen(r) 274 if err != nil { 275 return nil, err 276 } 277 if metadataLen > maxMetadataSize { 278 return nil, ErrInvalidMetadataLength 279 } 280 b = make([]byte, int(metadataLen)) 281 if _, err := io.ReadFull(r, b); err != nil { 282 return nil, err 283 } 284 return b, nil 285 }() 286 if err != nil { 287 return nil, err 288 } 289 290 if h != nil { 291 // Check the hash prior to decoding. 292 cur := h.Sum(nil) 293 buf := make([]byte, len(cur)) 294 if _, err := io.ReadFull(r, buf); err != nil { 295 return nil, err 296 } 297 if !hmac.Equal(cur, buf) { 298 return nil, compressio.ErrHashMismatch 299 } 300 } 301 302 // Decode the metadata. 303 metadata := make(map[string]string) 304 if err := json.Unmarshal(b, &metadata); err != nil { 305 return nil, err 306 } 307 308 return metadata, nil 309 } 310 311 // NewReader returns a reader for a statefile. 312 func NewReader(r io.Reader, key []byte) (wire.Reader, map[string]string, error) { 313 // Read the metadata with the hash. 314 h := hmac.New(sha256.New, key) 315 metadata, err := metadata(r, h) 316 if err != nil { 317 return nil, nil, err 318 } 319 320 // Determine image compression state. If the metadata doesn't contain 321 // compression information the default behavior is the "compressed" state 322 // because the default behavior used to be to always compress. 323 compression, err := CompressionLevelFromMetadata(metadata) 324 if err != nil { 325 return nil, nil, err 326 } 327 328 // Pick correct reader 329 var cr wire.Reader 330 331 if compression == CompressionLevelFlateBestSpeed { 332 cr, err = compressio.NewReader(r, key) 333 } else if compression == CompressionLevelNone { 334 cr, err = compressio.NewSimpleReader(r, key) 335 } else { 336 // Should never occur, as it has the default path. 337 return nil, nil, fmt.Errorf("metadata contains invalid compression flag value: %v", compression) 338 } 339 340 if err != nil { 341 return nil, nil, err 342 } 343 344 return cr, metadata, nil 345 }