Loading libs/androidfw/ResourceTypes.cpp +68 −36 Original line number Diff line number Diff line Loading @@ -727,6 +727,37 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) { AutoMutex lock(mDecodeLock); if (mCache != NULL && mCache[idx] != NULL) { return mCache[idx]; } // Retrieve the actual length of the utf8 string if the // encoded length was truncated if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) { return NULL; } // Since AAPT truncated lengths longer than 0x7FFF, check // that the bits that remain after truncation at least match // the bits of the actual length ssize_t actualLen = utf8_to_utf16_length(u8str, u8len); if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) { ALOGW("Bad string block: string #%lld decoded length is not correct " "%lld vs %llu\n", (long long)idx, (long long)actualLen, (long long)*u16len); return NULL; } *u16len = (size_t) actualLen; char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t)); if (!u16str) { ALOGW("No memory when trying to allocate decode cache for string #%d\n", (int)idx); return NULL; } utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); if (mCache == NULL) { #ifndef __ANDROID__ if (kDebugStringPoolNoisy) { Loading @@ -746,36 +777,10 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const } } if (mCache[idx] != NULL) { return mCache[idx]; } ssize_t actualLen = utf8_to_utf16_length(u8str, u8len); if (actualLen < 0 || (size_t)actualLen != *u16len) { ALOGW("Bad string block: string #%lld decoded length is not correct " "%lld vs %llu\n", (long long)idx, (long long)actualLen, (long long)*u16len); return NULL; } // Reject malformed (non null-terminated) strings if (u8str[u8len] != 0x00) { ALOGW("Bad string block: string #%d is not null-terminated", (int)idx); return NULL; } char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t)); if (!u16str) { ALOGW("No memory when trying to allocate decode cache for string #%d\n", (int)idx); return NULL; } if (kDebugStringPoolNoisy) { ALOGI("Caching UTF8 string: %s", u8str); } utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); mCache[idx] = u16str; return u16str; } else { Loading Loading @@ -812,13 +817,8 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const *outLen = encLen; if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { // Reject malformed (non null-terminated) strings if (str[encLen] != 0x00) { ALOGW("Bad string block: string #%d is not null-terminated", (int)idx); return NULL; } return (const char*)str; return stringDecodeAt(idx, str, encLen, outLen); } else { ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize); Loading @@ -832,6 +832,38 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const return NULL; } /** * AAPT incorrectly writes a truncated string length when the string size * exceeded the maximum possible encode length value (0x7FFF). To decode a * truncated length, iterate through length values that end in the encode length * bits. Strings that exceed the maximum encode length are not placed into * StringPools in AAPT2. **/ const char* ResStringPool::stringDecodeAt(size_t idx, const uint8_t* str, const size_t encLen, size_t* outLen) const { const uint8_t* strings = (uint8_t*)mStrings; size_t i = 0, end = encLen; while ((uint32_t)(str+end-strings) < mStringPoolSize) { if (str[end] == 0x00) { if (i != 0) { ALOGW("Bad string block: string #%d is truncated (actual length is %d)", (int)idx, (int)end); } *outLen = end; return (const char*)str; } end = (++i << (sizeof(uint8_t) * 8 * 2 - 1)) | encLen; } // Reject malformed (non null-terminated) strings ALOGW("Bad string block: string #%d is not null-terminated", (int)idx); return NULL; } const String8 ResStringPool::string8ObjectAt(size_t idx) const { size_t len; Loading libs/androidfw/include/androidfw/ResourceTypes.h +3 −0 Original line number Diff line number Diff line Loading @@ -538,6 +538,9 @@ private: uint32_t mStringPoolSize; // number of uint16_t const uint32_t* mStyles; uint32_t mStylePoolSize; // number of uint32_t const char* stringDecodeAt(size_t idx, const uint8_t* str, const size_t encLen, size_t* outLen) const; }; /** Loading libs/androidfw/tests/ResTable_test.cpp +52 −0 Original line number Diff line number Diff line Loading @@ -424,4 +424,56 @@ TEST(ResTableTest, GetConfigurationsReturnsUniqueList) { EXPECT_EQ(1, std::count(locales.begin(), locales.end(), String8("sv"))); } TEST(ResTableTest, TruncatedEncodeLength) { std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_valid.apk", "resources.arsc", &contents)); ResTable table; ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size())); Res_value val; ssize_t block = table.getResource(0x7f010001, &val, MAY_NOT_BE_BAG); ASSERT_GE(block, 0); ASSERT_EQ(Res_value::TYPE_STRING, val.dataType); const ResStringPool* pool = table.getTableStringBlock(block); ASSERT_TRUE(pool != NULL); ASSERT_LT(val.data, pool->size()); // Make sure a string with a truncated length is read to its correct length size_t str_len; const char* target_str8 = pool->string8At(val.data, &str_len); ASSERT_TRUE(target_str8 != NULL); ASSERT_EQ(size_t(40076), String8(target_str8, str_len).size()); ASSERT_EQ(target_str8[40075], ']'); const char16_t* target_str16 = pool->stringAt(val.data, &str_len); ASSERT_TRUE(target_str16 != NULL); ASSERT_EQ(size_t(40076), String16(target_str16, str_len).size()); ASSERT_EQ(target_str8[40075], (char16_t) ']'); // Load an edited apk with the null terminator removed from the end of the // string std::string invalid_contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_invalid.apk", "resources.arsc", &invalid_contents)); ResTable invalid_table; ASSERT_EQ(NO_ERROR, invalid_table.add(invalid_contents.data(), invalid_contents.size())); Res_value invalid_val; ssize_t invalid_block = invalid_table.getResource(0x7f010001, &invalid_val, MAY_NOT_BE_BAG); ASSERT_GE(invalid_block, 0); ASSERT_EQ(Res_value::TYPE_STRING, invalid_val.dataType); const ResStringPool* invalid_pool = invalid_table.getTableStringBlock(invalid_block); ASSERT_TRUE(invalid_pool != NULL); ASSERT_LT(invalid_val.data, invalid_pool->size()); // Make sure a string with a truncated length that is not null terminated errors // and does not return the string ASSERT_TRUE(invalid_pool->string8At(invalid_val.data, &str_len) == NULL); ASSERT_TRUE(invalid_pool->stringAt(invalid_val.data, &str_len) == NULL); } } // namespace android libs/androidfw/tests/data/length_decode/length_decode_invalid.apk 0 → 100644 +40.2 KiB File added.No diff preview for this file type. View file libs/androidfw/tests/data/length_decode/length_decode_valid.apk 0 → 100644 +40.2 KiB File added.No diff preview for this file type. View file Loading
libs/androidfw/ResourceTypes.cpp +68 −36 Original line number Diff line number Diff line Loading @@ -727,6 +727,37 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) { AutoMutex lock(mDecodeLock); if (mCache != NULL && mCache[idx] != NULL) { return mCache[idx]; } // Retrieve the actual length of the utf8 string if the // encoded length was truncated if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) { return NULL; } // Since AAPT truncated lengths longer than 0x7FFF, check // that the bits that remain after truncation at least match // the bits of the actual length ssize_t actualLen = utf8_to_utf16_length(u8str, u8len); if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) { ALOGW("Bad string block: string #%lld decoded length is not correct " "%lld vs %llu\n", (long long)idx, (long long)actualLen, (long long)*u16len); return NULL; } *u16len = (size_t) actualLen; char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t)); if (!u16str) { ALOGW("No memory when trying to allocate decode cache for string #%d\n", (int)idx); return NULL; } utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); if (mCache == NULL) { #ifndef __ANDROID__ if (kDebugStringPoolNoisy) { Loading @@ -746,36 +777,10 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const } } if (mCache[idx] != NULL) { return mCache[idx]; } ssize_t actualLen = utf8_to_utf16_length(u8str, u8len); if (actualLen < 0 || (size_t)actualLen != *u16len) { ALOGW("Bad string block: string #%lld decoded length is not correct " "%lld vs %llu\n", (long long)idx, (long long)actualLen, (long long)*u16len); return NULL; } // Reject malformed (non null-terminated) strings if (u8str[u8len] != 0x00) { ALOGW("Bad string block: string #%d is not null-terminated", (int)idx); return NULL; } char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t)); if (!u16str) { ALOGW("No memory when trying to allocate decode cache for string #%d\n", (int)idx); return NULL; } if (kDebugStringPoolNoisy) { ALOGI("Caching UTF8 string: %s", u8str); } utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); mCache[idx] = u16str; return u16str; } else { Loading Loading @@ -812,13 +817,8 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const *outLen = encLen; if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { // Reject malformed (non null-terminated) strings if (str[encLen] != 0x00) { ALOGW("Bad string block: string #%d is not null-terminated", (int)idx); return NULL; } return (const char*)str; return stringDecodeAt(idx, str, encLen, outLen); } else { ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize); Loading @@ -832,6 +832,38 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const return NULL; } /** * AAPT incorrectly writes a truncated string length when the string size * exceeded the maximum possible encode length value (0x7FFF). To decode a * truncated length, iterate through length values that end in the encode length * bits. Strings that exceed the maximum encode length are not placed into * StringPools in AAPT2. **/ const char* ResStringPool::stringDecodeAt(size_t idx, const uint8_t* str, const size_t encLen, size_t* outLen) const { const uint8_t* strings = (uint8_t*)mStrings; size_t i = 0, end = encLen; while ((uint32_t)(str+end-strings) < mStringPoolSize) { if (str[end] == 0x00) { if (i != 0) { ALOGW("Bad string block: string #%d is truncated (actual length is %d)", (int)idx, (int)end); } *outLen = end; return (const char*)str; } end = (++i << (sizeof(uint8_t) * 8 * 2 - 1)) | encLen; } // Reject malformed (non null-terminated) strings ALOGW("Bad string block: string #%d is not null-terminated", (int)idx); return NULL; } const String8 ResStringPool::string8ObjectAt(size_t idx) const { size_t len; Loading
libs/androidfw/include/androidfw/ResourceTypes.h +3 −0 Original line number Diff line number Diff line Loading @@ -538,6 +538,9 @@ private: uint32_t mStringPoolSize; // number of uint16_t const uint32_t* mStyles; uint32_t mStylePoolSize; // number of uint32_t const char* stringDecodeAt(size_t idx, const uint8_t* str, const size_t encLen, size_t* outLen) const; }; /** Loading
libs/androidfw/tests/ResTable_test.cpp +52 −0 Original line number Diff line number Diff line Loading @@ -424,4 +424,56 @@ TEST(ResTableTest, GetConfigurationsReturnsUniqueList) { EXPECT_EQ(1, std::count(locales.begin(), locales.end(), String8("sv"))); } TEST(ResTableTest, TruncatedEncodeLength) { std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_valid.apk", "resources.arsc", &contents)); ResTable table; ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size())); Res_value val; ssize_t block = table.getResource(0x7f010001, &val, MAY_NOT_BE_BAG); ASSERT_GE(block, 0); ASSERT_EQ(Res_value::TYPE_STRING, val.dataType); const ResStringPool* pool = table.getTableStringBlock(block); ASSERT_TRUE(pool != NULL); ASSERT_LT(val.data, pool->size()); // Make sure a string with a truncated length is read to its correct length size_t str_len; const char* target_str8 = pool->string8At(val.data, &str_len); ASSERT_TRUE(target_str8 != NULL); ASSERT_EQ(size_t(40076), String8(target_str8, str_len).size()); ASSERT_EQ(target_str8[40075], ']'); const char16_t* target_str16 = pool->stringAt(val.data, &str_len); ASSERT_TRUE(target_str16 != NULL); ASSERT_EQ(size_t(40076), String16(target_str16, str_len).size()); ASSERT_EQ(target_str8[40075], (char16_t) ']'); // Load an edited apk with the null terminator removed from the end of the // string std::string invalid_contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_invalid.apk", "resources.arsc", &invalid_contents)); ResTable invalid_table; ASSERT_EQ(NO_ERROR, invalid_table.add(invalid_contents.data(), invalid_contents.size())); Res_value invalid_val; ssize_t invalid_block = invalid_table.getResource(0x7f010001, &invalid_val, MAY_NOT_BE_BAG); ASSERT_GE(invalid_block, 0); ASSERT_EQ(Res_value::TYPE_STRING, invalid_val.dataType); const ResStringPool* invalid_pool = invalid_table.getTableStringBlock(invalid_block); ASSERT_TRUE(invalid_pool != NULL); ASSERT_LT(invalid_val.data, invalid_pool->size()); // Make sure a string with a truncated length that is not null terminated errors // and does not return the string ASSERT_TRUE(invalid_pool->string8At(invalid_val.data, &str_len) == NULL); ASSERT_TRUE(invalid_pool->stringAt(invalid_val.data, &str_len) == NULL); } } // namespace android
libs/androidfw/tests/data/length_decode/length_decode_invalid.apk 0 → 100644 +40.2 KiB File added.No diff preview for this file type. View file
libs/androidfw/tests/data/length_decode/length_decode_valid.apk 0 → 100644 +40.2 KiB File added.No diff preview for this file type. View file