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  }