/*
* Copyright (C) 2015 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.
*/
#define LOG_NDEBUG 0
#define LOG_TAG "AslrMallocTest"
#if !defined(BUILD_ONLY)
#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <linux/limits.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <unordered_set>
#endif
#include <gtest/gtest.h>
#include <string>
#include <utils/Log.h>
/* minimum entropy for malloc return addresses */
const size_t minEntropyBits = 8;
/* test using the following allocation sizes */
const size_t allocSizes[] = {
1 << 8, // small
1 << 16, // large
1 << 23 // huge
};
/* when started using this argument followed by the allocation size,
* performs malloc(size) and prints out the address */
static const std::string argPrint = "--print-malloc-address";
#if !defined(BUILD_ONLY)
class AslrMallocTest : public ::testing::Test
{
protected:
std::string self_;
AslrMallocTest() {}
virtual ~AslrMallocTest() {}
virtual void SetUp()
{
/* path to self for exec */
char path[PATH_MAX];
auto size = readlink("/proc/self/exe", path, sizeof(path));
ASSERT_TRUE(size > 0 && size < PATH_MAX);
path[size] = '\0';
self_ = path;
}
void GetAddress(size_t allocSize, uintptr_t& address)
{
int fds[2];
ASSERT_TRUE(pipe(fds) != -1);
auto pid = fork();
ASSERT_TRUE(pid != -1);
if (pid == 0) {
/* child process */
ASSERT_TRUE(TEMP_FAILURE_RETRY(dup2(fds[1], STDOUT_FILENO)) != -1);
for (auto fd : fds) {
TEMP_FAILURE_RETRY(close(fd));
}
/* exec self to print malloc output */
ASSERT_TRUE(execl(self_.c_str(), self_.c_str(), argPrint.c_str(),
android::base::StringPrintf("%zu", allocSize).c_str(),
nullptr) != -1);
}
/* parent process */
TEMP_FAILURE_RETRY(close(fds[1]));
std::string output;
ASSERT_TRUE(android::base::ReadFdToString(fds[0], &output));
TEMP_FAILURE_RETRY(close(fds[0]));
int status;
ASSERT_TRUE(waitpid(pid, &status, 0) != -1);
ASSERT_TRUE(WEXITSTATUS(status) == EXIT_SUCCESS);
ASSERT_TRUE(android::base::ParseUint(output.c_str(), &address));
}
void TestRandomization()
{
/* should be sufficient to see minEntropyBits when rounded up */
size_t iterations = 2 * (1 << minEntropyBits);
for (auto size : allocSizes) {
ALOGV("running %zu iterations for allocation size %zu",
iterations, size);
/* collect unique return addresses */
std::unordered_set<uintptr_t> addresses;
for (size_t i = 0; i < iterations; ++i) {
uintptr_t address;
GetAddress(size, address);
addresses.emplace(address);
}
size_t entropy = static_cast<size_t>(0.5 +
log2(static_cast<double>(addresses.size())));
ALOGV("%zu bits of entropy for allocation size %zu (minimum %zu)",
entropy, size, minEntropyBits);
ALOGE_IF(entropy < minEntropyBits,
"insufficient entropy for malloc(%zu)", size);
ASSERT_TRUE(entropy >= minEntropyBits);
}
}
};
#else /* defined(BUILD_ONLY) */
class AslrMallocTest : public ::testing::Test
{
protected:
void TestRandomization() {}
};
#endif
TEST_F(AslrMallocTest, testMallocRandomization) {
TestRandomization();
}
int main(int argc, char **argv)
{
#if !defined(BUILD_ONLY)
if (argc == 3 && argPrint == argv[1]) {
size_t size;
if (!android::base::ParseUint(argv[2], &size)) {
return EXIT_FAILURE;
}
printf("%p", malloc(size));
return EXIT_SUCCESS;
}
#endif
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}