/* * Copyright (C) 2018 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. */ #include #if defined(__BIONIC__) #include #include #include #include #include #include #include #include #include "utils.h" extern "C" void malloc_disable(); extern "C" void malloc_enable(); extern "C" int malloc_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t base, size_t size, void* arg), void* arg); struct AllocDataType { void* ptr; size_t size; size_t size_reported; size_t count; }; struct TestDataType { size_t total_allocated_bytes; std::vector allocs; }; static void AllocPtr(TestDataType* test_data, size_t size) { test_data->allocs.resize(test_data->allocs.size() + 1); AllocDataType* alloc = &test_data->allocs.back(); void* ptr = malloc(size); ASSERT_TRUE(ptr != nullptr); alloc->ptr = ptr; alloc->size = malloc_usable_size(ptr); alloc->size_reported = 0; alloc->count = 0; } static void FreePtrs(TestDataType* test_data) { for (size_t i = 0; i < test_data->allocs.size(); i++) { free(test_data->allocs[i].ptr); } } static void SavePointers(uintptr_t base, size_t size, void* data) { TestDataType* test_data = reinterpret_cast(data); test_data->total_allocated_bytes += size; uintptr_t end; if (__builtin_add_overflow(base, size, &end)) { // Skip this entry. return; } for (size_t i = 0; i < test_data->allocs.size(); i++) { uintptr_t ptr = reinterpret_cast(test_data->allocs[i].ptr); if (ptr >= base && ptr < end) { test_data->allocs[i].count++; uintptr_t max_size = end - ptr; if (max_size > test_data->allocs[i].size) { test_data->allocs[i].size_reported = test_data->allocs[i].size; } else { test_data->allocs[i].size_reported = max_size; } } } } static void VerifyPtrs(TestDataType* test_data) { test_data->total_allocated_bytes = 0; // Find all of the maps that are from the native allocator. auto callback = [&](uint64_t start, uint64_t end, uint16_t, uint64_t, ino_t, const char* name, bool) { if (strcmp(name, "[anon:libc_malloc]") == 0 || strncmp(name, "[anon:scudo:", 12) == 0 || strncmp(name, "[anon:GWP-ASan", 14) == 0) { malloc_iterate(start, end - start, SavePointers, test_data); } }; std::vector buffer(64 * 1024); // Avoid doing allocations so that the maps don't change while looking // for the pointers. malloc_disable(); bool parsed = android::procinfo::ReadMapFileAsyncSafe("/proc/self/maps", buffer.data(), buffer.size(), callback); malloc_enable(); ASSERT_TRUE(parsed) << "Failed to parse /proc/self/maps"; for (size_t i = 0; i < test_data->allocs.size(); i++) { EXPECT_EQ(1UL, test_data->allocs[i].count) << "Failed on size " << test_data->allocs[i].size; if (test_data->allocs[i].count == 1) { EXPECT_EQ(test_data->allocs[i].size, test_data->allocs[i].size_reported); } } } static void AllocateSizes(TestDataType* test_data, const std::vector& sizes) { static constexpr size_t kInitialAllocations = 40; static constexpr size_t kNumAllocs = 50; for (size_t size : sizes) { // Verify that if the tcache is enabled, that tcache pointers // are found by allocating and freeing 20 pointers (should be larger // than the total number of cache entries). for (size_t i = 0; i < kInitialAllocations; i++) { void* ptr = malloc(size); ASSERT_TRUE(ptr != nullptr); memset(ptr, 0, size); free(ptr); } for (size_t i = 0; i < kNumAllocs; i++) { AllocPtr(test_data, size); } } } #endif // Verify that small allocs can be found properly. TEST(malloc_iterate, small_allocs) { #if defined(__BIONIC__) SKIP_WITH_HWASAN; TestDataType test_data; // Try to cycle through all of the different small bins. // This is specific to the implementation of jemalloc and should be // adjusted if a different native memory allocator is used. std::vector sizes{8, 16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048, 2560, 3072, 3584, 4096, 5120, 6144, 7168, 8192, 10240, 12288, 14336, 16384, 32768, 65536}; AllocateSizes(&test_data, sizes); SCOPED_TRACE(""); VerifyPtrs(&test_data); FreePtrs(&test_data); #else GTEST_SKIP() << "bionic-only test"; #endif } // Verify that large allocs can be found properly. TEST(malloc_iterate, large_allocs) { #if defined(__BIONIC__) SKIP_WITH_HWASAN; TestDataType test_data; // Try some larger sizes. std::vector sizes{131072, 262144, 524288, 1048576, 2097152}; AllocateSizes(&test_data, sizes); SCOPED_TRACE(""); VerifyPtrs(&test_data); FreePtrs(&test_data); #else GTEST_SKIP() << "bionic-only test"; #endif } // Verify that there are no crashes attempting to get pointers from // non-allocated pointers. TEST(malloc_iterate, invalid_pointers) { #if defined(__BIONIC__) SKIP_WITH_HWASAN; TestDataType test_data = {}; // Only attempt to get memory data for maps that are not from the native allocator. auto callback = [&](uint64_t start, uint64_t end, uint16_t, uint64_t, ino_t, const char* name, bool) { if (strcmp(name, "[anon:libc_malloc]") != 0 && strncmp(name, "[anon:scudo:", 12) != 0 && strncmp(name, "[anon:GWP-ASan", 14) != 0) { size_t total = test_data.total_allocated_bytes; malloc_iterate(start, end - start, SavePointers, &test_data); total = test_data.total_allocated_bytes - total; if (total > 0) { char buffer[256]; int len = 0; if (name[0] != '\0') { len = async_safe_format_buffer(buffer, sizeof(buffer), "Failed on map %s: %zu\n", name, total); } else { len = async_safe_format_buffer(buffer, sizeof(buffer), "Failed on map anon:<%" PRIx64 "-%" PRIx64 ">: %zu\n", start, end, total); } if (len > 0) { write(STDOUT_FILENO, buffer, len); } } } }; std::vector buffer(64 * 1024); // Need to make sure that there are no allocations while reading the // maps. Otherwise, it might create a new map during this check and // incorrectly think a map is empty while it actually includes real // allocations. malloc_disable(); bool parsed = android::procinfo::ReadMapFileAsyncSafe("/proc/self/maps", buffer.data(), buffer.size(), callback); malloc_enable(); ASSERT_TRUE(parsed) << "Failed to parse /proc/self/maps"; ASSERT_EQ(0UL, test_data.total_allocated_bytes); #else GTEST_SKIP() << "bionic-only test"; #endif } TEST(malloc_iterate, malloc_disable_prevents_allocs) { #if defined(__BIONIC__) SKIP_WITH_HWASAN; pid_t pid; if ((pid = fork()) == 0) { malloc_disable(); void* ptr = malloc(1024); if (ptr == nullptr) { exit(1); } memset(ptr, 0, 1024); exit(0); } ASSERT_NE(-1, pid); // Expect that the malloc will hang forever, and that if the process // does not return for two seconds, it is hung. sleep(2); pid_t wait_pid = TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); if (wait_pid <= 0) { kill(pid, SIGKILL); } ASSERT_NE(-1, wait_pid) << "Unknown failure in waitpid."; ASSERT_EQ(0, wait_pid) << "malloc_disable did not prevent allocation calls."; #else GTEST_SKIP() << "bionic-only test"; #endif }