DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (901e88980751)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
#include "XZStream.h"

#include <algorithm>
#include <cstring>
#include "mozilla/Assertions.h"
#include "mozilla/CheckedInt.h"
#include "Logging.h"

// LZMA dictionary size, should have a minimum size for the given compression
// rate, see XZ Utils docs for details.
static const uint32_t kDictSize = 1 << 24;

static const size_t kFooterSize = 12;

// Parses a variable-length integer (VLI),
// see http://tukaani.org/xz/xz-file-format.txt for details.
static size_t ParseVarLenInt(const uint8_t* aBuf, size_t aBufSize,
                             uint64_t* aValue) {
  if (!aBufSize) {
    return 0;
  }
  aBufSize = std::min(size_t(9), aBufSize);

  *aValue = aBuf[0] & 0x7F;
  size_t i = 0;

  while (aBuf[i++] & 0x80) {
    if (i >= aBufSize || aBuf[i] == 0x0) {
      return 0;
    }
    *aValue |= static_cast<uint64_t>(aBuf[i] & 0x7F) << (i * 7);
  }
  return i;
}

/* static */
bool XZStream::IsXZ(const void* aBuf, size_t aBufSize) {
  static const uint8_t kXzMagic[] = {0xfd, '7', 'z', 'X', 'Z', 0x0};
  MOZ_ASSERT(aBuf);
  return aBufSize > sizeof(kXzMagic) &&
         !memcmp(reinterpret_cast<const void*>(kXzMagic), aBuf,
                 sizeof(kXzMagic));
}

XZStream::XZStream(const void* aInBuf, size_t aInSize)
    : mInBuf(static_cast<const uint8_t*>(aInBuf)),
      mUncompSize(0),
      mDec(nullptr) {
  mBuffers.in = mInBuf;
  mBuffers.in_pos = 0;
  mBuffers.in_size = aInSize;
}

XZStream::~XZStream() { xz_dec_end(mDec); }

bool XZStream::Init() {
#ifdef XZ_USE_CRC64
  xz_crc64_init();
#endif
  xz_crc32_init();

  mDec = xz_dec_init(XZ_DYNALLOC, kDictSize);

  if (!mDec) {
    return false;
  }

  mUncompSize = ParseUncompressedSize();
  if (!mUncompSize) {
    return false;
  }

  return true;
}

size_t XZStream::Decode(void* aOutBuf, size_t aOutSize) {
  if (!mDec) {
    return 0;
  }

  mBuffers.out = static_cast<uint8_t*>(aOutBuf);
  mBuffers.out_pos = 0;
  mBuffers.out_size = aOutSize;

  while (mBuffers.in_pos < mBuffers.in_size &&
         mBuffers.out_pos < mBuffers.out_size) {
    const xz_ret ret = xz_dec_run(mDec, &mBuffers);

    switch (ret) {
      case XZ_STREAM_END:
        // Stream ended, the next loop iteration should terminate.
        MOZ_ASSERT(mBuffers.in_pos == mBuffers.in_size);
        MOZ_FALLTHROUGH;
#ifdef XZ_DEC_ANY_CHECK
      case XZ_UNSUPPORTED_CHECK:
        // Ignore unsupported check.
        MOZ_FALLTHROUGH;
#endif
      case XZ_OK:
        // Chunk decoded, proceed.
        break;

      case XZ_MEM_ERROR:
        ERROR("XZ decoding: memory allocation failed");
        return 0;

      case XZ_MEMLIMIT_ERROR:
        ERROR("XZ decoding: memory usage limit reached");
        return 0;

      case XZ_FORMAT_ERROR:
        ERROR("XZ decoding: invalid stream format");
        return 0;

      case XZ_OPTIONS_ERROR:
        ERROR("XZ decoding: unsupported header options");
        return 0;

      case XZ_DATA_ERROR:
        MOZ_FALLTHROUGH;
      case XZ_BUF_ERROR:
        ERROR("XZ decoding: corrupt input stream");
        return 0;

      default:
        MOZ_ASSERT_UNREACHABLE("XZ decoding: unknown error condition");
        return 0;
    }
  }
  return mBuffers.out_pos;
}

size_t XZStream::RemainingInput() const {
  return mBuffers.in_size - mBuffers.in_pos;
}

size_t XZStream::Size() const { return mBuffers.in_size; }

size_t XZStream::UncompressedSize() const { return mUncompSize; }

size_t XZStream::ParseIndexSize() const {
  static const uint8_t kFooterMagic[] = {'Y', 'Z'};

  const uint8_t* footer = mInBuf + mBuffers.in_size - kFooterSize;
  // The magic bytes are at the end of the footer.
  if (memcmp(reinterpret_cast<const void*>(kFooterMagic),
             footer + kFooterSize - sizeof(kFooterMagic),
             sizeof(kFooterMagic))) {
    // Not a valid footer at stream end.
    ERROR("XZ parsing: Invalid footer at end of stream");
    return 0;
  }
  // Backward size is a 32 bit LE integer field positioned after the 32 bit
  // CRC32 code. It encodes the index size as a multiple of 4 bytes with a
  // minimum size of 4 bytes.
  const uint32_t backwardSizeRaw = *(footer + 4);
  // Check for overflow.
  mozilla::CheckedInt<size_t> backwardSizeBytes(backwardSizeRaw);
  backwardSizeBytes = (backwardSizeBytes + 1) * 4;
  if (!backwardSizeBytes.isValid()) {
    ERROR("XZ parsing: Cannot parse index size");
    return 0;
  }
  return backwardSizeBytes.value();
}

size_t XZStream::ParseUncompressedSize() const {
  static const uint8_t kIndexIndicator[] = {0x0};

  const size_t indexSize = ParseIndexSize();
  if (!indexSize) {
    return 0;
  }
  // The footer follows directly the index, so we can use it as a reference.
  const uint8_t* end = mInBuf + mBuffers.in_size;
  const uint8_t* index = end - kFooterSize - indexSize;

  // The xz stream index consists of three concatenated elements:
  //  (1) 1 byte indicator (always OxOO)
  //  (2) a Variable Length Integer (VLI) field for the number of records
  //  (3) a list of records
  // See https://tukaani.org/xz/xz-file-format-1.0.4.txt
  // Each record contains a VLI field for unpadded size followed by a var field
  // for uncompressed size. We only support xz streams with a single record.

  if (memcmp(reinterpret_cast<const void*>(kIndexIndicator), index,
             sizeof(kIndexIndicator))) {
    ERROR("XZ parsing: Invalid stream index");
    return 0;
  }

  index += sizeof(kIndexIndicator);
  uint64_t numRecords = 0;
  index += ParseVarLenInt(index, end - index, &numRecords);
  // Only streams with a single record are supported.
  if (numRecords != 1) {
    ERROR("XZ parsing: Multiple records not supported");
    return 0;
  }
  uint64_t unpaddedSize = 0;
  index += ParseVarLenInt(index, end - index, &unpaddedSize);
  if (!unpaddedSize) {
    ERROR("XZ parsing: Unpadded size is 0");
    return 0;
  }
  uint64_t uncompressedSize = 0;
  index += ParseVarLenInt(index, end - index, &uncompressedSize);
  mozilla::CheckedInt<size_t> checkedSize(uncompressedSize);
  if (!checkedSize.isValid()) {
    ERROR("XZ parsing: Uncompressed stream size is too large");
    return 0;
  }

  return checkedSize.value();
}