diff options
author | Dan Bornstein <danfuzz@android.com> | 2011-04-14 13:08:06 -0700 |
---|---|---|
committer | Dan Bornstein <danfuzz@android.com> | 2011-04-14 13:32:49 -0700 |
commit | a70a3d8faa8f7332549fa0c9ae2008d428e28606 (patch) | |
tree | 44c87c48520df636c76e3b175e2d9d00efd5b28d /libdex/DexSwapVerify.cpp | |
parent | c6d2470eec726ae0ad95e4fd2d9d7da7cb2cdcba (diff) | |
download | android_dalvik-a70a3d8faa8f7332549fa0c9ae2008d428e28606.tar.gz android_dalvik-a70a3d8faa8f7332549fa0c9ae2008d428e28606.tar.bz2 android_dalvik-a70a3d8faa8f7332549fa0c9ae2008d428e28606.zip |
Compile libdex as C++.
The major-looking code changes were all just to unravel some gotos.
Change-Id: I86f98a48b160f357ce93c87446bad5d705d5f05b
Diffstat (limited to 'libdex/DexSwapVerify.cpp')
-rw-r--r-- | libdex/DexSwapVerify.cpp | 2946 |
1 files changed, 2946 insertions, 0 deletions
diff --git a/libdex/DexSwapVerify.cpp b/libdex/DexSwapVerify.cpp new file mode 100644 index 000000000..13259370d --- /dev/null +++ b/libdex/DexSwapVerify.cpp @@ -0,0 +1,2946 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Byte-swapping and verification of dex files. + */ + +#include "DexFile.h" +#include "DexClass.h" +#include "DexDataMap.h" +#include "DexProto.h" +#include "DexUtf.h" +#include "Leb128.h" + +#include <safe_iop.h> +#include <zlib.h> + +#include <stdlib.h> +#include <string.h> + +#ifndef __BYTE_ORDER +# error "byte ordering not defined" +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +# define SWAP2(_value) (_value) +# define SWAP4(_value) (_value) +# define SWAP8(_value) (_value) +#else +# define SWAP2(_value) endianSwapU2((_value)) +# define SWAP4(_value) endianSwapU4((_value)) +# define SWAP8(_value) endianSwapU8((_value)) +static u2 endianSwapU2(u2 value) { + return (value >> 8) | (value << 8); +} +static u4 endianSwapU4(u4 value) { + /* ABCD --> CDAB --> DCBA */ + value = (value >> 16) | (value << 16); + return ((value & 0xff00ff00) >> 8) | ((value << 8) & 0xff00ff00); +} +static u8 endianSwapU8(u8 value) { + /* ABCDEFGH --> EFGHABCD --> GHEFCDAB --> HGFEDCBA */ + value = (value >> 32) | (value << 32); + value = ((value & 0xffff0000ffff0000ULL) >> 16) | + ((value << 16) & 0xffff0000ffff0000ULL); + return ((value & 0xff00ff00ff00ff00ULL) >> 8) | + ((value << 8) & 0xff00ff00ff00ff00ULL); +} +#endif + +#define SWAP_FIELD2(_field) (_field) = SWAP2(_field) +#define SWAP_FIELD4(_field) (_field) = SWAP4(_field) +#define SWAP_FIELD8(_field) (_field) = SWAP8(_field) + +/* + * Some information we pass around to help verify values. + */ +typedef struct CheckState { + const DexHeader* pHeader; + const u1* fileStart; + const u1* fileEnd; // points to fileStart + fileLen + u4 fileLen; + DexDataMap* pDataMap; // set after map verification + const DexFile* pDexFile; // set after intraitem verification + + /* + * bitmap of type_id indices that have been used to define classes; + * initialized immediately before class_def cross-verification, and + * freed immediately after it + */ + u4* pDefinedClassBits; + + const void* previousItem; // set during section iteration +} CheckState; + +/* + * Return the file offset of the given pointer. + */ +static inline u4 fileOffset(const CheckState* state, const void* ptr) { + return ((const u1*) ptr) - state->fileStart; +} + +/* + * Return a pointer for the given file offset. + */ +static inline void* filePointer(const CheckState* state, u4 offset) { + return (void*) (state->fileStart + offset); +} + +/* + * Verify that a pointer range, start inclusive to end exclusive, only + * covers bytes in the file and doesn't point beyond the end of the + * file. That is, the start must indicate a valid byte or may point at + * the byte just past the end of the file (but no further), and the + * end must be no less than the start and must also not point beyond + * the byte just past the end of the file. + */ +static inline bool checkPtrRange(const CheckState* state, + const void* start, const void* end, const char* label) { + const void* fileStart = state->fileStart; + const void* fileEnd = state->fileEnd; + if ((start < fileStart) || (start > fileEnd) + || (end < start) || (end > fileEnd)) { + LOGW("Bad offset range for %s: 0x%x..0x%x\n", label, + fileOffset(state, start), fileOffset(state, end)); + return false; + } + return true; +} + +/* + * Verify that a range of offsets, start inclusive to end exclusive, + * are all valid. That is, the start must indicate a valid byte or may + * point at the byte just past the end of the file (but no further), + * and the end must be no less than the start and must also not point + * beyond the byte just past the end of the file. + * + * Assumes "const CheckState* state". + */ +#define CHECK_OFFSET_RANGE(_start, _end) { \ + const u1* _startPtr = (const u1*) filePointer(state, (_start)); \ + const u1* _endPtr = (const u1*) filePointer(state, (_end)); \ + if (!checkPtrRange(state, _startPtr, _endPtr, \ + #_start ".." #_end)) { \ + return 0; \ + } \ + } + +/* + * Verify that a pointer range, start inclusive to end exclusive, only + * covers bytes in the file and doesn't point beyond the end of the + * file. That is, the start must indicate a valid byte or may point at + * the byte just past the end of the file (but no further), and the + * end must be no less than the start and must also not point beyond + * the byte just past the end of the file. + * + * Assumes "const CheckState* state". + */ +#define CHECK_PTR_RANGE(_start, _end) { \ + if (!checkPtrRange(state, (_start), (_end), #_start ".." #_end)) { \ + return 0; \ + } \ + } + +/* + * Make sure a list of items fits entirely within the file. + * + * Assumes "const CheckState* state" and "typeof(_count) == typeof(_elemSize)" + * If the type sizes or signs are mismatched, this will return 0. + */ +#define CHECK_LIST_SIZE(_ptr, _count, _elemSize) { \ + const u1* _start = (const u1*) (_ptr); \ + const u1* _end = _start + ((_count) * (_elemSize)); \ + if (!safe_mul(NULL, (_count), (_elemSize)) || \ + !checkPtrRange(state, _start, _end, #_ptr)) { \ + return 0; \ + } \ + } + +/* + * Swap a field that is known to hold an absolute DEX file offset. Note: + * This does not check to see that the swapped offset points within the + * mapped file, since that should be handled (with even more rigor) by + * the cross-verification phase. + * + * Assumes "const CheckState* state". + */ +#define SWAP_OFFSET4(_field) { \ + SWAP_FIELD4((_field)); \ + } + +/* + * Verify that an index falls in a valid range. + */ +#define CHECK_INDEX(_field, _limit) { \ + if ((_field) >= (_limit)) { \ + LOGW("Bad index: %s(%u) > %s(%u)\n", \ + #_field, (u4)(_field), #_limit, (u4)(_limit)); \ + return 0; \ + } \ + } + +/* + * Swap an index, and verify that it falls in a valid range. + */ +#define SWAP_INDEX2(_field, _limit) { \ + SWAP_FIELD2((_field)); \ + CHECK_INDEX((_field), (_limit)); \ + } + +/* + * Verify that an index falls in a valid range or is kDexNoIndex. + */ +#define CHECK_INDEX_OR_NOINDEX(_field, _limit) { \ + if ((_field) != kDexNoIndex && (_field) >= (_limit)) { \ + LOGW("Bad index: %s(%u) > %s(%u)\n", \ + #_field, (u4)(_field), #_limit, (u4)(_limit)); \ + return 0; \ + } \ + } + +/* + * Swap an index, and verify that it falls in a valid range. + */ +#define SWAP_INDEX4(_field, _limit) { \ + SWAP_FIELD4((_field)); \ + CHECK_INDEX((_field), (_limit)); \ + } + +/* + * Swap an index, and verify that it falls in a valid range or is + * kDexNoIndex. + */ +#define SWAP_INDEX4_OR_NOINDEX(_field, _limit) { \ + SWAP_FIELD4((_field)); \ + CHECK_INDEX_OR_NOINDEX((_field), (_limit)); \ + } + +/* Verify the definer of a given field_idx. */ +static bool verifyFieldDefiner(const CheckState* state, u4 definingClass, + u4 fieldIdx) { + const DexFieldId* field = dexGetFieldId(state->pDexFile, fieldIdx); + return field->classIdx == definingClass; +} + +/* Verify the definer of a given method_idx. */ +static bool verifyMethodDefiner(const CheckState* state, u4 definingClass, + u4 methodIdx) { + const DexMethodId* meth = dexGetMethodId(state->pDexFile, methodIdx); + return meth->classIdx == definingClass; +} + +/* + * Calculate the required size (in elements) of the array pointed at by + * pDefinedClassBits. + */ +static size_t calcDefinedClassBitsSize(const CheckState* state) +{ + // Divide typeIdsSize by 32 (0x20), rounding up. + return (state->pHeader->typeIdsSize + 0x1f) >> 5; +} + +/* + * Set the given bit in pDefinedClassBits, returning its former value. + */ +static bool setDefinedClassBit(const CheckState* state, u4 typeIdx) { + u4 arrayIdx = typeIdx >> 5; + u4 bit = 1 << (typeIdx & 0x1f); + u4* element = &state->pDefinedClassBits[arrayIdx]; + bool result = (*element & bit) != 0; + + *element |= bit; + + return result; +} + +/* + * Swap the header_item. + */ +static bool swapDexHeader(const CheckState* state, DexHeader* pHeader) +{ + CHECK_PTR_RANGE(pHeader, pHeader + 1); + + // magic is ok + SWAP_FIELD4(pHeader->checksum); + // signature is ok + SWAP_FIELD4(pHeader->fileSize); + SWAP_FIELD4(pHeader->headerSize); + SWAP_FIELD4(pHeader->endianTag); + SWAP_FIELD4(pHeader->linkSize); + SWAP_OFFSET4(pHeader->linkOff); + SWAP_OFFSET4(pHeader->mapOff); + SWAP_FIELD4(pHeader->stringIdsSize); + SWAP_OFFSET4(pHeader->stringIdsOff); + SWAP_FIELD4(pHeader->typeIdsSize); + SWAP_OFFSET4(pHeader->typeIdsOff); + SWAP_FIELD4(pHeader->fieldIdsSize); + SWAP_OFFSET4(pHeader->fieldIdsOff); + SWAP_FIELD4(pHeader->methodIdsSize); + SWAP_OFFSET4(pHeader->methodIdsOff); + SWAP_FIELD4(pHeader->protoIdsSize); + SWAP_OFFSET4(pHeader->protoIdsOff); + SWAP_FIELD4(pHeader->classDefsSize); + SWAP_OFFSET4(pHeader->classDefsOff); + SWAP_FIELD4(pHeader->dataSize); + SWAP_OFFSET4(pHeader->dataOff); + + if (pHeader->endianTag != kDexEndianConstant) { + LOGE("Unexpected endian_tag: 0x%x\n", pHeader->endianTag); + return false; + } + + // Assign variables so the diagnostic is prettier. (Hooray for macros.) + u4 linkOff = pHeader->linkOff; + u4 linkEnd = linkOff + pHeader->linkSize; + u4 dataOff = pHeader->dataOff; + u4 dataEnd = dataOff + pHeader->dataSize; + CHECK_OFFSET_RANGE(linkOff, linkEnd); + CHECK_OFFSET_RANGE(dataOff, dataEnd); + + /* + * Note: The offsets and ranges of the other header items end up getting + * checked during the first iteration over the map. + */ + + return true; +} + +/* Check the header section for sanity. */ +static bool checkHeaderSection(const CheckState* state, u4 sectionOffset, + u4 sectionCount, u4* endOffset) { + if (sectionCount != 1) { + LOGE("Multiple header items\n"); + return false; + } + + if (sectionOffset != 0) { + LOGE("Header at 0x%x; not at start of file\n", sectionOffset); + return false; + } + + const DexHeader* pHeader = (const DexHeader*) filePointer(state, 0); + *endOffset = pHeader->headerSize; + return true; +} + +/* + * Helper for swapMap(), which turns a map type constant into a small + * one-bit-on integer, suitable for use in an int-sized bit set. + */ +static u4 mapTypeToBitMask(int mapType) { + switch (mapType) { + case kDexTypeHeaderItem: return 1 << 0; + case kDexTypeStringIdItem: return 1 << 1; + case kDexTypeTypeIdItem: return 1 << 2; + case kDexTypeProtoIdItem: return 1 << 3; + case kDexTypeFieldIdItem: return 1 << 4; + case kDexTypeMethodIdItem: return 1 << 5; + case kDexTypeClassDefItem: return 1 << 6; + case kDexTypeMapList: return 1 << 7; + case kDexTypeTypeList: return 1 << 8; + case kDexTypeAnnotationSetRefList: return 1 << 9; + case kDexTypeAnnotationSetItem: return 1 << 10; + case kDexTypeClassDataItem: return 1 << 11; + case kDexTypeCodeItem: return 1 << 12; + case kDexTypeStringDataItem: return 1 << 13; + case kDexTypeDebugInfoItem: return 1 << 14; + case kDexTypeAnnotationItem: return 1 << 15; + case kDexTypeEncodedArrayItem: return 1 << 16; + case kDexTypeAnnotationsDirectoryItem: return 1 << 17; + default: { + LOGE("Unknown map item type %04x\n", mapType); + return 0; + } + } +} + +/* + * Helper for swapMap(), which indicates if an item type should appear + * in the data section. + */ +static bool isDataSectionType(int mapType) { + switch (mapType) { + case kDexTypeHeaderItem: + case kDexTypeStringIdItem: + case kDexTypeTypeIdItem: + case kDexTypeProtoIdItem: + case kDexTypeFieldIdItem: + case kDexTypeMethodIdItem: + case kDexTypeClassDefItem: { + return false; + } + } + + return true; +} + +/* + * Swap the map_list and verify what we can about it. Also, if verification + * passes, allocate the state's DexDataMap. + */ +static bool swapMap(CheckState* state, DexMapList* pMap) +{ + DexMapItem* item = pMap->list; + u4 count; + u4 dataItemCount = 0; // Total count of items in the data section. + u4 dataItemsLeft = state->pHeader->dataSize; // See use below. + u4 usedBits = 0; // Bit set: one bit per section + bool first = true; + u4 lastOffset = 0; + + SWAP_FIELD4(pMap->size); + count = pMap->size; + + CHECK_LIST_SIZE(item, count, sizeof(DexMapItem)); + + while (count--) { + SWAP_FIELD2(item->type); + SWAP_FIELD2(item->unused); + SWAP_FIELD4(item->size); + SWAP_OFFSET4(item->offset); + + if (first) { + first = false; + } else if (lastOffset >= item->offset) { + LOGE("Out-of-order map item: 0x%x then 0x%x\n", + lastOffset, item->offset); + return false; + } + + if (item->offset >= state->pHeader->fileSize) { + LOGE("Map item after end of file: %x, size 0x%x\n", + item->offset, state->pHeader->fileSize); + return false; + } + + if (isDataSectionType(item->type)) { + u4 icount = item->size; + + /* + * This sanity check on the data section items ensures that + * there are no more items than the number of bytes in + * the data section. + */ + if (icount > dataItemsLeft) { + LOGE("Unrealistically many items in the data section: " + "at least %d\n", dataItemCount + icount); + return false; + } + + dataItemsLeft -= icount; + dataItemCount += icount; + } + + u4 bit = mapTypeToBitMask(item->type); + + if (bit == 0) { + return false; + } + + if ((usedBits & bit) != 0) { + LOGE("Duplicate map section of type 0x%x\n", item->type); + return false; + } + + usedBits |= bit; + lastOffset = item->offset; + item++; + } + + if ((usedBits & mapTypeToBitMask(kDexTypeHeaderItem)) == 0) { + LOGE("Map is missing header entry\n"); + return false; + } + + if ((usedBits & mapTypeToBitMask(kDexTypeMapList)) == 0) { + LOGE("Map is missing map_list entry\n"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeStringIdItem)) == 0) + && ((state->pHeader->stringIdsOff != 0) + || (state->pHeader->stringIdsSize != 0))) { + LOGE("Map is missing string_ids entry\n"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeTypeIdItem)) == 0) + && ((state->pHeader->typeIdsOff != 0) + || (state->pHeader->typeIdsSize != 0))) { + LOGE("Map is missing type_ids entry\n"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeProtoIdItem)) == 0) + && ((state->pHeader->protoIdsOff != 0) + || (state->pHeader->protoIdsSize != 0))) { + LOGE("Map is missing proto_ids entry\n"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeFieldIdItem)) == 0) + && ((state->pHeader->fieldIdsOff != 0) + || (state->pHeader->fieldIdsSize != 0))) { + LOGE("Map is missing field_ids entry\n"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeMethodIdItem)) == 0) + && ((state->pHeader->methodIdsOff != 0) + || (state->pHeader->methodIdsSize != 0))) { + LOGE("Map is missing method_ids entry\n"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeClassDefItem)) == 0) + && ((state->pHeader->classDefsOff != 0) + || (state->pHeader->classDefsSize != 0))) { + LOGE("Map is missing class_defs entry\n"); + return false; + } + + state->pDataMap = dexDataMapAlloc(dataItemCount); + if (state->pDataMap == NULL) { + LOGE("Unable to allocate data map (size 0x%x)\n", dataItemCount); + return false; + } + + return true; +} + +/* Check the map section for sanity. */ +static bool checkMapSection(const CheckState* state, u4 sectionOffset, + u4 sectionCount, u4* endOffset) { + if (sectionCount != 1) { + LOGE("Multiple map list items"); + return false; + } + + if (sectionOffset != state->pHeader->mapOff) { + LOGE("Map not at header-defined offset: 0x%x, expected 0x%x\n", + sectionOffset, state->pHeader->mapOff); + return false; + } + + const DexMapList* pMap = (const DexMapList*) filePointer(state, sectionOffset); + + *endOffset = + sectionOffset + sizeof(u4) + (pMap->size * sizeof(DexMapItem)); + return true; +} + +/* Perform byte-swapping and intra-item verification on string_id_item. */ +static void* swapStringIdItem(const CheckState* state, void* ptr) { + DexStringId* item = (DexStringId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_OFFSET4(item->stringDataOff); + + return item + 1; +} + +/* Perform cross-item verification of string_id_item. */ +static void* crossVerifyStringIdItem(const CheckState* state, void* ptr) { + const DexStringId* item = (const DexStringId*) ptr; + + if (!dexDataMapVerify(state->pDataMap, + item->stringDataOff, kDexTypeStringDataItem)) { + return NULL; + } + + const DexStringId* item0 = (const DexStringId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. + const char* s0 = dexGetStringData(state->pDexFile, item0); + const char* s1 = dexGetStringData(state->pDexFile, item); + if (dexUtf8Cmp(s0, s1) >= 0) { + LOGE("Out-of-order string_ids: '%s' then '%s'\n", s0, s1); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on type_id_item. */ +static void* swapTypeIdItem(const CheckState* state, void* ptr) { + DexTypeId* item = (DexTypeId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX4(item->descriptorIdx, state->pHeader->stringIdsSize); + + return item + 1; +} + +/* Perform cross-item verification of type_id_item. */ +static void* crossVerifyTypeIdItem(const CheckState* state, void* ptr) { + const DexTypeId* item = (const DexTypeId*) ptr; + const char* descriptor = + dexStringById(state->pDexFile, item->descriptorIdx); + + if (!dexIsValidTypeDescriptor(descriptor)) { + LOGE("Invalid type descriptor: '%s'\n", descriptor); + return NULL; + } + + const DexTypeId* item0 = (const DexTypeId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on string_ids being in order. + if (item0->descriptorIdx >= item->descriptorIdx) { + LOGE("Out-of-order type_ids: 0x%x then 0x%x\n", + item0->descriptorIdx, item->descriptorIdx); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on proto_id_item. */ +static void* swapProtoIdItem(const CheckState* state, void* ptr) { + DexProtoId* item = (DexProtoId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX4(item->shortyIdx, state->pHeader->stringIdsSize); + SWAP_INDEX4(item->returnTypeIdx, state->pHeader->typeIdsSize); + SWAP_OFFSET4(item->parametersOff); + + return item + 1; +} + +/* Helper for crossVerifyProtoIdItem(), which checks a shorty character + * to see if it is compatible with a type descriptor. Returns true if + * so, false if not. */ +static bool shortyDescMatch(char shorty, const char* descriptor, bool + isReturnType) { + switch (shorty) { + case 'V': { + if (!isReturnType) { + LOGE("Invalid use of void\n"); + return false; + } + // Fall through. + } + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + case 'Z': { + if ((descriptor[0] != shorty) || (descriptor[1] != '\0')) { + LOGE("Shorty vs. primitive type mismatch: '%c', '%s'\n", + shorty, descriptor); + return false; + } + break; + } + case 'L': { + if ((descriptor[0] != 'L') && (descriptor[0] != '[')) { + LOGE("Shorty vs. type mismatch: '%c', '%s'\n", + shorty, descriptor); + return false; + } + break; + } + default: { + LOGE("Bogus shorty: '%c'\n", shorty); + return false; + } + } + + return true; +} + +/* Perform cross-item verification of proto_id_item. */ +static void* crossVerifyProtoIdItem(const CheckState* state, void* ptr) { + const DexProtoId* item = (const DexProtoId*) ptr; + const char* shorty = + dexStringById(state->pDexFile, item->shortyIdx); + + if (!dexDataMapVerify0Ok(state->pDataMap, + item->parametersOff, kDexTypeTypeList)) { + return NULL; + } + + if (!shortyDescMatch(*shorty, + dexStringByTypeIdx(state->pDexFile, item->returnTypeIdx), + true)) { + return NULL; + } + + u4 protoIdx = item - state->pDexFile->pProtoIds; + DexProto proto = { state->pDexFile, protoIdx }; + DexParameterIterator iterator; + + dexParameterIteratorInit(&iterator, &proto); + shorty++; // Skip the return type. + + for (;;) { + const char *desc = dexParameterIteratorNextDescriptor(&iterator); + + if (desc == NULL) { + break; + } + + if (*shorty == '\0') { + LOGE("Shorty is too short\n"); + return NULL; + } + + if (!shortyDescMatch(*shorty, desc, false)) { + return NULL; + } + + shorty++; + } + + if (*shorty != '\0') { + LOGE("Shorty is too long\n"); + return NULL; + } + + const DexProtoId* item0 = (const DexProtoId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on type_ids being in order. + if (item0->returnTypeIdx > item->returnTypeIdx) { + LOGE("Out-of-order proto_id return types\n"); + return NULL; + } else if (item0->returnTypeIdx == item->returnTypeIdx) { + bool badOrder = false; + DexProto proto0 = { state->pDexFile, protoIdx - 1 }; + DexParameterIterator iterator0; + + dexParameterIteratorInit(&iterator, &proto); + dexParameterIteratorInit(&iterator0, &proto0); + + for (;;) { + u4 idx0 = dexParameterIteratorNextIndex(&iterator0); + u4 idx1 = dexParameterIteratorNextIndex(&iterator); + + if (idx1 == kDexNoIndex) { + badOrder = true; + break; + } + + if (idx0 == kDexNoIndex) { + break; + } + + if (idx0 < idx1) { + break; + } else if (idx0 > idx1) { + badOrder = true; + break; + } + } + + if (badOrder) { + LOGE("Out-of-order proto_id arguments\n"); + return NULL; + } + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on field_id_item. */ +static void* swapFieldIdItem(const CheckState* state, void* ptr) { + DexFieldId* item = (DexFieldId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX2(item->classIdx, state->pHeader->typeIdsSize); + SWAP_INDEX2(item->typeIdx, state->pHeader->typeIdsSize); + SWAP_INDEX4(item->nameIdx, state->pHeader->stringIdsSize); + + return item + 1; +} + +/* Perform cross-item verification of field_id_item. */ +static void* crossVerifyFieldIdItem(const CheckState* state, void* ptr) { + const DexFieldId* item = (const DexFieldId*) ptr; + const char* s; + + s = dexStringByTypeIdx(state->pDexFile, item->classIdx); + if (!dexIsClassDescriptor(s)) { + LOGE("Invalid descriptor for class_idx: '%s'\n", s); + return NULL; + } + + s = dexStringByTypeIdx(state->pDexFile, item->typeIdx); + if (!dexIsFieldDescriptor(s)) { + LOGE("Invalid descriptor for type_idx: '%s'\n", s); + return NULL; + } + + s = dexStringById(state->pDexFile, item->nameIdx); + if (!dexIsValidMemberName(s)) { + LOGE("Invalid name: '%s'\n", s); + return NULL; + } + + const DexFieldId* item0 = (const DexFieldId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on the other sections being in order. + bool done = false; + bool bogus = false; + + if (item0->classIdx > item->classIdx) { + bogus = true; + done = true; + } else if (item0->classIdx < item->classIdx) { + done = true; + } + + if (!done) { + if (item0->nameIdx > item->nameIdx) { + bogus = true; + done = true; + } else if (item0->nameIdx < item->nameIdx) { + done = true; + } + } + + if (!done) { + if (item0->typeIdx >= item->typeIdx) { + bogus = true; + } + } + + if (bogus) { + LOGE("Out-of-order field_ids\n"); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on method_id_item. */ +static void* swapMethodIdItem(const CheckState* state, void* ptr) { + DexMethodId* item = (DexMethodId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX2(item->classIdx, state->pHeader->typeIdsSize); + SWAP_INDEX2(item->protoIdx, state->pHeader->protoIdsSize); + SWAP_INDEX4(item->nameIdx, state->pHeader->stringIdsSize); + + return item + 1; +} + +/* Perform cross-item verification of method_id_item. */ +static void* crossVerifyMethodIdItem(const CheckState* state, void* ptr) { + const DexMethodId* item = (const DexMethodId*) ptr; + const char* s; + + s = dexStringByTypeIdx(state->pDexFile, item->classIdx); + if (!dexIsReferenceDescriptor(s)) { + LOGE("Invalid descriptor for class_idx: '%s'\n", s); + return NULL; + } + + s = dexStringById(state->pDexFile, item->nameIdx); + if (!dexIsValidMemberName(s)) { + LOGE("Invalid name: '%s'\n", s); + return NULL; + } + + const DexMethodId* item0 = (const DexMethodId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on the other sections being in order. + bool done = false; + bool bogus = false; + + if (item0->classIdx > item->classIdx) { + bogus = true; + done = true; + } else if (item0->classIdx < item->classIdx) { + done = true; + } + + if (!done) { + if (item0->nameIdx > item->nameIdx) { + bogus = true; + done = true; + } else if (item0->nameIdx < item->nameIdx) { + done = true; + } + } + + if (!done) { + if (item0->protoIdx >= item->protoIdx) { + bogus = true; + } + } + + if (bogus) { + LOGE("Out-of-order method_ids\n"); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on class_def_item. */ +static void* swapClassDefItem(const CheckState* state, void* ptr) { + DexClassDef* item = (DexClassDef*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX4(item->classIdx, state->pHeader->typeIdsSize); + SWAP_FIELD4(item->accessFlags); + SWAP_INDEX4_OR_NOINDEX(item->superclassIdx, state->pHeader->typeIdsSize); + SWAP_OFFSET4(item->interfacesOff); + SWAP_INDEX4_OR_NOINDEX(item->sourceFileIdx, state->pHeader->stringIdsSize); + SWAP_OFFSET4(item->annotationsOff); + SWAP_OFFSET4(item->classDataOff); + + return item + 1; +} + +/* defined below */ +static u4 findFirstClassDataDefiner(const CheckState* state, + DexClassData* classData); +static u4 findFirstAnnotationsDirectoryDefiner(const CheckState* state, + const DexAnnotationsDirectoryItem* dir); + +/* Helper for crossVerifyClassDefItem(), which checks a class_data_item to + * make sure all its references are to a given class. */ +static bool verifyClassDataIsForDef(const CheckState* state, u4 offset, + u4 definerIdx) { + if (offset == 0) { + return true; + } + + const u1* data = (const u1*) filePointer(state, offset); + DexClassData* classData = dexReadAndVerifyClassData(&data, NULL); + + if (classData == NULL) { + // Shouldn't happen, but bail here just in case. + return false; + } + + /* + * The class_data_item verification ensures that + * it consistently refers to the same definer, so all we need to + * do is check the first one. + */ + u4 dataDefiner = findFirstClassDataDefiner(state, classData); + bool result = (dataDefiner == definerIdx) || (dataDefiner == kDexNoIndex); + + free(classData); + return result; +} + +/* Helper for crossVerifyClassDefItem(), which checks an + * annotations_directory_item to make sure all its references are to a + * given class. */ +static bool verifyAnnotationsDirectoryIsForDef(const CheckState* state, + u4 offset, u4 definerIdx) { + if (offset == 0) { + return true; + } + + const DexAnnotationsDirectoryItem* dir = + (const DexAnnotationsDirectoryItem*) filePointer(state, offset); + u4 annoDefiner = findFirstAnnotationsDirectoryDefiner(state, dir); + + return (annoDefiner == definerIdx) || (annoDefiner == kDexNoIndex); +} + +/* Perform cross-item verification of class_def_item. */ +static void* crossVerifyClassDefItem(const CheckState* state, void* ptr) { + const DexClassDef* item = (const DexClassDef*) ptr; + u4 classIdx = item->classIdx; + const char* descriptor = dexStringByTypeIdx(state->pDexFile, classIdx); + + if (!dexIsClassDescriptor(descriptor)) { + LOGE("Invalid class: '%s'\n", descriptor); + return NULL; + } + + if (setDefinedClassBit(state, classIdx)) { + LOGE("Duplicate class definition: '%s'\n", descriptor); + return NULL; + } + + bool okay = + dexDataMapVerify0Ok(state->pDataMap, + item->interfacesOff, kDexTypeTypeList) + && dexDataMapVerify0Ok(state->pDataMap, + item->annotationsOff, kDexTypeAnnotationsDirectoryItem) + && dexDataMapVerify0Ok(state->pDataMap, + item->classDataOff, kDexTypeClassDataItem) + && dexDataMapVerify0Ok(state->pDataMap, + item->staticValuesOff, kDexTypeEncodedArrayItem); + + if (!okay) { + return NULL; + } + + if (item->superclassIdx != kDexNoIndex) { + descriptor = dexStringByTypeIdx(state->pDexFile, item->superclassIdx); + if (!dexIsClassDescriptor(descriptor)) { + LOGE("Invalid superclass: '%s'\n", descriptor); + return NULL; + } + } + + const DexTypeList* interfaces = + dexGetInterfacesList(state->pDexFile, item); + if (interfaces != NULL) { + u4 size = interfaces->size; + u4 i; + + /* + * Ensure that all interfaces refer to classes (not arrays or + * primitives). + */ + for (i = 0; i < size; i++) { + descriptor = dexStringByTypeIdx(state->pDexFile, + dexTypeListGetIdx(interfaces, i)); + if (!dexIsClassDescriptor(descriptor)) { + LOGE("Invalid interface: '%s'\n", descriptor); + return NULL; + } + } + + /* + * Ensure that there are no duplicates. This is an O(N^2) test, + * but in practice the number of interfaces implemented by any + * given class is low. I will buy a milkshake for the + * first person to show me a realistic case for which this test + * would be unacceptably slow. + */ + for (i = 1; i < size; i++) { + u4 idx1 = dexTypeListGetIdx(interfaces, i); + u4 j; + for (j = 0; j < i; j++) { + u4 idx2 = dexTypeListGetIdx(interfaces, j); + if (idx1 == idx2) { + LOGE("Duplicate interface: '%s'\n", + dexStringByTypeIdx(state->pDexFile, idx1)); + return NULL; + } + } + } + } + + if (!verifyClassDataIsForDef(state, item->classDataOff, item->classIdx)) { + LOGE("Invalid class_data_item\n"); + return NULL; + } + + if (!verifyAnnotationsDirectoryIsForDef(state, item->annotationsOff, + item->classIdx)) { + LOGE("Invalid annotations_directory_item\n"); + return NULL; + } + + return (void*) (item + 1); +} + +/* Helper for swapAnnotationsDirectoryItem(), which performs + * byte-swapping and intra-item verification on an + * annotation_directory_item's field elements. */ +static u1* swapFieldAnnotations(const CheckState* state, u4 count, u1* addr) { + DexFieldAnnotationsItem* item = (DexFieldAnnotationsItem*) addr; + bool first = true; + u4 lastIdx = 0; + + CHECK_LIST_SIZE(item, count, sizeof(DexFieldAnnotationsItem)); + + while (count--) { + SWAP_INDEX4(item->fieldIdx, state->pHeader->fieldIdsSize); + SWAP_OFFSET4(item->annotationsOff); + + if (first) { + first = false; + } else if (lastIdx >= item->fieldIdx) { + LOGE("Out-of-order field_idx: 0x%x then 0x%x\n", lastIdx, + item->fieldIdx); + return NULL; + } + + lastIdx = item->fieldIdx; + item++; + } + + return (u1*) item; +} + +/* Helper for swapAnnotationsDirectoryItem(), which performs + * byte-swapping and intra-item verification on an + * annotation_directory_item's method elements. */ +static u1* swapMethodAnnotations(const CheckState* state, u4 count, u1* addr) { + DexMethodAnnotationsItem* item = (DexMethodAnnotationsItem*) addr; + bool first = true; + u4 lastIdx = 0; + + CHECK_LIST_SIZE(item, count, sizeof(DexMethodAnnotationsItem)); + + while (count--) { + SWAP_INDEX4(item->methodIdx, state->pHeader->methodIdsSize); + SWAP_OFFSET4(item->annotationsOff); + + if (first) { + first = false; + } else if (lastIdx >= item->methodIdx) { + LOGE("Out-of-order method_idx: 0x%x then 0x%x\n", lastIdx, + item->methodIdx); + return NULL; + } + + lastIdx = item->methodIdx; + item++; + } + + return (u1*) item; +} + +/* Helper for swapAnnotationsDirectoryItem(), which performs + * byte-swapping and intra-item verification on an + * annotation_directory_item's parameter elements. */ +static u1* swapParameterAnnotations(const CheckState* state, u4 count, + u1* addr) { + DexParameterAnnotationsItem* item = (DexParameterAnnotationsItem*) addr; + bool first = true; + u4 lastIdx = 0; + + CHECK_LIST_SIZE(item, count, sizeof(DexParameterAnnotationsItem)); + + while (count--) { + SWAP_INDEX4(item->methodIdx, state->pHeader->methodIdsSize); + SWAP_OFFSET4(item->annotationsOff); + + if (first) { + first = false; + } else if (lastIdx >= item->methodIdx) { + LOGE("Out-of-order method_idx: 0x%x then 0x%x\n", lastIdx, + item->methodIdx); + return NULL; + } + + lastIdx = item->methodIdx; + item++; + } + + return (u1*) item; +} + +/* Perform byte-swapping and intra-item verification on + * annotations_directory_item. */ +static void* swapAnnotationsDirectoryItem(const CheckState* state, void* ptr) { + DexAnnotationsDirectoryItem* item = (DexAnnotationsDirectoryItem*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_OFFSET4(item->classAnnotationsOff); + SWAP_FIELD4(item->fieldsSize); + SWAP_FIELD4(item->methodsSize); + SWAP_FIELD4(item->parametersSize); + + u1* addr = (u1*) (item + 1); + + if (item->fieldsSize != 0) { + addr = swapFieldAnnotations(state, item->fieldsSize, addr); + if (addr == NULL) { + return NULL; + } + } + + if (item->methodsSize != 0) { + addr = swapMethodAnnotations(state, item->methodsSize, addr); + if (addr == NULL) { + return NULL; + } + } + + if (item->parametersSize != 0) { + addr = swapParameterAnnotations(state, item->parametersSize, addr); + if (addr == NULL) { + return NULL; + } + } + + return addr; +} + +/* Helper for crossVerifyAnnotationsDirectoryItem(), which checks the + * field elements. */ +static const u1* crossVerifyFieldAnnotations(const CheckState* state, u4 count, + const u1* addr, u4 definingClass) { + const DexFieldAnnotationsItem* item = (DexFieldAnnotationsItem*) addr; + + while (count--) { + if (!verifyFieldDefiner(state, definingClass, item->fieldIdx)) { + return NULL; + } + if (!dexDataMapVerify(state->pDataMap, item->annotationsOff, + kDexTypeAnnotationSetItem)) { + return NULL; + } + item++; + } + + return (const u1*) item; +} + +/* Helper for crossVerifyAnnotationsDirectoryItem(), which checks the + * method elements. */ +static const u1* crossVerifyMethodAnnotations(const CheckState* state, + u4 count, const u1* addr, u4 definingClass) { + const DexMethodAnnotationsItem* item = (DexMethodAnnotationsItem*) addr; + + while (count--) { + if (!verifyMethodDefiner(state, definingClass, item->methodIdx)) { + return NULL; + } + if (!dexDataMapVerify(state->pDataMap, item->annotationsOff, + kDexTypeAnnotationSetItem)) { + return NULL; + } + item++; + } + + return (const u1*) item; +} + +/* Helper for crossVerifyAnnotationsDirectoryItem(), which checks the + * parameter elements. */ +static const u1* crossVerifyParameterAnnotations(const CheckState* state, + u4 count, const u1* addr, u4 definingClass) { + const DexParameterAnnotationsItem* item = + (DexParameterAnnotationsItem*) addr; + + while (count--) { + if (!verifyMethodDefiner(state, definingClass, item->methodIdx)) { + return NULL; + } + if (!dexDataMapVerify(state->pDataMap, item->annotationsOff, + kDexTypeAnnotationSetRefList)) { + return NULL; + } + item++; + } + + return (const u1*) item; +} + +/* Helper for crossVerifyClassDefItem() and + * crossVerifyAnnotationsDirectoryItem(), which finds the type_idx of + * the definer of the first item in the data. */ +static u4 findFirstAnnotationsDirectoryDefiner(const CheckState* state, + const DexAnnotationsDirectoryItem* dir) { + if (dir->fieldsSize != 0) { + const DexFieldAnnotationsItem* fields = + dexGetFieldAnnotations(state->pDexFile, dir); + const DexFieldId* field = + dexGetFieldId(state->pDexFile, fields[0].fieldIdx); + return field->classIdx; + } + + if (dir->methodsSize != 0) { + const DexMethodAnnotationsItem* methods = + dexGetMethodAnnotations(state->pDexFile, dir); + const DexMethodId* method = + dexGetMethodId(state->pDexFile, methods[0].methodIdx); + return method->classIdx; + } + + if (dir->parametersSize != 0) { + const DexParameterAnnotationsItem* parameters = + dexGetParameterAnnotations(state->pDexFile, dir); + const DexMethodId* method = + dexGetMethodId(state->pDexFile, parameters[0].methodIdx); + return method->classIdx; + } + + return kDexNoIndex; +} + +/* Perform cross-item verification of annotations_directory_item. */ +static void* crossVerifyAnnotationsDirectoryItem(const CheckState* state, + void* ptr) { + const DexAnnotationsDirectoryItem* item = (const DexAnnotationsDirectoryItem*) ptr; + u4 definingClass = findFirstAnnotationsDirectoryDefiner(state, item); + + if (!dexDataMapVerify0Ok(state->pDataMap, + item->classAnnotationsOff, kDexTypeAnnotationSetItem)) { + return NULL; + } + + const u1* addr = (const u1*) (item + 1); + + if (item->fieldsSize != 0) { + addr = crossVerifyFieldAnnotations(state, item->fieldsSize, addr, + definingClass); + if (addr == NULL) { + return NULL; + } + } + + if (item->methodsSize != 0) { + addr = crossVerifyMethodAnnotations(state, item->methodsSize, addr, + definingClass); + if (addr == NULL) { + return NULL; + } + } + + if (item->parametersSize != 0) { + addr = crossVerifyParameterAnnotations(state, item->parametersSize, + addr, definingClass); + if (addr == NULL) { + return NULL; + } + } + + return (void*) addr; +} + +/* Perform byte-swapping and intra-item verification on type_list. */ +static void* swapTypeList(const CheckState* state, void* ptr) +{ + DexTypeList* pTypeList = (DexTypeList*) ptr; + DexTypeItem* pType; + u4 count; + + CHECK_PTR_RANGE(pTypeList, pTypeList + 1); + SWAP_FIELD4(pTypeList->size); + count = pTypeList->size; + pType = pTypeList->list; + CHECK_LIST_SIZE(pType, count, sizeof(DexTypeItem)); + + while (count--) { + SWAP_INDEX2(pType->typeIdx, state->pHeader->typeIdsSize); + pType++; + } + + return pType; +} + +/* Perform byte-swapping and intra-item verification on + * annotation_set_ref_list. */ +static void* swapAnnotationSetRefList(const CheckState* state, void* ptr) { + DexAnnotationSetRefList* list = (DexAnnotationSetRefList*) ptr; + DexAnnotationSetRefItem* item; + u4 count; + + CHECK_PTR_RANGE(list, list + 1); + SWAP_FIELD4(list->size); + count = list->size; + item = list->list; + CHECK_LIST_SIZE(item, count, sizeof(DexAnnotationSetRefItem)); + + while (count--) { + SWAP_OFFSET4(item->annotationsOff); + item++; + } + + return item; +} + +/* Perform cross-item verification of annotation_set_ref_list. */ +static void* crossVerifyAnnotationSetRefList(const CheckState* state, + void* ptr) { + const DexAnnotationSetRefList* list = (const DexAnnotationSetRefList*) ptr; + const DexAnnotationSetRefItem* item = list->list; + int count = list->size; + + while (count--) { + if (!dexDataMapVerify0Ok(state->pDataMap, + item->annotationsOff, kDexTypeAnnotationSetItem)) { + return NULL; + } + item++; + } + + return (void*) item; +} + +/* Perform byte-swapping and intra-item verification on + * annotation_set_item. */ +static void* swapAnnotationSetItem(const CheckState* state, void* ptr) { + DexAnnotationSetItem* set = (DexAnnotationSetItem*) ptr; + u4* item; + u4 count; + + CHECK_PTR_RANGE(set, set + 1); + SWAP_FIELD4(set->size); + count = set->size; + item = set->entries; + CHECK_LIST_SIZE(item, count, sizeof(u4)); + + while (count--) { + SWAP_OFFSET4(*item); + item++; + } + + return item; +} + +/* Helper for crossVerifyAnnotationSetItem(), which extracts the type_idx + * out of an annotation_item. */ +static u4 annotationItemTypeIdx(const DexAnnotationItem* item) { + const u1* data = item->annotation; + return readUnsignedLeb128(&data); +} + +/* Perform cross-item verification of annotation_set_item. */ +static void* crossVerifyAnnotationSetItem(const CheckState* state, void* ptr) { + const DexAnnotationSetItem* set = (const DexAnnotationSetItem*) ptr; + int count = set->size; + u4 lastIdx = 0; + bool first = true; + int i; + + for (i = 0; i < count; i++) { + if (!dexDataMapVerify0Ok(state->pDataMap, + dexGetAnnotationOff(set, i), kDexTypeAnnotationItem)) { + return NULL; + } + + const DexAnnotationItem* annotation = + dexGetAnnotationItem(state->pDexFile, set, i); + u4 idx = annotationItemTypeIdx(annotation); + + if (first) { + first = false; + } else if (lastIdx >= idx) { + LOGE("Out-of-order entry types: 0x%x then 0x%x\n", + lastIdx, idx); + return NULL; + } + + lastIdx = idx; + } + + return (void*) (set->entries + count); +} + +/* Helper for verifyClassDataItem(), which checks a list of fields. */ +static bool verifyFields(const CheckState* state, u4 size, + DexField* fields, bool expectStatic) { + u4 i; + + for (i = 0; i < size; i++) { + DexField* field = &fields[i]; + u4 accessFlags = field->accessFlags; + bool isStatic = (accessFlags & ACC_STATIC) != 0; + + CHECK_INDEX(field->fieldIdx, state->pHeader->fieldIdsSize); + + if (isStatic != expectStatic) { + LOGE("Field in wrong list @ %d\n", i); + return false; + } + + if ((accessFlags & ~ACC_FIELD_MASK) != 0) { + LOGE("Bogus field access flags %x @ %d\n", accessFlags, i); + return false; + } + } + + return true; +} + +/* Helper for verifyClassDataItem(), which checks a list of methods. */ +static bool verifyMethods(const CheckState* state, u4 size, + DexMethod* methods, bool expectDirect) { + u4 i; + + for (i = 0; i < size; i++) { + DexMethod* method = &methods[i]; + + CHECK_INDEX(method->methodIdx, state->pHeader->methodIdsSize); + + u4 accessFlags = method->accessFlags; + bool isDirect = + (accessFlags & (ACC_STATIC | ACC_PRIVATE | ACC_CONSTRUCTOR)) != 0; + bool expectCode = (accessFlags & (ACC_NATIVE | ACC_ABSTRACT)) == 0; + bool isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0; + bool allowSynchronized = (accessFlags & ACC_NATIVE) != 0; + + if (isDirect != expectDirect) { + LOGE("Method in wrong list @ %d\n", i); + return false; + } + + if (((accessFlags & ~ACC_METHOD_MASK) != 0) + || (isSynchronized && !allowSynchronized)) { + LOGE("Bogus method access flags %x @ %d\n", accessFlags, i); + return false; + } + + if (expectCode) { + if (method->codeOff == 0) { + LOGE("Unexpected zero code_off for access_flags %x\n", + accessFlags); + return false; + } + } else if (method->codeOff != 0) { + LOGE("Unexpected non-zero code_off 0x%x for access_flags %x\n", + method->codeOff, accessFlags); + return false; + } + } + + return true; +} + +/* Helper for verifyClassDataItem(), which does most of the work. */ +static bool verifyClassDataItem0(const CheckState* state, + DexClassData* classData) { + bool okay; + + okay = verifyFields(state, classData->header.staticFieldsSize, + classData->staticFields, true); + + if (!okay) { + LOGE("Trouble with static fields\n"); + return false; + } + + verifyFields(state, classData->header.instanceFieldsSize, + classData->instanceFields, false); + + if (!okay) { + LOGE("Trouble with instance fields\n"); + return false; + } + + okay = verifyMethods(state, classData->header.directMethodsSize, + classData->directMethods, true); + + if (!okay) { + LOGE("Trouble with direct methods\n"); + return false; + } + + okay = verifyMethods(state, classData->header.virtualMethodsSize, + classData->virtualMethods, false); + + if (!okay) { + LOGE("Trouble with virtual methods\n"); + return false; + } + + return true; +} + +/* Perform intra-item verification on class_data_item. */ +static void* intraVerifyClassDataItem(const CheckState* state, void* ptr) { + const u1* data = (const u1*) ptr; + DexClassData* classData = dexReadAndVerifyClassData(&data, state->fileEnd); + + if (classData == NULL) { + LOGE("Unable to parse class_data_item\n"); + return NULL; + } + + bool okay = verifyClassDataItem0(state, classData); + + free(classData); + + if (!okay) { + return NULL; + } + + return (void*) data; +} + +/* Helper for crossVerifyClassDefItem() and + * crossVerifyClassDataItem(), which finds the type_idx of the definer + * of the first item in the data. */ +static u4 findFirstClassDataDefiner(const CheckState* state, + DexClassData* classData) { + if (classData->header.staticFieldsSize != 0) { + u4 fieldIdx = classData->staticFields[0].fieldIdx; + const DexFieldId* field = dexGetFieldId(state->pDexFile, fieldIdx); + return field->classIdx; + } + + if (classData->header.instanceFieldsSize != 0) { + u4 fieldIdx = classData->instanceFields[0].fieldIdx; + const DexFieldId* field = dexGetFieldId(state->pDexFile, fieldIdx); + return field->classIdx; + } + + if (classData->header.directMethodsSize != 0) { + u4 methodIdx = classData->directMethods[0].methodIdx; + const DexMethodId* meth = dexGetMethodId(state->pDexFile, methodIdx); + return meth->classIdx; + } + + if (classData->header.virtualMethodsSize != 0) { + u4 methodIdx = classData->virtualMethods[0].methodIdx; + const DexMethodId* meth = dexGetMethodId(state->pDexFile, methodIdx); + return meth->classIdx; + } + + return kDexNoIndex; +} + +/* Perform cross-item verification of class_data_item. */ +static void* crossVerifyClassDataItem(const CheckState* state, void* ptr) { + const u1* data = (const u1*) ptr; + DexClassData* classData = dexReadAndVerifyClassData(&data, state->fileEnd); + u4 definingClass = findFirstClassDataDefiner(state, classData); + bool okay = true; + u4 i; + + for (i = classData->header.staticFieldsSize; okay && (i > 0); /*i*/) { + i--; + const DexField* field = &classData->staticFields[i]; + okay = verifyFieldDefiner(state, definingClass, field->fieldIdx); + } + + for (i = classData->header.instanceFieldsSize; okay && (i > 0); /*i*/) { + i--; + const DexField* field = &classData->instanceFields[i]; + okay = verifyFieldDefiner(state, definingClass, field->fieldIdx); + } + + for (i = classData->header.directMethodsSize; okay && (i > 0); /*i*/) { + i--; + const DexMethod* meth = &classData->directMethods[i]; + okay = dexDataMapVerify0Ok(state->pDataMap, meth->codeOff, + kDexTypeCodeItem) + && verifyMethodDefiner(state, definingClass, meth->methodIdx); + } + + for (i = classData->header.virtualMethodsSize; okay && (i > 0); /*i*/) { + i--; + const DexMethod* meth = &classData->virtualMethods[i]; + okay = dexDataMapVerify0Ok(state->pDataMap, meth->codeOff, + kDexTypeCodeItem) + && verifyMethodDefiner(state, definingClass, meth->methodIdx); + } + + free(classData); + + if (!okay) { + return NULL; + } + + return (void*) data; +} + +/* Helper for swapCodeItem(), which fills an array with all the valid + * handlerOff values for catch handlers and also verifies the handler + * contents. */ +static u4 setHandlerOffsAndVerify(const CheckState* state, + DexCode* code, u4 firstOffset, u4 handlersSize, u4* handlerOffs) { + const u1* fileEnd = state->fileEnd; + const u1* handlersBase = dexGetCatchHandlerData(code); + u4 offset = firstOffset; + bool okay = true; + u4 i; + + for (i = 0; i < handlersSize; i++) { + const u1* ptr = handlersBase + offset; + int size = readAndVerifySignedLeb128(&ptr, fileEnd, &okay); + bool catchAll; + + if (!okay) { + LOGE("Bogus size\n"); + return 0; + } + + if ((size < -65536) || (size > 65536)) { + LOGE("Invalid size: %d\n", size); + return 0; + } + + if (size <= 0) { + catchAll = true; + size = -size; + } else { + catchAll = false; + } + + handlerOffs[i] = offset; + + while (size-- > 0) { + u4 typeIdx = + readAndVerifyUnsignedLeb128(&ptr, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus type_idx"); + return 0; + } + + CHECK_INDEX(typeIdx, state->pHeader->typeIdsSize); + + u4 addr = readAndVerifyUnsignedLeb128(&ptr, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus addr"); + return 0; + } + + if (addr >= code->insnsSize) { + LOGE("Invalid addr: 0x%x", addr); + return 0; + } + } + + if (catchAll) { + u4 addr = readAndVerifyUnsignedLeb128(&ptr, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus catch_all_addr"); + return 0; + } + + if (addr >= code->insnsSize) { + LOGE("Invalid catch_all_addr: 0x%x", addr); + return 0; + } + } + + offset = ptr - handlersBase; + } + + return offset; +} + +/* Helper for swapCodeItem(), which does all the try-catch related + * swapping and verification. */ +static void* swapTriesAndCatches(const CheckState* state, DexCode* code) { + const u1* encodedHandlers = dexGetCatchHandlerData(code); + const u1* encodedPtr = encodedHandlers; + bool okay = true; + u4 handlersSize = + readAndVerifyUnsignedLeb128(&encodedPtr, state->fileEnd, &okay); + + if (!okay) { + LOGE("Bogus handlers_size\n"); + return NULL; + } + + if ((handlersSize == 0) || (handlersSize >= 65536)) { + LOGE("Invalid handlers_size: %d\n", handlersSize); + return NULL; + } + + u4 handlerOffs[handlersSize]; // list of valid handlerOff values + u4 endOffset = setHandlerOffsAndVerify(state, code, + encodedPtr - encodedHandlers, + handlersSize, handlerOffs); + + if (endOffset == 0) { + return NULL; + } + + DexTry* tries = (DexTry*) dexGetTries(code); + u4 count = code->triesSize; + u4 lastEnd = 0; + + CHECK_LIST_SIZE(tries, count, sizeof(DexTry)); + + while (count--) { + u4 i; + + SWAP_FIELD4(tries->startAddr); + SWAP_FIELD2(tries->insnCount); + SWAP_FIELD2(tries->handlerOff); + + if (tries->startAddr < lastEnd) { + LOGE("Out-of-order try\n"); + return NULL; + } + + if (tries->startAddr >= code->insnsSize) { + LOGE("Invalid start_addr: 0x%x\n", tries->startAddr); + return NULL; + } + + for (i = 0; i < handlersSize; i++) { + if (tries->handlerOff == handlerOffs[i]) { + break; + } + } + + if (i == handlersSize) { + LOGE("Bogus handler offset: 0x%x\n", tries->handlerOff); + return NULL; + } + + lastEnd = tries->startAddr + tries->insnCount; + + if (lastEnd > code->insnsSize) { + LOGE("Invalid insn_count: 0x%x (end addr 0x%x)\n", + tries->insnCount, lastEnd); + return NULL; + } + + tries++; + } + + return (u1*) encodedHandlers + endOffset; +} + +/* Perform byte-swapping and intra-item verification on code_item. */ +static void* swapCodeItem(const CheckState* state, void* ptr) { + DexCode* item = (DexCode*) ptr; + u2* insns; + u4 count; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_FIELD2(item->registersSize); + SWAP_FIELD2(item->insSize); + SWAP_FIELD2(item->outsSize); + SWAP_FIELD2(item->triesSize); + SWAP_OFFSET4(item->debugInfoOff); + SWAP_FIELD4(item->insnsSize); + + if (item->insSize > item->registersSize) { + LOGE("insSize (%u) > registersSize (%u)\n", item->insSize, + item->registersSize); + return NULL; + } + + if ((item->outsSize > 5) && (item->outsSize > item->registersSize)) { + /* + * It's okay for outsSize to be up to five, even if registersSize + * is smaller, since the short forms of method invocation allow + * repetition of a register multiple times within a single parameter + * list. Longer parameter lists, though, need to be represented + * in-order in the register file. + */ + LOGE("outsSize (%u) > registersSize (%u)\n", item->outsSize, + item->registersSize); + return NULL; + } + + count = item->insnsSize; + insns = item->insns; + CHECK_LIST_SIZE(insns, count, sizeof(u2)); + + while (count--) { + *insns = SWAP2(*insns); + insns++; + } + + if (item->triesSize == 0) { + ptr = insns; + } else { + if ((((u4) insns) & 3) != 0) { + // Four-byte alignment for the tries. Verify the spacer is a 0. + if (*insns != 0) { + LOGE("Non-zero padding: 0x%x\n", (u4) *insns); + return NULL; + } + } + + ptr = swapTriesAndCatches(state, item); + } + + return ptr; +} + +/* Perform intra-item verification on string_data_item. */ +static void* intraVerifyStringDataItem(const CheckState* state, void* ptr) { + const u1* fileEnd = state->fileEnd; + const u1* data = (const u1*) ptr; + bool okay = true; + u4 utf16Size = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + u4 i; + + if (!okay) { + LOGE("Bogus utf16_size\n"); + return NULL; + } + + for (i = 0; i < utf16Size; i++) { + if (data >= fileEnd) { + LOGE("String data would go beyond end-of-file\n"); + return NULL; + } + + u1 byte1 = *(data++); + + // Switch on the high four bits. + switch (byte1 >> 4) { + case 0x00: { + // Special case of bit pattern 0xxx. + if (byte1 == 0) { + LOGE("String shorter than indicated utf16_size 0x%x\n", + utf16Size); + return NULL; + } + break; + } + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: { + // Bit pattern 0xxx. No need for any extra bytes or checks. + break; + } + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0f: { + /* + * Bit pattern 10xx or 1111, which are illegal start bytes. + * Note: 1111 is valid for normal UTF-8, but not the + * modified UTF-8 used here. + */ + LOGE("Illegal start byte 0x%x\n", byte1); + return NULL; + } + case 0x0e: { + // Bit pattern 1110, so there are two additional bytes. + u1 byte2 = *(data++); + if ((byte2 & 0xc0) != 0x80) { + LOGE("Illegal continuation byte 0x%x\n", byte2); + return NULL; + } + u1 byte3 = *(data++); + if ((byte3 & 0xc0) != 0x80) { + LOGE("Illegal continuation byte 0x%x\n", byte3); + return NULL; + } + u2 value = ((byte1 & 0x0f) << 12) | ((byte2 & 0x3f) << 6) + | (byte3 & 0x3f); + if (value < 0x800) { + LOGE("Illegal representation for value %x\n", value); + return NULL; + } + break; + } + case 0x0c: + case 0x0d: { + // Bit pattern 110x, so there is one additional byte. + u1 byte2 = *(data++); + if ((byte2 & 0xc0) != 0x80) { + LOGE("Illegal continuation byte 0x%x\n", byte2); + return NULL; + } + u2 value = ((byte1 & 0x1f) << 6) | (byte2 & 0x3f); + if ((value != 0) && (value < 0x80)) { + LOGE("Illegal representation for value %x\n", value); + return NULL; + } + break; + } + } + } + + if (*(data++) != '\0') { + LOGE("String longer than indicated utf16_size 0x%x\n", utf16Size); + return NULL; + } + + return (void*) data; +} + +/* Perform intra-item verification on debug_info_item. */ +static void* intraVerifyDebugInfoItem(const CheckState* state, void* ptr) { + const u1* fileEnd = state->fileEnd; + const u1* data = (const u1*) ptr; + bool okay = true; + u4 i; + + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus line_start\n"); + return NULL; + } + + u4 parametersSize = + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus parameters_size\n"); + return NULL; + } + + if (parametersSize > 65536) { + LOGE("Invalid parameters_size: 0x%x\n", parametersSize); + return NULL; + } + + for (i = 0; i < parametersSize; i++) { + u4 parameterName = + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus parameter_name\n"); + return NULL; + } + + if (parameterName != 0) { + parameterName--; + CHECK_INDEX(parameterName, state->pHeader->stringIdsSize); + } + } + + bool done = false; + while (!done) { + u1 opcode = *(data++); + + switch (opcode) { + case DBG_END_SEQUENCE: { + done = true; + break; + } + case DBG_ADVANCE_PC: { + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + break; + } + case DBG_ADVANCE_LINE: { + readAndVerifySignedLeb128(&data, fileEnd, &okay); + break; + } + case DBG_START_LOCAL: { + u4 idx; + u4 regNum = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (regNum >= 65536) { + okay = false; + break; + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + break; + } + case DBG_END_LOCAL: + case DBG_RESTART_LOCAL: { + u4 regNum = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (regNum >= 65536) { + okay = false; + break; + } + break; + } + case DBG_START_LOCAL_EXTENDED: { + u4 idx; + u4 regNum = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (regNum >= 65536) { + okay = false; + break; + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + break; + } + case DBG_SET_FILE: { + u4 idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + break; + } + default: { + // No arguments to parse for anything else. + } + } + + if (!okay) { + LOGE("Bogus syntax for opcode %02x\n", opcode); + return NULL; + } + } + + return (void*) data; +} + +/* defined below */ +static const u1* verifyEncodedValue(const CheckState* state, const u1* data, + bool crossVerify); +static const u1* verifyEncodedAnnotation(const CheckState* state, + const u1* data, bool crossVerify); + +/* Helper for verifyEncodedValue(), which reads a 1- to 4- byte unsigned + * little endian value. */ +static u4 readUnsignedLittleEndian(const CheckState* state, const u1** pData, + u4 size) { + const u1* data = *pData; + u4 result = 0; + u4 i; + + CHECK_PTR_RANGE(data, data + size); + + for (i = 0; i < size; i++) { + result |= ((u4) *(data++)) << (i * 8); + } + + *pData = data; + return result; +} + +/* Helper for *VerifyAnnotationItem() and *VerifyEncodedArrayItem(), which + * verifies an encoded_array. */ +static const u1* verifyEncodedArray(const CheckState* state, + const u1* data, bool crossVerify) { + bool okay = true; + u4 size = readAndVerifyUnsignedLeb128(&data, state->fileEnd, &okay); + + if (!okay) { + LOGE("Bogus encoded_array size\n"); + return NULL; + } + + while (size--) { + data = verifyEncodedValue(state, data, crossVerify); + if (data == NULL) { + LOGE("Bogus encoded_array value\n"); + return NULL; + } + } + + return data; +} + +/* Helper for *VerifyAnnotationItem() and *VerifyEncodedArrayItem(), which + * verifies an encoded_value. */ +static const u1* verifyEncodedValue(const CheckState* state, + const u1* data, bool crossVerify) { + CHECK_PTR_RANGE(data, data + 1); + + u1 headerByte = *(data++); + u4 valueType = headerByte & kDexAnnotationValueTypeMask; + u4 valueArg = headerByte >> kDexAnnotationValueArgShift; + + switch (valueType) { + case kDexAnnotationByte: { + if (valueArg != 0) { + LOGE("Bogus byte size 0x%x\n", valueArg); + return NULL; + } + data++; + break; + } + case kDexAnnotationShort: + case kDexAnnotationChar: { + if (valueArg > 1) { + LOGE("Bogus char/short size 0x%x\n", valueArg); + return NULL; + } + data += valueArg + 1; + break; + } + case kDexAnnotationInt: + case kDexAnnotationFloat: { + if (valueArg > 3) { + LOGE("Bogus int/float size 0x%x\n", valueArg); + return NULL; + } + data += valueArg + 1; + break; + } + case kDexAnnotationLong: + case kDexAnnotationDouble: { + data += valueArg + 1; + break; + } + case kDexAnnotationString: { + if (valueArg > 3) { + LOGE("Bogus string size 0x%x\n", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + break; + } + case kDexAnnotationType: { + if (valueArg > 3) { + LOGE("Bogus type size 0x%x\n", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->typeIdsSize); + break; + } + case kDexAnnotationField: + case kDexAnnotationEnum: { + if (valueArg > 3) { + LOGE("Bogus field/enum size 0x%x\n", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->fieldIdsSize); + break; + } + case kDexAnnotationMethod: { + if (valueArg > 3) { + LOGE("Bogus method size 0x%x\n", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->methodIdsSize); + break; + } + case kDexAnnotationArray: { + if (valueArg != 0) { + LOGE("Bogus array value_arg 0x%x\n", valueArg); + return NULL; + } + data = verifyEncodedArray(state, data, crossVerify); + break; + } + case kDexAnnotationAnnotation: { + if (valueArg != 0) { + LOGE("Bogus annotation value_arg 0x%x\n", valueArg); + return NULL; + } + data = verifyEncodedAnnotation(state, data, crossVerify); + break; + } + case kDexAnnotationNull: { + if (valueArg != 0) { + LOGE("Bogus null value_arg 0x%x\n", valueArg); + return NULL; + } + // Nothing else to do for this type. + break; + } + case kDexAnnotationBoolean: { + if (valueArg > 1) { + LOGE("Bogus boolean value_arg 0x%x\n", valueArg); + return NULL; + } + // Nothing else to do for this type. + break; + } + default: { + LOGE("Bogus value_type 0x%x\n", valueType); + return NULL; + } + } + + return data; +} + +/* Helper for *VerifyAnnotationItem() and *VerifyEncodedArrayItem(), which + * verifies an encoded_annotation. */ +static const u1* verifyEncodedAnnotation(const CheckState* state, + const u1* data, bool crossVerify) { + const u1* fileEnd = state->fileEnd; + bool okay = true; + u4 idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus encoded_annotation type_idx\n"); + return NULL; + } + + CHECK_INDEX(idx, state->pHeader->typeIdsSize); + + if (crossVerify) { + const char* descriptor = dexStringByTypeIdx(state->pDexFile, idx); + if (!dexIsClassDescriptor(descriptor)) { + LOGE("Bogus annotation type: '%s'\n", descriptor); + return NULL; + } + } + + u4 size = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + u4 lastIdx = 0; + bool first = true; + + if (!okay) { + LOGE("Bogus encoded_annotation size\n"); + return NULL; + } + + while (size--) { + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + LOGE("Bogus encoded_annotation name_idx\n"); + return NULL; + } + + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + + if (crossVerify) { + const char* name = dexStringById(state->pDexFile, idx); + if (!dexIsValidMemberName(name)) { + LOGE("Bogus annotation member name: '%s'\n", name); + return NULL; + } + } + + if (first) { + first = false; + } else if (lastIdx >= idx) { + LOGE("Out-of-order encoded_annotation name_idx: 0x%x then 0x%x\n", + lastIdx, idx); + return NULL; + } + + data = verifyEncodedValue(state, data, crossVerify); + lastIdx = idx; + + if (data == NULL) { + return NULL; + } + } + + return data; +} + +/* Perform intra-item verification on encoded_array_item. */ +static void* intraVerifyEncodedArrayItem(const CheckState* state, void* ptr) { + return (void*) verifyEncodedArray(state, (const u1*) ptr, false); +} + +/* Perform intra-item verification on annotation_item. */ +static void* intraVerifyAnnotationItem(const CheckState* state, void* ptr) { + const u1* data = (const u1*) ptr; + + CHECK_PTR_RANGE(data, data + 1); + + switch (*(data++)) { + case kDexVisibilityBuild: + case kDexVisibilityRuntime: + case kDexVisibilitySystem: { + break; + } + default: { + LOGE("Bogus annotation visibility: 0x%x\n", *data); + return NULL; + } + } + + return (void*) verifyEncodedAnnotation(state, data, false); +} + +/* Perform cross-item verification on annotation_item. */ +static void* crossVerifyAnnotationItem(const CheckState* state, void* ptr) { + const u1* data = (const u1*) ptr; + + // Skip the visibility byte. + data++; + + return (void*) verifyEncodedAnnotation(state, data, true); +} + + + + +/* + * Function to visit an individual top-level item type. + */ +typedef void* ItemVisitorFunction(const CheckState* state, void* ptr); + +/* + * Iterate over all the items in a section, optionally updating the + * data map (done if mapType is passed as non-negative). The section + * must consist of concatenated items of the same type. + */ +static bool iterateSectionWithOptionalUpdate(CheckState* state, + u4 offset, u4 count, ItemVisitorFunction* func, u4 alignment, + u4* nextOffset, int mapType) { + u4 alignmentMask = alignment - 1; + u4 i; + + state->previousItem = NULL; + + for (i = 0; i < count; i++) { + u4 newOffset = (offset + alignmentMask) & ~alignmentMask; + u1* ptr = (u1*) filePointer(state, newOffset); + + if (offset < newOffset) { + ptr = (u1*) filePointer(state, offset); + if (offset < newOffset) { + CHECK_OFFSET_RANGE(offset, newOffset); + while (offset < newOffset) { + if (*ptr != '\0') { + LOGE("Non-zero padding 0x%02x @ %x\n", *ptr, offset); + return false; + } + ptr++; + offset++; + } + } + } + + u1* newPtr = (u1*) func(state, ptr); + newOffset = fileOffset(state, newPtr); + + if (newPtr == NULL) { + LOGE("Trouble with item %d @ offset 0x%x\n", i, offset); + return false; + } + + if (newOffset > state->fileLen) { + LOGE("Item %d @ offset 0x%x ends out of bounds\n", i, offset); + return false; + } + + if (mapType >= 0) { + dexDataMapAdd(state->pDataMap, offset, mapType); + } + + state->previousItem = ptr; + offset = newOffset; + } + + if (nextOffset != NULL) { + *nextOffset = offset; + } + + return true; +} + +/* + * Iterate over all the items in a section. The section must consist of + * concatenated items of the same type. This variant will not update the data + * map. + */ +static bool iterateSection(CheckState* state, u4 offset, u4 count, + ItemVisitorFunction* func, u4 alignment, u4* nextOffset) { + return iterateSectionWithOptionalUpdate(state, offset, count, func, + alignment, nextOffset, -1); +} + +/* + * Like iterateSection(), but also check that the offset and count match + * a given pair of expected values. + */ +static bool checkBoundsAndIterateSection(CheckState* state, + u4 offset, u4 count, u4 expectedOffset, u4 expectedCount, + ItemVisitorFunction* func, u4 alignment, u4* nextOffset) { + if (offset != expectedOffset) { + LOGE("Bogus offset for section: got 0x%x; expected 0x%x\n", + offset, expectedOffset); + return false; + } + + if (count != expectedCount) { + LOGE("Bogus size for section: got 0x%x; expected 0x%x\n", + count, expectedCount); + return false; + } + + return iterateSection(state, offset, count, func, alignment, nextOffset); +} + +/* + * Like iterateSection(), but also update the data section map and + * check that all the items fall within the data section. + */ +static bool iterateDataSection(CheckState* state, u4 offset, u4 count, + ItemVisitorFunction* func, u4 alignment, u4* nextOffset, int mapType) { + u4 dataStart = state->pHeader->dataOff; + u4 dataEnd = dataStart + state->pHeader->dataSize; + + assert(nextOffset != NULL); + + if ((offset < dataStart) || (offset >= dataEnd)) { + LOGE("Bogus offset for data subsection: 0x%x\n", offset); + return false; + } + + if (!iterateSectionWithOptionalUpdate(state, offset, count, func, + alignment, nextOffset, mapType)) { + return false; + } + + if (*nextOffset > dataEnd) { + LOGE("Out-of-bounds end of data subsection: 0x%x\n", *nextOffset); + return false; + } + + return true; +} + +/* + * Byte-swap all items in the given map except the header and the map + * itself, both of which should have already gotten swapped. This also + * does all possible intra-item verification, that is, verification + * that doesn't need to assume the sanctity of the contents of *other* + * items. The intra-item limitation is because at the time an item is + * asked to verify itself, it can't assume that the items it refers to + * have been byte-swapped and verified. + */ +static bool swapEverythingButHeaderAndMap(CheckState* state, + DexMapList* pMap) { + const DexMapItem* item = pMap->list; + u4 lastOffset = 0; + u4 count = pMap->size; + bool okay = true; + + while (okay && count--) { + u4 sectionOffset = item->offset; + u4 sectionCount = item->size; + u2 type = item->type; + + if (lastOffset < sectionOffset) { + CHECK_OFFSET_RANGE(lastOffset, sectionOffset); + const u1* ptr = (const u1*) filePointer(state, lastOffset); + while (lastOffset < sectionOffset) { + if (*ptr != '\0') { + LOGE("Non-zero padding 0x%02x before section start @ %x\n", + *ptr, lastOffset); + okay = false; + break; + } + ptr++; + lastOffset++; + } + } else if (lastOffset > sectionOffset) { + LOGE("Section overlap or out-of-order map: %x, %x\n", + lastOffset, sectionOffset); + okay = false; + } + + if (!okay) { + break; + } + + switch (type) { + case kDexTypeHeaderItem: { + /* + * The header got swapped very early on, but do some + * additional sanity checking here. + */ + okay = checkHeaderSection(state, sectionOffset, sectionCount, + &lastOffset); + break; + } + case kDexTypeStringIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->stringIdsOff, + state->pHeader->stringIdsSize, swapStringIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeTypeIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->typeIdsOff, + state->pHeader->typeIdsSize, swapTypeIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeProtoIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->protoIdsOff, + state->pHeader->protoIdsSize, swapProtoIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeFieldIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->fieldIdsOff, + state->pHeader->fieldIdsSize, swapFieldIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeMethodIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->methodIdsOff, + state->pHeader->methodIdsSize, swapMethodIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeClassDefItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->classDefsOff, + state->pHeader->classDefsSize, swapClassDefItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeMapList: { + /* + * The map section was swapped early on, but do some + * additional sanity checking here. + */ + okay = checkMapSection(state, sectionOffset, sectionCount, + &lastOffset); + break; + } + case kDexTypeTypeList: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapTypeList, sizeof(u4), &lastOffset, type); + break; + } + case kDexTypeAnnotationSetRefList: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapAnnotationSetRefList, sizeof(u4), &lastOffset, + type); + break; + } + case kDexTypeAnnotationSetItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapAnnotationSetItem, sizeof(u4), &lastOffset, type); + break; + } + case kDexTypeClassDataItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyClassDataItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeCodeItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapCodeItem, sizeof(u4), &lastOffset, type); + break; + } + case kDexTypeStringDataItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyStringDataItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeDebugInfoItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyDebugInfoItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeAnnotationItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyAnnotationItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeEncodedArrayItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyEncodedArrayItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeAnnotationsDirectoryItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapAnnotationsDirectoryItem, sizeof(u4), &lastOffset, + type); + break; + } + default: { + LOGE("Unknown map item type %04x\n", type); + return false; + } + } + + if (!okay) { + LOGE("Swap of section type %04x failed\n", type); + } + + item++; + } + + return okay; +} + +/* + * Perform cross-item verification on everything that needs it. This + * pass is only called after all items are byte-swapped and + * intra-verified (checked for internal consistency). + */ +static bool crossVerifyEverything(CheckState* state, DexMapList* pMap) +{ + const DexMapItem* item = pMap->list; + u4 count = pMap->size; + bool okay = true; + + while (okay && count--) { + u4 sectionOffset = item->offset; + u4 sectionCount = item->size; + + switch (item->type) { + case kDexTypeHeaderItem: + case kDexTypeMapList: + case kDexTypeTypeList: + case kDexTypeCodeItem: + case kDexTypeStringDataItem: + case kDexTypeDebugInfoItem: + case kDexTypeAnnotationItem: + case kDexTypeEncodedArrayItem: { + // There is no need for cross-item verification for these. + break; + } + case kDexTypeStringIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyStringIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeTypeIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyTypeIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeProtoIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyProtoIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeFieldIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyFieldIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeMethodIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyMethodIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeClassDefItem: { + // Allocate (on the stack) the "observed class_def" bits. + size_t arraySize = calcDefinedClassBitsSize(state); + u4 definedClassBits[arraySize]; + memset(definedClassBits, 0, arraySize * sizeof(u4)); + state->pDefinedClassBits = definedClassBits; + + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyClassDefItem, sizeof(u4), NULL); + + state->pDefinedClassBits = NULL; + break; + } + case kDexTypeAnnotationSetRefList: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyAnnotationSetRefList, sizeof(u4), NULL); + break; + } + case kDexTypeAnnotationSetItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyAnnotationSetItem, sizeof(u4), NULL); + break; + } + case kDexTypeClassDataItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyClassDataItem, sizeof(u1), NULL); + break; + } + case kDexTypeAnnotationsDirectoryItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyAnnotationsDirectoryItem, sizeof(u4), NULL); + break; + } + default: { + LOGE("Unknown map item type %04x\n", item->type); + return false; + } + } + + if (!okay) { + LOGE("Cross-item verify of section type %04x failed\n", + item->type); + } + + item++; + } + + return okay; +} + +/* + * Fix the byte ordering of all fields in the DEX file, and do + * structural verification. This is only required for code that opens + * "raw" DEX files, such as the DEX optimizer. + * + * Returns 0 on success, nonzero on failure. + */ +int dexSwapAndVerify(u1* addr, int len) +{ + DexHeader* pHeader; + CheckState state; + bool okay = true; + + memset(&state, 0, sizeof(state)); + LOGV("+++ swapping and verifying\n"); + + /* + * Start by verifying the magic number. The caller verified that "len" + * says we have at least a header's worth of data. + */ + pHeader = (DexHeader*) addr; + if (memcmp(pHeader->magic, DEX_MAGIC, 4) != 0) { + /* really shouldn't be here -- this is weird */ + LOGE("ERROR: Can't byte swap: bad magic number " + "(0x%02x %02x %02x %02x)\n", + pHeader->magic[0], pHeader->magic[1], + pHeader->magic[2], pHeader->magic[3]); + okay = false; + } + + if (okay && memcmp(pHeader->magic+4, DEX_MAGIC_VERS, 4) != 0) { + /* older or newer version we don't know how to read */ + LOGE("ERROR: Can't byte swap: bad dex version " + "(0x%02x %02x %02x %02x)\n", + pHeader->magic[4], pHeader->magic[5], + pHeader->magic[6], pHeader->magic[7]); + okay = false; + } + + if (okay) { + int expectedLen = (int) SWAP4(pHeader->fileSize); + if (len < expectedLen) { + LOGE("ERROR: Bad length: expected %d, got %d\n", expectedLen, len); + okay = false; + } else if (len != expectedLen) { + LOGW("WARNING: Odd length: expected %d, got %d\n", expectedLen, + len); + // keep going + } + } + + if (okay) { + /* + * Compute the adler32 checksum and compare it to what's stored in + * the file. This isn't free, but chances are good that we just + * unpacked this from a jar file and have all of the pages sitting + * in memory, so it's pretty quick. + * + * This might be a big-endian system, so we need to do this before + * we byte-swap the header. + */ + uLong adler = adler32(0L, Z_NULL, 0); + const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum); + u4 storedFileSize = SWAP4(pHeader->fileSize); + u4 expectedChecksum = SWAP4(pHeader->checksum); + + adler = adler32(adler, ((const u1*) pHeader) + nonSum, + storedFileSize - nonSum); + + if (adler != expectedChecksum) { + LOGE("ERROR: bad checksum (%08lx, expected %08x)\n", + adler, expectedChecksum); + okay = false; + } + } + + if (okay) { + state.fileStart = addr; + state.fileEnd = addr + len; + state.fileLen = len; + state.pDexFile = NULL; + state.pDataMap = NULL; + state.pDefinedClassBits = NULL; + state.previousItem = NULL; + + /* + * Swap the header and check the contents. + */ + okay = swapDexHeader(&state, pHeader); + } + + if (okay) { + state.pHeader = pHeader; + + if (pHeader->headerSize < sizeof(DexHeader)) { + LOGE("ERROR: Small header size %d, struct %d\n", + pHeader->headerSize, (int) sizeof(DexHeader)); + okay = false; + } else if (pHeader->headerSize > sizeof(DexHeader)) { + LOGW("WARNING: Large header size %d, struct %d\n", + pHeader->headerSize, (int) sizeof(DexHeader)); + // keep going? + } + } + + if (okay) { + /* + * Look for the map. Swap it and then use it to find and swap + * everything else. + */ + if (pHeader->mapOff != 0) { + DexFile dexFile; + DexMapList* pDexMap = (DexMapList*) (addr + pHeader->mapOff); + + okay = okay && swapMap(&state, pDexMap); + okay = okay && swapEverythingButHeaderAndMap(&state, pDexMap); + + dexFileSetupBasicPointers(&dexFile, addr); + state.pDexFile = &dexFile; + + okay = okay && crossVerifyEverything(&state, pDexMap); + } else { + LOGE("ERROR: No map found; impossible to byte-swap and verify"); + okay = false; + } + } + + if (!okay) { + LOGE("ERROR: Byte swap + verify failed\n"); + } + + if (state.pDataMap != NULL) { + dexDataMapFree(state.pDataMap); + } + + return !okay; // 0 == success +} + +/* + * Detect the file type of the given memory buffer via magic number. + * Call dexSwapAndVerify() on an unoptimized DEX file, do nothing + * but return successfully on an optimized DEX file, and report an + * error for all other cases. + * + * Returns 0 on success, nonzero on failure. + */ +int dexSwapAndVerifyIfNecessary(u1* addr, int len) +{ + if (memcmp(addr, DEX_OPT_MAGIC, 4) == 0) { + // It is an optimized dex file. + return 0; + } + + if (memcmp(addr, DEX_MAGIC, 4) == 0) { + // It is an unoptimized dex file. + return dexSwapAndVerify(addr, len); + } + + LOGE("ERROR: Bad magic number (0x%02x %02x %02x %02x)\n", + addr[0], addr[1], addr[2], addr[3]); + + return 1; +} |