/* * 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 "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 = filePointer(state, (_start)); \ const u1* _endPtr = 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 = 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 = 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 = 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 = ptr; if (!dexDataMapVerify(state->pDataMap, item->stringDataOff, kDexTypeStringDataItem)) { return NULL; } const DexStringId* item0 = 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 = 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 = ptr; const char* descriptor = dexStringById(state->pDexFile, item->descriptorIdx); if (!dexIsValidTypeDescriptor(descriptor)) { LOGE("Invalid type descriptor: '%s'\n", descriptor); return NULL; } const DexTypeId* item0 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = filePointer(state, newOffset); if (offset < newOffset) { ptr = 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 = 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; }