diff --git a/mfer/constants.go b/mfer/constants.go index e8c73b6..9812e7f 100644 --- a/mfer/constants.go +++ b/mfer/constants.go @@ -17,4 +17,17 @@ const ( // uuidLength is the length in bytes of a binary UUID. uuidLength = 16 + + // filesFieldNumber and hashesFieldNumber are the numbers of + // MFFile.files and MFFilePath.hashes in mf.proto. + filesFieldNumber = 101 + hashesFieldNumber = 3 + + // minHashSize is the encoded size of the smallest hash: a two-byte + // multihash (algorithm code, zero digest length) after its tag and length. + minHashSize = 2 + 2 + + // minFileEntrySize is the encoded size of the smallest file entry: a + // one-byte path and one hash, each after its tag and length. + minFileEntrySize = 2 + 1 + 2 + minHashSize ) diff --git a/mfer/deserialize.go b/mfer/deserialize.go index 1fd842f..9b11774 100644 --- a/mfer/deserialize.go +++ b/mfer/deserialize.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/klauspost/compress/zstd" "github.com/spf13/afero" + "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/proto" "sneak.berlin/go/mfer/internal/bork" "sneak.berlin/go/mfer/internal/log" @@ -27,6 +28,8 @@ var ( errUUIDMismatch = errors.New("outer and inner UUID mismatch") errInvalidFileFormat = errors.New("invalid file format") errInvalidManifestPath = errors.New("manifest contains invalid path") + errEntryTooShort = errors.New( + "manifest contains a file entry or hash shorter than the format allows") ) // validateUUID checks that the byte slice is a valid UUID (16 bytes, parseable). @@ -154,6 +157,57 @@ func (m *manifest) decompressInner() ([]byte, error) { return dat, nil } +// checkEntrySizes rejects an encoded inner message holding a file entry or +// a hash shorter than the format allows. Decoding allocates a fixed amount +// for each entry and each hash, however short, so a payload of empty ones +// would decode to about 50 times its size. +func checkEntrySizes(inner []byte) error { + return forEachBytesField(inner, filesFieldNumber, func(entry []byte) error { + if len(entry) < minFileEntrySize { + return errEntryTooShort + } + + return forEachBytesField(entry, hashesFieldNumber, func(hash []byte) error { + if len(hash) < minHashSize { + return errEntryTooShort + } + + return nil + }) + }) +} + +// forEachBytesField calls fn with the value of each length-delimited field +// numbered num in the encoded message msg, and fails if msg is malformed. +func forEachBytesField( + msg []byte, num protowire.Number, fn func(value []byte) error, +) error { + for len(msg) > 0 { + fieldNum, wireType, tagLen := protowire.ConsumeTag(msg) + if tagLen < 0 { + return protowire.ParseError(tagLen) + } + + valueLen := protowire.ConsumeFieldValue(fieldNum, wireType, msg[tagLen:]) + if valueLen < 0 { + return protowire.ParseError(valueLen) + } + + if fieldNum == num && wireType == protowire.BytesType { + value, _ := protowire.ConsumeBytes(msg[tagLen:]) + + err := fn(value) + if err != nil { + return err + } + } + + msg = msg[tagLen+valueLen:] + } + + return nil +} + func (m *manifest) deserializeInner() error { err := m.validateOuterHeader() if err != nil { @@ -177,6 +231,11 @@ func (m *manifest) deserializeInner() error { return bork.ErrFileTruncated } + err = checkEntrySizes(dat) + if err != nil { + return fmt.Errorf("deserialize: unmarshal inner: %w", err) + } + // Deserialize inner message m.pbInner = new(MFFile) diff --git a/mfer/deserialize_fuzz_test.go b/mfer/deserialize_fuzz_test.go index c1e6f43..41f4513 100644 --- a/mfer/deserialize_fuzz_test.go +++ b/mfer/deserialize_fuzz_test.go @@ -55,8 +55,10 @@ func FuzzNewManifestFromReader(f *testing.F) { // It also keeps a few copies of its input. Buffers grow by // copying, so reaching those sizes allocates a few times them in - // total: sixteen times the input and the decompressed data leaves - // room for that. + // total. Decoding the decompressed data takes up to about 25 times + // its size, when every file entry and hash is as short as the + // parser accepts. Thirty-six times the input and the decompressed + // data leaves room for both. // // The decoder also sets aside a new buffer of one to two times the // window for each frame that asks for a larger window than the @@ -71,8 +73,10 @@ func FuzzNewManifestFromReader(f *testing.F) { // fails if the decoder accepts windows of twice zstdWindowSize; the // seed whose two frames together exceed MaxDecompressedSize fails // if the decoder decodes them in full instead of stopping at the - // declared size. - limit := 16*(uint64(len(data))+decompressed) + 24*zstdWindowSize + // declared size; the seeds of empty file entries and of a file + // entry of empty hashes fail if the parser decodes entries or + // hashes shorter than the format allows. + limit := 36*(uint64(len(data))+decompressed) + 24*zstdWindowSize allocated := after.TotalAlloc - before.TotalAlloc if allocated > limit { diff --git a/mfer/deserialize_path_test.go b/mfer/deserialize_path_test.go index f2abacc..97f6b66 100644 --- a/mfer/deserialize_path_test.go +++ b/mfer/deserialize_path_test.go @@ -17,12 +17,18 @@ import ( ) // craftInnerBytes builds the wire bytes of an inner MFFile holding a single -// file entry whose path is exactly pathBytes. It writes the wire form by hand -// so a hostile path — including one that is not valid UTF-8 — can be embedded -// without proto.Marshal's own UTF-8 enforcement rejecting it first. -func craftInnerBytes(id uuid.UUID, pathBytes string) []byte { +// file entry whose path is exactly pathBytes and whose one hash is multihash. +// It writes the wire form by hand so a hostile path — including one that is +// not valid UTF-8 — can be embedded without proto.Marshal's own UTF-8 +// enforcement rejecting it first. +func craftInnerBytes(id uuid.UUID, pathBytes string, multihash []byte) []byte { + hash := protowire.AppendTag(nil, 1, protowire.BytesType) // MFFileChecksum.multiHash + hash = protowire.AppendBytes(hash, multihash) + entry := protowire.AppendTag(nil, 1, protowire.BytesType) // MFFilePath.path entry = protowire.AppendString(entry, pathBytes) + entry = protowire.AppendTag(entry, 3, protowire.BytesType) // MFFilePath.hashes + entry = protowire.AppendBytes(entry, hash) inner := protowire.AppendTag(nil, 100, protowire.VarintType) // MFFile.version inner = protowire.AppendVarint(inner, uint64(MFFile_VERSION_ONE)) @@ -89,7 +95,8 @@ func TestDeserializeRejectsInvalidEntryPaths(t *testing.T) { t.Parallel() id := uuid.New() - data := wrapInner(t, id, craftInnerBytes(id, tt.path)) + hash := make([]byte, 34) // multihash: 2-byte prefix + 32-byte SHA-256 + data := wrapInner(t, id, craftInnerBytes(id, tt.path, hash)) _, err := NewManifestFromReader(bytes.NewReader(data)) require.Error(t, err) @@ -114,6 +121,25 @@ func TestDeserializeRejectsInvalidEntryPaths(t *testing.T) { } } +// A one-byte path and a multihash of algorithm code and zero digest length +// make the smallest file entry and hash the format allows: they load, and an +// entry or hash one byte shorter is refused before decoding. +func TestDeserializeRejectsEntriesShorterThanFormatAllows(t *testing.T) { + t.Parallel() + + load := func(path string, multihash []byte) error { + id := uuid.New() + data := wrapInner(t, id, craftInnerBytes(id, path, multihash)) + _, err := NewManifestFromReader(bytes.NewReader(data)) + + return err + } + + require.NoError(t, load("a", []byte{0, 0})) + require.ErrorIs(t, load("", []byte{0, 0}), errEntryTooShort) + require.ErrorIs(t, load("ab", []byte{0}), errEntryTooShort) +} + func TestDeserializeValidManifestRoundTrips(t *testing.T) { t.Parallel() diff --git a/mfer/testdata/fuzz/FuzzNewManifestFromReader/empty-file-entries b/mfer/testdata/fuzz/FuzzNewManifestFromReader/empty-file-entries new file mode 100644 index 0000000..a56cb8c --- /dev/null +++ b/mfer/testdata/fuzz/FuzzNewManifestFromReader/empty-file-entries @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("ZNAVSRFG\xa8\x06\x01\xb0\x06\x01\xb8\x06\x80\x80\x80\b\xc2\x06 \x8cS\x11[\xbc\xfd\\c\x81B\xe3ah\x95=\xea\x9aJ0}\xc9\xef\xab\x16кj.&\xce\xcf\xdb\xca\x06\x10\x93\x85\vpu\x85\xe4\x04\xe4\x95\x1a=\xdc\x1f\x05\xa3\xba\f\xa7\f(\xb5/\xfd\x04h|\x00\x000\xa0\x06\x01\xaa\x06\x00\x01T\x06\x024\xf7\xff\x06L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15\xec\x00\x004\x01\xb2\x06\x10\x93\x85\vpu\x85\xe4\x04\xe4\x95\x1a=\xdc\x1f\x05\xa3\x01T\x00\x044\xea\xff\x15\x01\x00\x00\vN^\xb3") diff --git a/mfer/testdata/fuzz/FuzzNewManifestFromReader/file-entry-of-empty-hashes b/mfer/testdata/fuzz/FuzzNewManifestFromReader/file-entry-of-empty-hashes new file mode 100644 index 0000000..1a8052d --- /dev/null +++ b/mfer/testdata/fuzz/FuzzNewManifestFromReader/file-entry-of-empty-hashes @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("ZNAVSRFG\xa8\x06\x01\xb0\x06\x01\xb8\x06\xff\xff\xff\a\xc2\x06 \xcegai8\xe5n[\xa0:MY\xd9\x04J\xd6Ѫ\x9a%\x9ch\x84\xca\xcd\x15\\\x00K\x14\xca\x06\x10\x93\x85\vpu\x85\xe4\x04\xe4\x95\x1a=\xdc\x1f\x05\xa3\xba\f\xac\f(\xb5/\xfd\x04h\xbc\x00\x00p\xa0\x06\x01\xaa\x06\xe3\xff\xff\a\n\x01a\x1a\x00\x01T\x0e\x024\xef\xff\x05L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15L\x00\x00\x00\x01T\x00\x044\xfd\xff\x15\xed\x00\x004\x01\xb2\x06\x10\x93\x85\vpu\x85\xe4\x04\xe4\x95\x1a=\xdc\x1f\x05\xa3\x01T\x00\x044\xe9\xff\x15?Q\t\xab")