Migrate aslr test to gtest.

Create tests which test the values of mmap_rnd_bits, and
mmap_rnd_compat_bits, if applicable, and verify that the address space
is randomized as expected given the provided values.

Also add a pair of tests to CTS that enforce that the observed entropy
is at least as high as a designated value.  That value will start as our
default value, which also corresponds to the maximum value of some
configurations.

Packaging of executables along with a nativetest suite is not supported,
so add a dummy nativetest suite, scrape_mmap_addr, which ensure that the
executables are present along with the given tests at predictable
locations.

(cherry-pick of internal commit: d661642651a6689c76d3f575b4b5dbf04e6b75bd)

Bug: 26512380
Change-Id: I8c4a68f3d10a57f9a51bdc6084df622cf8517b51
diff --git a/tests/kernel.config/Android.mk b/tests/kernel.config/Android.mk
index 7a02521..fc90f66 100644
--- a/tests/kernel.config/Android.mk
+++ b/tests/kernel.config/Android.mk
@@ -16,6 +16,7 @@
 
 # Required Tests
 cts_src_files := \
+    aslr_test.cpp \
     multicast_test.cpp \
     pstore_test.cpp \
     sysvipc_test.cpp \
@@ -24,6 +25,7 @@
 # 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 @@
 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 0000000..3dc61e6
--- /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 0000000..cda121c
--- /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 0000000..355a62a
--- /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 0000000..be5995f
--- /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;
+}