Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include <algorithm> |
michael@0 | 6 | #include "SeekableZStream.h" |
michael@0 | 7 | #include "Logging.h" |
michael@0 | 8 | |
michael@0 | 9 | bool |
michael@0 | 10 | SeekableZStream::Init(const void *buf, size_t length) |
michael@0 | 11 | { |
michael@0 | 12 | const SeekableZStreamHeader *header = SeekableZStreamHeader::validate(buf); |
michael@0 | 13 | if (!header) { |
michael@0 | 14 | LOG("Not a seekable zstream"); |
michael@0 | 15 | return false; |
michael@0 | 16 | } |
michael@0 | 17 | |
michael@0 | 18 | buffer = reinterpret_cast<const unsigned char *>(buf); |
michael@0 | 19 | totalSize = header->totalSize; |
michael@0 | 20 | chunkSize = header->chunkSize; |
michael@0 | 21 | lastChunkSize = header->lastChunkSize; |
michael@0 | 22 | windowBits = header->windowBits; |
michael@0 | 23 | dictionary.Init(buffer + sizeof(SeekableZStreamHeader), header->dictSize); |
michael@0 | 24 | offsetTable.Init(buffer + sizeof(SeekableZStreamHeader) + header->dictSize, |
michael@0 | 25 | header->nChunks); |
michael@0 | 26 | filter = GetFilter(header->filter); |
michael@0 | 27 | |
michael@0 | 28 | /* Sanity check */ |
michael@0 | 29 | if ((chunkSize == 0) || |
michael@0 | 30 | (!IsPageAlignedSize(chunkSize)) || |
michael@0 | 31 | (chunkSize > 8 * PageSize()) || |
michael@0 | 32 | (offsetTable.numElements() < 1) || |
michael@0 | 33 | (lastChunkSize == 0) || |
michael@0 | 34 | (lastChunkSize > chunkSize) || |
michael@0 | 35 | (length < totalSize)) { |
michael@0 | 36 | LOG("Malformed or broken seekable zstream"); |
michael@0 | 37 | return false; |
michael@0 | 38 | } |
michael@0 | 39 | |
michael@0 | 40 | return true; |
michael@0 | 41 | } |
michael@0 | 42 | |
michael@0 | 43 | bool |
michael@0 | 44 | SeekableZStream::Decompress(void *where, size_t chunk, size_t length) |
michael@0 | 45 | { |
michael@0 | 46 | while (length) { |
michael@0 | 47 | size_t len = std::min(length, static_cast<size_t>(chunkSize)); |
michael@0 | 48 | if (!DecompressChunk(where, chunk, len)) |
michael@0 | 49 | return false; |
michael@0 | 50 | where = reinterpret_cast<unsigned char *>(where) + len; |
michael@0 | 51 | length -= len; |
michael@0 | 52 | chunk++; |
michael@0 | 53 | } |
michael@0 | 54 | return true; |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | bool |
michael@0 | 58 | SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length) |
michael@0 | 59 | { |
michael@0 | 60 | if (chunk >= offsetTable.numElements()) { |
michael@0 | 61 | LOG("DecompressChunk: chunk #%" PRIdSize " out of range [0-%" PRIdSize ")", |
michael@0 | 62 | chunk, offsetTable.numElements()); |
michael@0 | 63 | return false; |
michael@0 | 64 | } |
michael@0 | 65 | |
michael@0 | 66 | bool isLastChunk = (chunk == offsetTable.numElements() - 1); |
michael@0 | 67 | |
michael@0 | 68 | size_t chunkLen = isLastChunk ? lastChunkSize : chunkSize; |
michael@0 | 69 | |
michael@0 | 70 | if (length == 0 || length > chunkLen) |
michael@0 | 71 | length = chunkLen; |
michael@0 | 72 | |
michael@0 | 73 | DEBUG_LOG("DecompressChunk #%" PRIdSize " @%p (%" PRIdSize "/% " PRIdSize ")", |
michael@0 | 74 | chunk, where, length, chunkLen); |
michael@0 | 75 | z_stream zStream; |
michael@0 | 76 | memset(&zStream, 0, sizeof(zStream)); |
michael@0 | 77 | zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1])) |
michael@0 | 78 | - uint32_t(offsetTable[chunk]); |
michael@0 | 79 | zStream.next_in = const_cast<Bytef *>(buffer + uint32_t(offsetTable[chunk])); |
michael@0 | 80 | zStream.avail_out = length; |
michael@0 | 81 | zStream.next_out = reinterpret_cast<Bytef *>(where); |
michael@0 | 82 | |
michael@0 | 83 | /* Decompress chunk */ |
michael@0 | 84 | if (inflateInit2(&zStream, windowBits) != Z_OK) { |
michael@0 | 85 | LOG("inflateInit failed: %s", zStream.msg); |
michael@0 | 86 | return false; |
michael@0 | 87 | } |
michael@0 | 88 | if (dictionary && inflateSetDictionary(&zStream, dictionary, |
michael@0 | 89 | dictionary.numElements()) != Z_OK) { |
michael@0 | 90 | LOG("inflateSetDictionary failed: %s", zStream.msg); |
michael@0 | 91 | return false; |
michael@0 | 92 | } |
michael@0 | 93 | if (inflate(&zStream, (length == chunkLen) ? Z_FINISH : Z_SYNC_FLUSH) |
michael@0 | 94 | != (length == chunkLen) ? Z_STREAM_END : Z_OK) { |
michael@0 | 95 | LOG("inflate failed: %s", zStream.msg); |
michael@0 | 96 | return false; |
michael@0 | 97 | } |
michael@0 | 98 | if (inflateEnd(&zStream) != Z_OK) { |
michael@0 | 99 | LOG("inflateEnd failed: %s", zStream.msg); |
michael@0 | 100 | return false; |
michael@0 | 101 | } |
michael@0 | 102 | if (filter) |
michael@0 | 103 | filter(chunk * chunkSize, UNFILTER, (unsigned char *)where, chunkLen); |
michael@0 | 104 | |
michael@0 | 105 | return true; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | /* Branch/Call/Jump conversion filter for Thumb, derived from xz-utils |
michael@0 | 109 | * by Igor Pavlov and Lasse Collin, published in the public domain */ |
michael@0 | 110 | static void |
michael@0 | 111 | BCJ_Thumb_filter(off_t offset, SeekableZStream::FilterDirection dir, |
michael@0 | 112 | unsigned char *buf, size_t size) |
michael@0 | 113 | { |
michael@0 | 114 | size_t i; |
michael@0 | 115 | for (i = 0; i + 4 <= size; i += 2) { |
michael@0 | 116 | if ((buf[i + 1] & 0xf8) == 0xf0 && (buf[i + 3] & 0xf8) == 0xf8) { |
michael@0 | 117 | uint32_t src = (buf[i] << 11) |
michael@0 | 118 | | ((buf[i + 1] & 0x07) << 19) |
michael@0 | 119 | | buf[i + 2] |
michael@0 | 120 | | ((buf[i + 3] & 0x07) << 8); |
michael@0 | 121 | src <<= 1; |
michael@0 | 122 | uint32_t dest; |
michael@0 | 123 | if (dir == SeekableZStream::FILTER) |
michael@0 | 124 | dest = offset + (uint32_t)(i) + 4 + src; |
michael@0 | 125 | else |
michael@0 | 126 | dest = src - (offset + (uint32_t)(i) + 4); |
michael@0 | 127 | |
michael@0 | 128 | dest >>= 1; |
michael@0 | 129 | buf[i] = dest >> 11; |
michael@0 | 130 | buf[i + 1] = 0xf0 | ((dest >> 19) & 0x07); |
michael@0 | 131 | buf[i + 2] = dest; |
michael@0 | 132 | buf[i + 3] = 0xf8 | ((dest >> 8) & 0x07); |
michael@0 | 133 | i += 2; |
michael@0 | 134 | } |
michael@0 | 135 | } |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | /* Branch/Call/Jump conversion filter for ARM, derived from xz-utils |
michael@0 | 139 | * by Igor Pavlov and Lasse Collin, published in the public domain */ |
michael@0 | 140 | static void |
michael@0 | 141 | BCJ_ARM_filter(off_t offset, SeekableZStream::FilterDirection dir, |
michael@0 | 142 | unsigned char *buf, size_t size) |
michael@0 | 143 | { |
michael@0 | 144 | size_t i; |
michael@0 | 145 | for (i = 0; i + 4 <= size; i += 4) { |
michael@0 | 146 | if (buf[i + 3] == 0xeb) { |
michael@0 | 147 | uint32_t src = buf[i] |
michael@0 | 148 | | (buf[i + 1] << 8) |
michael@0 | 149 | | (buf[i + 2] << 16); |
michael@0 | 150 | src <<= 2; |
michael@0 | 151 | uint32_t dest; |
michael@0 | 152 | if (dir == SeekableZStream::FILTER) |
michael@0 | 153 | dest = offset + (uint32_t)(i) + 8 + src; |
michael@0 | 154 | else |
michael@0 | 155 | dest = src - (offset + (uint32_t)(i) + 8); |
michael@0 | 156 | |
michael@0 | 157 | dest >>= 2; |
michael@0 | 158 | buf[i] = dest; |
michael@0 | 159 | buf[i + 1] = dest >> 8; |
michael@0 | 160 | buf[i + 2] = dest >> 16; |
michael@0 | 161 | } |
michael@0 | 162 | } |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | /* Branch/Call/Jump conversion filter for x86, derived from xz-utils |
michael@0 | 166 | * by Igor Pavlov and Lasse Collin, published in the public domain */ |
michael@0 | 167 | |
michael@0 | 168 | #define Test86MSByte(b) ((b) == 0 || (b) == 0xff) |
michael@0 | 169 | |
michael@0 | 170 | static void |
michael@0 | 171 | BCJ_X86_filter(off_t offset, SeekableZStream::FilterDirection dir, |
michael@0 | 172 | unsigned char *buf, size_t size) |
michael@0 | 173 | { |
michael@0 | 174 | static const bool MASK_TO_ALLOWED_STATUS[8] = |
michael@0 | 175 | { true, true, true, false, true, false, false, false }; |
michael@0 | 176 | |
michael@0 | 177 | static const uint32_t MASK_TO_BIT_NUMBER[8] = |
michael@0 | 178 | { 0, 1, 2, 2, 3, 3, 3, 3 }; |
michael@0 | 179 | |
michael@0 | 180 | uint32_t prev_mask = 0; |
michael@0 | 181 | uint32_t prev_pos = 0; |
michael@0 | 182 | |
michael@0 | 183 | for (size_t i = 0; i + 5 <= size;) { |
michael@0 | 184 | uint8_t b = buf[i]; |
michael@0 | 185 | if (b != 0xe8 && b != 0xe9) { |
michael@0 | 186 | ++i; |
michael@0 | 187 | continue; |
michael@0 | 188 | } |
michael@0 | 189 | |
michael@0 | 190 | const uint32_t off = offset + (uint32_t)(i) - prev_pos; |
michael@0 | 191 | prev_pos = offset + (uint32_t)(i); |
michael@0 | 192 | |
michael@0 | 193 | if (off > 5) { |
michael@0 | 194 | prev_mask = 0; |
michael@0 | 195 | } else { |
michael@0 | 196 | for (uint32_t i = 0; i < off; ++i) { |
michael@0 | 197 | prev_mask &= 0x77; |
michael@0 | 198 | prev_mask <<= 1; |
michael@0 | 199 | } |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | b = buf[i + 4]; |
michael@0 | 203 | |
michael@0 | 204 | if (Test86MSByte(b) && MASK_TO_ALLOWED_STATUS[(prev_mask >> 1) & 0x7] |
michael@0 | 205 | && (prev_mask >> 1) < 0x10) { |
michael@0 | 206 | |
michael@0 | 207 | uint32_t src = ((uint32_t)(b) << 24) |
michael@0 | 208 | | ((uint32_t)(buf[i + 3]) << 16) |
michael@0 | 209 | | ((uint32_t)(buf[i + 2]) << 8) |
michael@0 | 210 | | (buf[i + 1]); |
michael@0 | 211 | |
michael@0 | 212 | uint32_t dest; |
michael@0 | 213 | while (true) { |
michael@0 | 214 | if (dir == SeekableZStream::FILTER) |
michael@0 | 215 | dest = src + (offset + (uint32_t)(i) + 5); |
michael@0 | 216 | else |
michael@0 | 217 | dest = src - (offset + (uint32_t)(i) + 5); |
michael@0 | 218 | |
michael@0 | 219 | if (prev_mask == 0) |
michael@0 | 220 | break; |
michael@0 | 221 | |
michael@0 | 222 | const uint32_t i = MASK_TO_BIT_NUMBER[prev_mask >> 1]; |
michael@0 | 223 | |
michael@0 | 224 | b = (uint8_t)(dest >> (24 - i * 8)); |
michael@0 | 225 | |
michael@0 | 226 | if (!Test86MSByte(b)) |
michael@0 | 227 | break; |
michael@0 | 228 | |
michael@0 | 229 | src = dest ^ ((1 << (32 - i * 8)) - 1); |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | buf[i + 4] = (uint8_t)(~(((dest >> 24) & 1) - 1)); |
michael@0 | 233 | buf[i + 3] = (uint8_t)(dest >> 16); |
michael@0 | 234 | buf[i + 2] = (uint8_t)(dest >> 8); |
michael@0 | 235 | buf[i + 1] = (uint8_t)(dest); |
michael@0 | 236 | i += 5; |
michael@0 | 237 | prev_mask = 0; |
michael@0 | 238 | |
michael@0 | 239 | } else { |
michael@0 | 240 | ++i; |
michael@0 | 241 | prev_mask |= 1; |
michael@0 | 242 | if (Test86MSByte(b)) |
michael@0 | 243 | prev_mask |= 0x10; |
michael@0 | 244 | } |
michael@0 | 245 | } |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | SeekableZStream::ZStreamFilter |
michael@0 | 249 | SeekableZStream::GetFilter(SeekableZStream::FilterId id) |
michael@0 | 250 | { |
michael@0 | 251 | switch (id) { |
michael@0 | 252 | case BCJ_THUMB: |
michael@0 | 253 | return BCJ_Thumb_filter; |
michael@0 | 254 | case BCJ_ARM: |
michael@0 | 255 | return BCJ_ARM_filter; |
michael@0 | 256 | case BCJ_X86: |
michael@0 | 257 | return BCJ_X86_filter; |
michael@0 | 258 | default: |
michael@0 | 259 | return nullptr; |
michael@0 | 260 | } |
michael@0 | 261 | return nullptr; |
michael@0 | 262 | } |