github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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/nicocha30/gvisor-ligolo/pkg/compressio" 60 "github.com/nicocha30/gvisor-ligolo/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 // WriteCloser is an io.Closer and wire.Writer. 88 type WriteCloser interface { 89 wire.Writer 90 io.Closer 91 } 92 93 func writeMetadataLen(w io.Writer, val uint64) error { 94 var buf [8]byte 95 binary.BigEndian.PutUint64(buf[:], val) 96 _, err := w.Write(buf[:]) 97 return err 98 } 99 100 // NewWriter returns a state data writer for a statefile. 101 // 102 // Note that the returned WriteCloser must be closed. 103 func NewWriter(w io.Writer, key []byte, metadata map[string]string) (WriteCloser, error) { 104 if metadata == nil { 105 metadata = make(map[string]string) 106 } 107 for k := range metadata { 108 if strings.HasPrefix(k, "_") { 109 return nil, ErrMetadataInvalid 110 } 111 } 112 113 // Create our HMAC function. 114 h := hmac.New(sha256.New, key) 115 mw := io.MultiWriter(w, h) 116 117 // First, write the header. 118 if _, err := mw.Write(magicHeader); err != nil { 119 return nil, err 120 } 121 122 // Generate a timestamp, for convenience only. 123 metadata["_timestamp"] = time.Now().UTC().String() 124 defer delete(metadata, "_timestamp") 125 126 // Write the metadata. 127 b, err := json.Marshal(metadata) 128 if err != nil { 129 return nil, err 130 } 131 132 if len(b) > maxMetadataSize { 133 return nil, ErrInvalidMetadataLength 134 } 135 136 // Metadata length. 137 if err := writeMetadataLen(mw, uint64(len(b))); err != nil { 138 return nil, err 139 } 140 // Metadata bytes; io.MultiWriter will return a short write error if 141 // any of the writers returns < n. 142 if _, err := mw.Write(b); err != nil { 143 return nil, err 144 } 145 // Write the current hash. 146 cur := h.Sum(nil) 147 for done := 0; done < len(cur); { 148 n, err := mw.Write(cur[done:]) 149 done += n 150 if err != nil { 151 return nil, err 152 } 153 } 154 155 // Wrap in compression. We always use "best speed" mode here. When using 156 // "best compression" mode, there is usually only a little gain in file 157 // size reduction, which translate to even smaller gain in restore 158 // latency reduction, while inccuring much more CPU usage at save time. 159 return compressio.NewWriter(w, key, compressionChunkSize, flate.BestSpeed) 160 } 161 162 // MetadataUnsafe reads out the metadata from a state file without verifying any 163 // HMAC. This function shouldn't be called for untrusted input files. 164 func MetadataUnsafe(r io.Reader) (map[string]string, error) { 165 return metadata(r, nil) 166 } 167 168 func readMetadataLen(r io.Reader) (uint64, error) { 169 var buf [8]byte 170 if _, err := io.ReadFull(r, buf[:]); err != nil { 171 return 0, err 172 } 173 return binary.BigEndian.Uint64(buf[:]), nil 174 } 175 176 // metadata validates the magic header and reads out the metadata from a state 177 // data stream. 178 func metadata(r io.Reader, h hash.Hash) (map[string]string, error) { 179 if h != nil { 180 r = io.TeeReader(r, h) 181 } 182 183 // Read and validate magic header. 184 b := make([]byte, len(magicHeader)) 185 if _, err := r.Read(b); err != nil { 186 return nil, err 187 } 188 if !bytes.Equal(b, magicHeader) { 189 return nil, ErrBadMagic 190 } 191 192 // Read and validate metadata. 193 b, err := func() (b []byte, err error) { 194 defer func() { 195 if r := recover(); r != nil { 196 b = nil 197 err = fmt.Errorf("%v", r) 198 } 199 }() 200 201 metadataLen, err := readMetadataLen(r) 202 if err != nil { 203 return nil, err 204 } 205 if metadataLen > maxMetadataSize { 206 return nil, ErrInvalidMetadataLength 207 } 208 b = make([]byte, int(metadataLen)) 209 if _, err := io.ReadFull(r, b); err != nil { 210 return nil, err 211 } 212 return b, nil 213 }() 214 if err != nil { 215 return nil, err 216 } 217 218 if h != nil { 219 // Check the hash prior to decoding. 220 cur := h.Sum(nil) 221 buf := make([]byte, len(cur)) 222 if _, err := io.ReadFull(r, buf); err != nil { 223 return nil, err 224 } 225 if !hmac.Equal(cur, buf) { 226 return nil, compressio.ErrHashMismatch 227 } 228 } 229 230 // Decode the metadata. 231 metadata := make(map[string]string) 232 if err := json.Unmarshal(b, &metadata); err != nil { 233 return nil, err 234 } 235 236 return metadata, nil 237 } 238 239 // NewReader returns a reader for a statefile. 240 func NewReader(r io.Reader, key []byte) (wire.Reader, map[string]string, error) { 241 // Read the metadata with the hash. 242 h := hmac.New(sha256.New, key) 243 metadata, err := metadata(r, h) 244 if err != nil { 245 return nil, nil, err 246 } 247 248 // Wrap in compression. 249 cr, err := compressio.NewReader(r, key) 250 if err != nil { 251 return nil, nil, err 252 } 253 return cr, metadata, nil 254 }