diff options
author | Daniel Cashman <dcashman@google.com> | 2016-03-14 17:38:58 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2016-03-14 17:38:59 +0000 |
commit | b098491ac69154f8633d11c0fc4a50429d374a4a (patch) | |
tree | 9a66fe158756dce427c5b3c9ebb970ea0402f5a1 | |
parent | ad64c38f5652c8557c3f61953a92c457da3e82d4 (diff) | |
parent | d661642651a6689c76d3f575b4b5dbf04e6b75bd (diff) | |
download | extras-b098491ac69154f8633d11c0fc4a50429d374a4a.tar.gz |
Merge "Migrate aslr test to gtest." into nyc-dev
-rw-r--r-- | tests/kernel.config/Android.mk | 9 | ||||
-rw-r--r-- | tests/kernel.config/aslr_rec_test.cpp | 135 | ||||
-rw-r--r-- | tests/kernel.config/aslr_test.cpp | 231 | ||||
-rw-r--r-- | tests/kernel.config/aslr_test.h | 77 | ||||
-rw-r--r-- | tests/kernel.config/scrape_mmap_addr.cpp | 32 |
5 files changed, 484 insertions, 0 deletions
diff --git a/tests/kernel.config/Android.mk b/tests/kernel.config/Android.mk index 7a02521a..fc90f669 100644 --- a/tests/kernel.config/Android.mk +++ b/tests/kernel.config/Android.mk @@ -16,6 +16,7 @@ test_c_flags := \ # Required Tests cts_src_files := \ + aslr_test.cpp \ multicast_test.cpp \ pstore_test.cpp \ sysvipc_test.cpp \ @@ -24,6 +25,7 @@ cts_src_files := \ # Required plus Recommended Tests test_src_files := \ $(cts_src_files) \ + aslr_rec_test.cpp \ mmc_max_speed_test.cpp \ cts_executable := CtsKernelConfigTestCases @@ -68,3 +70,10 @@ LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk include $(BUILD_HOST_NATIVE_TEST) endif # ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64)) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + scrape_mmap_addr.cpp + +LOCAL_MODULE := scrape_mmap_addr +include $(BUILD_NATIVE_TEST) diff --git a/tests/kernel.config/aslr_rec_test.cpp b/tests/kernel.config/aslr_rec_test.cpp new file mode 100644 index 00000000..3dc61e6e --- /dev/null +++ b/tests/kernel.config/aslr_rec_test.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 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 "aslr_test.h" + +/* run tests if on supported arch */ +#if defined(__x86__64__) || defined(__i386__) || defined(__aarch64__) || defined(__arm__) + +/* make sure the default entropy values matches what we expect */ +TEST_F(AslrMmapTest, match_default) { + if (user32) { + // running 32-bit userspace on 64-bit kernel, only compat used. + return; + } else { + EXPECT_EQ(def, get_mmap_rnd_bits(false)); + } +} + +/* make sure the default compat entropy values matches what we expect */ +TEST_F(AslrMmapTest, match_compat_default) { + if (compat || user32) + EXPECT_EQ(def_cmpt, get_mmap_rnd_bits(true)); +} + +/* make sure we can't set entropy below a minimum threshold */ +TEST_F(AslrMmapTest, match_min) { + if (user32) { + // running 32-bit userspace on 64-bit kernel, only compat used. + return; + } else { + EXPECT_FALSE(set_mmap_rnd_bits(min - 1, false)); + EXPECT_TRUE(set_mmap_rnd_bits(min, false)); + EXPECT_EQ(min, get_mmap_rnd_bits(false)); + } +} + +/* make sure we can't set compat entropy below a minimum threshold */ +TEST_F(AslrMmapTest, match_compat_min) { + if (compat || user32) { + EXPECT_FALSE(set_mmap_rnd_bits(min_cmpt - 1, true)); + EXPECT_TRUE(set_mmap_rnd_bits(min_cmpt, true)); + EXPECT_EQ(min_cmpt, get_mmap_rnd_bits(true)); + } +} + +/* make sure we can't set entropy above a maximum threshold */ +TEST_F(AslrMmapTest, match_max) { + if (user32) { + // running 32-bit userspace on 64-bit kernel, only compat used. + return; + } else { + EXPECT_FALSE(set_mmap_rnd_bits(max + 1, false)); + EXPECT_TRUE(set_mmap_rnd_bits(max, false)); + EXPECT_EQ(max, get_mmap_rnd_bits(false)); + } +} + +/* make sure we can't set compat entropy above a maximum threshold */ +TEST_F(AslrMmapTest, match_compat_max) { + if (compat || user32) { + EXPECT_FALSE(set_mmap_rnd_bits(max_cmpt + 1, true)); + EXPECT_TRUE(set_mmap_rnd_bits(max_cmpt, true)); + EXPECT_EQ(max_cmpt, get_mmap_rnd_bits(true)); + } +} + +/* make sure observed entropy is what we expect when we set min value */ +TEST_F(AslrMmapTest, entropy_min) { + if (user32) { + // running 32-bit userspace on 64-bit kernel, only compat used. + return; + } else { + EXPECT_TRUE(set_mmap_rnd_bits(min, false)); + EXPECT_EQ(min, calc_mmap_entropy(path, lib, 16)); + } +} + +/* make sure observed compat entropy is what we expect when we set min value */ +TEST_F(AslrMmapTest, entropy_cmpt_min) { + if (compat || user32) { + EXPECT_TRUE(set_mmap_rnd_bits(min_cmpt, true)); + EXPECT_EQ(min_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16)); + } +} + +/* make sure observed entropy is what we expect when we set max value */ +TEST_F(AslrMmapTest, entropy_max) { + if (user32) { + // running 32-bit userspace on 64-bit kernel, only compat used. + return; + } else { + EXPECT_TRUE(set_mmap_rnd_bits(max, false)); + EXPECT_EQ(max, calc_mmap_entropy(path, lib, 16)); + } +} + +/* make sure observed compat entropy is what we expect when we set max value */ +TEST_F(AslrMmapTest, entropy_cmpt_max) { + if (compat || user32) { + EXPECT_TRUE(set_mmap_rnd_bits(max_cmpt, true)); + EXPECT_EQ(max_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16)); + } +} + +/* make sure observed entropy is what we expect for default value */ +TEST_F(AslrMmapTest, entropy_def) { + if (user32) { + // running 32-bit userspace on 64-bit kernel, only compat used. + return; + } else { + EXPECT_EQ(def, calc_mmap_entropy(path, lib, 16)); + } +} + +/* make sure observed entropy is what we expect for default compat value */ +TEST_F(AslrMmapTest, entropy_cmpt_def) { + if (compat || user32) { + EXPECT_EQ(def_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16)); + } +} + +#endif /* supported arch */ diff --git a/tests/kernel.config/aslr_test.cpp b/tests/kernel.config/aslr_test.cpp new file mode 100644 index 00000000..cda121c6 --- /dev/null +++ b/tests/kernel.config/aslr_test.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2016 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 "aslr_test.h" + +unsigned int get_mmap_rnd_bits(bool compat) { + std::string path; + + if (compat) + path = PROCFS_COMPAT_PATH; + else + path = PROCFS_PATH; + + std::ifstream bi_file(path); + if (!bi_file) + return false; + std::string str_rec; + bi_file >> str_rec; + + return stoi(str_rec); +} + +bool set_mmap_rnd_bits(unsigned int new_val, bool compat) { + std::string path; + + if (compat) + path = "/proc/sys/vm/mmap_rnd_compat_bits"; + else + path = "/proc/sys/vm/mmap_rnd_bits"; + + std::ofstream bo_file(path, std::ios::out); + if (!bo_file) + return false; + + std::string str_val = std::to_string(new_val); + bo_file << str_val << std::flush; + bo_file.close(); + + // check to make sure it was recorded + std::ifstream bi_file(path); + if (!bi_file) + return false; + std::string str_rec; + bi_file >> str_rec; + bi_file.close(); + if (str_val.compare(str_rec) != 0) + return false; + return true; +} + +std::string scrape_addr(const char *exec_name, const char *lib_match) { + pid_t pid; + int fd[2]; + char buff[MAX_ADDR_LEN]; + int len, status; + if(pipe(fd)) { + std::cerr << "Error creating pipe:" << strerror(errno) << "\n"; + return std::string(); + } + + if ((pid = fork()) < 0) { + std::cerr << "Error creating new process: " << strerror(errno) << "\n"; + close(fd[0]); + close(fd[1]); + return std::string(); + } else if (pid > 0) { + // parent + close(fd[1]); + wait(&status); + if (status == -1) { + std::cerr << "Unable to find starting address of mmapp'd libc. Aborting.\n"; + close(fd[0]); + return std::string(); + } + len = read(fd[0], buff, MAX_ADDR_LEN - 1); + if (len < 0) { + std::cerr << "Error reading pipe from child: " << strerror(errno) << "\n"; + close(fd[0]); + return std::string(); + } + buff[len] = '\0'; + close(fd[0]); + } else { + // child, dup 'n' exec + close(fd[0]); + if(dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) { + std::cerr << "Error dup'n pipe to STDOUT of child: " << strerror(errno) << "\n"; + close(fd[1]); + return std::string(); + } + if(execlp(exec_name, exec_name, lib_match, (char *) NULL)) { + std::cerr << "Error exec'ing mmap_scraper: " << strerror(errno) << "\n"; + close(fd[1]); + return std::string(); + } + } + return std::string(buff, strlen(buff)); +} + +unsigned int calc_mmap_entropy(const char *exec_name, const char *lib_match, size_t samp_sz) { + uint64_t min_addr = 0, max_addr = 0; + + std::unordered_set<uint64_t> addrs = { }; + + // get our first value + uint64_t addr = min_addr = max_addr = std::stoll(scrape_addr(exec_name, lib_match), 0, 16); + addrs.insert(addr); + for (unsigned int i = 0; i < samp_sz - 1; ++i) { + std::string addr_str = scrape_addr(exec_name, lib_match); + if (addr_str.empty()) + return 0; + addr = std::stoll(addr_str, 0, 16); + if (addr < min_addr) + min_addr = addr; + if (addr >= max_addr) + max_addr = addr; + addrs.insert(addr); + } + if (addrs.size() < (samp_sz >> 1)) { + std::cerr << "> 50% collisions in mmap addresses, entropy appears to be rigged!"; + return 0; + } + unsigned int e_bits = (int) (std::ceil(std::log2(max_addr - min_addr)) - std::log2(getpagesize())); + return e_bits; +} + +const char *AslrMmapTest::path; +const char *AslrMmapTest::lib; +unsigned int AslrMmapTest::def, AslrMmapTest::min, AslrMmapTest::max; +bool AslrMmapTest::compat = false, AslrMmapTest::user32 = false; +unsigned int AslrMmapTest::def_cmpt, AslrMmapTest::min_cmpt, AslrMmapTest::max_cmpt; + +void AslrMmapTest::SetUpTestCase() { + /* set up per-arch values */ +#if defined(__x86__64__) + def = 32; + min = 28; + max = 32; + path = SCRAPE_PATH_64; + lib = SCRAPE_LIB_64; + + compat = true; + def_cmpt = 16; + min_cmpt = 8; + max_cmpt = 16; + +#elif defined(__i386__) + def = 16; + min = 8; + max = 16; + path = SCRAPE_PATH_32; + lib = SCRAPE_LIB_32; + + if (!access(PROCFS_COMPAT_PATH, F_OK)) { + // running 32 bit userspace over 64-bit kernel + user32 = true; + def_cmpt = 16; + min_cmpt = 8; + max_cmpt = 16; + } + +#elif defined(__aarch64__) + unsigned int pgbits = std::log2(getpagesize()); + def = 24; + min = 18 - (pgbits - 12); + max = 24; + path = SCRAPE_PATH_64; + lib = SCRAPE_LIB_64; + + compat = true; + def_cmpt = 16; + min_cmpt = 11 - (pgbits - 12); + max_cmpt = 16; + +#elif defined(__arm__) + unsigned int pgbits = std::log2(getpagesize()); + def = 8; + min = 8; + max = 16; + path = SCRAPE_PATH_32; + lib = SCRAPE_LIB_32; + + if (!access(PROCFS_COMPAT_PATH, F_OK)) { + // running 32 bit userspace over 64-bit kernel + user32 = true; + def_cmpt = 16; + min_cmpt = 11 - (pgbits - 12);; + max_cmpt = 16; + } +#endif +} + +void AslrMmapTest::TearDown() { + if (!user32) + set_mmap_rnd_bits(def, false); + if (user32 || compat) + set_mmap_rnd_bits(def_cmpt, true); +} + +/* run tests only if on supported arch */ +#if defined(__x86__64__) || defined(__i386__) || defined(__aarch64__) || defined(__arm__) + +TEST_F(AslrMmapTest, entropy_min_def) { + if (user32) { + // running 32-bit userspace on 64-bit kernel, only compat used. + return; + } else { + EXPECT_GE(def, calc_mmap_entropy(path, lib, 16)); + } +} + +TEST_F(AslrMmapTest, entropy_min_cmpt_def) { + if (compat || user32) { + EXPECT_GE(def_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16)); + } +} + +#endif /* supported arch */ diff --git a/tests/kernel.config/aslr_test.h b/tests/kernel.config/aslr_test.h new file mode 100644 index 00000000..355a62a5 --- /dev/null +++ b/tests/kernel.config/aslr_test.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ASLR_TEST_H +#define ASLR_TEST_H + +#include <cmath> +#include <errno.h> +#include <fstream> +#include <iostream> +#include <stdint.h> +#include <string> +#include <sys/wait.h> +#include <unistd.h> +#include <unordered_set> + +#include <gtest/gtest.h> + +#define MAX_ADDR_LEN 256 + +#define PROCFS_PATH "/proc/sys/vm/mmap_rnd_bits" +#define PROCFS_COMPAT_PATH "/proc/sys/vm/mmap_rnd_compat_bits" + +#define SCRAPE_PATH_64 "/data/nativetest64/scrape_mmap_addr/scrape_mmap_addr" +#define SCRAPE_PATH_32 "/data/nativetest/scrape_mmap_addr/scrape_mmap_addr" +#define SCRAPE_LIB_64 "/system/bin/linker64" +#define SCRAPE_LIB_32 "/system/bin/linker" + +class AslrMmapTest : public ::testing::Test { + protected: + static void SetUpTestCase(); + static const char *path; + static const char *lib; + static unsigned int def, min, max; + static bool compat, user32; + static unsigned int def_cmpt, min_cmpt, max_cmpt; + + void TearDown(); +}; + +/* + * gets the current mmap_rnd_bits value. requires root. + */ +unsigned int get_mmap_rnd_bits(bool compat); + +/* + * sets the corresponding mmap_rnd_bits variable, returns false if couldn't + * change. requires root. + */ +bool set_mmap_rnd_bits(unsigned int new_val, bool compat); + +/* + * scrape_addr - get the raw starting address from /proc/child_pid/mmaps + */ +std::string scrape_addr(const char *exec_name, const char *lib_match); + +/* + * forks off sample_size processes and records the starting address of the + * indicated library as reported by exec_name. Reports entropy observed among + * recorded samples. + */ +unsigned int calc_mmap_entropy(const char *exec_name, const char *lib_match, size_t samp_sz); + +#endif //ASLR_TEST_H diff --git a/tests/kernel.config/scrape_mmap_addr.cpp b/tests/kernel.config/scrape_mmap_addr.cpp new file mode 100644 index 00000000..be5995f1 --- /dev/null +++ b/tests/kernel.config/scrape_mmap_addr.cpp @@ -0,0 +1,32 @@ +#include <errno.h> +#include <fstream> +#include <iostream> +#include <regex> +#include <string> +#include <string.h> + +int main(int argc, char * argv[]) { + if (argc != 2) { + std::cerr << "usage: " << argv[0] << ": libname\n"; + return -1; + } + std::regex reg(std::string("^([a-f0-9]+)\\-[0-9a-f]+\\s+.+\\s+(\\d+)\\s+.+\\s+\\d+\\s+") + std::string(argv[1]) + std::string("\\s*$")); + + /* open /proc/self/maps */ + std::string ln; + std::ifstream m_file("/proc/self/maps"); + if (!m_file) { + std::cerr << "Unable to open /proc/self/maps " << strerror(errno) << "\n"; + return -1; + } + while (getline(m_file, ln)) { + std::smatch sm; + if (std::regex_match (ln,sm, reg)) { + if (std::stoi(sm[2]) == 0) { + std::cout << sm[1]; + return 0; + } + } + } + return -1; +} |