1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "aslr_test.h"
18
19unsigned int get_mmap_rnd_bits(bool compat) {
20    std::string path;
21
22    if (compat)
23        path = PROCFS_COMPAT_PATH;
24    else
25        path = PROCFS_PATH;
26
27    std::ifstream bi_file(path);
28    if (!bi_file)
29        return false;
30    std::string str_rec;
31    bi_file >> str_rec;
32
33    return stoi(str_rec);
34}
35
36bool set_mmap_rnd_bits(unsigned int new_val, bool compat) {
37    std::string path;
38
39    if (compat)
40        path = "/proc/sys/vm/mmap_rnd_compat_bits";
41    else
42        path = "/proc/sys/vm/mmap_rnd_bits";
43
44    std::ofstream bo_file(path, std::ios::out);
45    if (!bo_file)
46        return false;
47
48    std::string str_val = std::to_string(new_val);
49    bo_file << str_val << std::flush;
50    bo_file.close();
51
52    // check to make sure it was recorded
53    std::ifstream bi_file(path);
54    if (!bi_file)
55        return false;
56    std::string str_rec;
57    bi_file >> str_rec;
58    bi_file.close();
59    if (str_val.compare(str_rec) != 0)
60        return false;
61    return true;
62}
63
64std::string scrape_addr(const char *exec_name, const char *lib_match) {
65    pid_t pid;
66    int fd[2];
67    char buff[MAX_ADDR_LEN];
68    int len, status;
69    if(pipe(fd)) {
70        std::cerr << "Error creating pipe:" << strerror(errno) << "\n";
71        return std::string();
72    }
73
74    if ((pid = fork()) < 0) {
75        std::cerr << "Error creating new process: " << strerror(errno) << "\n";
76        close(fd[0]);
77        close(fd[1]);
78        return std::string();
79    } else if (pid > 0) {
80        // parent
81        close(fd[1]);
82        wait(&status);
83        if (status == -1) {
84            std::cerr << "Unable to find starting address of mmapp'd libc. Aborting.\n";
85            close(fd[0]);
86            return std::string();
87        }
88        len = read(fd[0], buff, MAX_ADDR_LEN - 1);
89        if (len < 0) {
90            std::cerr << "Error reading pipe from child: " << strerror(errno) << "\n";
91            close(fd[0]);
92            return std::string();
93        }
94        buff[len] = '\0';
95        close(fd[0]);
96    } else {
97        // child, dup 'n' exec
98        close(fd[0]);
99        if(dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
100            std::cerr << "Error dup'n pipe to STDOUT of child: " << strerror(errno) << "\n";
101            close(fd[1]);
102            return std::string();
103        }
104        if(execlp(exec_name, exec_name, lib_match, (char *) NULL)) {
105            std::cerr << "Error exec'ing mmap_scraper: " << strerror(errno) << "\n";
106            close(fd[1]);
107            return std::string();
108        }
109    }
110    return std::string(buff, strlen(buff));
111}
112
113unsigned int calc_mmap_entropy(const char *exec_name, const char *lib_match, size_t samp_sz) {
114    uint64_t min_addr = 0, max_addr = 0;
115
116    std::unordered_set<uint64_t> addrs = { };
117
118    // get our first value
119    uint64_t addr = min_addr = max_addr = std::stoll(scrape_addr(exec_name, lib_match), 0, 16);
120    addrs.insert(addr);
121    for (unsigned int i = 0; i < samp_sz - 1; ++i) {
122        std::string addr_str = scrape_addr(exec_name, lib_match);
123        if (addr_str.empty())
124            return 0;
125        addr = std::stoll(addr_str, 0, 16);
126        if (addr < min_addr)
127            min_addr = addr;
128        if (addr >= max_addr)
129            max_addr = addr;
130        addrs.insert(addr);
131    }
132    if (addrs.size() < (samp_sz >> 1)) {
133        std::cerr << "> 50% collisions in mmap addresses, entropy appears to be rigged!";
134        return 0;
135    }
136    unsigned int e_bits = (int) (std::ceil(std::log2(max_addr - min_addr)) - std::log2(getpagesize()));
137    return e_bits;
138}
139
140const char *AslrMmapTest::path;
141const char *AslrMmapTest::lib;
142unsigned int AslrMmapTest::def, AslrMmapTest::min, AslrMmapTest::max;
143bool AslrMmapTest::compat = false, AslrMmapTest::user32 = false;
144unsigned int AslrMmapTest::def_cmpt, AslrMmapTest::min_cmpt, AslrMmapTest::max_cmpt;
145
146void AslrMmapTest::SetUpTestCase() {
147    /* set up per-arch values */
148#if defined(__x86_64__)
149    def = 32;
150    min = 28;
151    max = 32;
152    path = SCRAPE_PATH_64;
153    lib = SCRAPE_LIB_64;
154
155    compat = true;
156    def_cmpt = 16;
157    min_cmpt = 8;
158    max_cmpt = 16;
159
160#elif defined(__i386__)
161    def = 16;
162    min = 8;
163    max = 16;
164    path = SCRAPE_PATH_32;
165    lib = SCRAPE_LIB_32;
166
167    if (!access(PROCFS_COMPAT_PATH, F_OK)) {
168        // running 32 bit userspace over 64-bit kernel
169        user32 = true;
170        def_cmpt = 16;
171        min_cmpt = 8;
172        max_cmpt = 16;
173    }
174
175#elif defined(__aarch64__)
176    unsigned int pgbits = std::log2(getpagesize());
177    def = 24;
178    min = 18 - (pgbits - 12);
179    max = 24;
180    path = SCRAPE_PATH_64;
181    lib = SCRAPE_LIB_64;
182
183    compat = true;
184    def_cmpt = 16;
185    min_cmpt = 11 - (pgbits - 12);
186    max_cmpt = 16;
187
188#elif defined(__arm__)
189    unsigned int pgbits = std::log2(getpagesize());
190    def = 16;
191    min = 8;
192    max = 16;
193    path = SCRAPE_PATH_32;
194    lib = SCRAPE_LIB_32;
195
196    if (!access(PROCFS_COMPAT_PATH, F_OK)) {
197        // running 32 bit userspace over 64-bit kernel
198        user32 = true;
199        def_cmpt = 16;
200        min_cmpt = 11 - (pgbits - 12);;
201        max_cmpt = 16;
202    }
203#endif
204}
205
206void AslrMmapTest::TearDown() {
207    if (!user32)
208        set_mmap_rnd_bits(def, false);
209    if (user32 || compat)
210        set_mmap_rnd_bits(def_cmpt, true);
211}
212
213/* run tests only if on supported arch */
214#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) || defined(__arm__)
215
216TEST_F(AslrMmapTest, entropy_min_def) {
217    if (user32) {
218        // running 32-bit userspace on 64-bit kernel, only compat used.
219        return;
220    } else {
221        EXPECT_GE(def, calc_mmap_entropy(path, lib, 16));
222    }
223}
224
225TEST_F(AslrMmapTest, entropy_min_cmpt_def) {
226    if (compat || user32) {
227        EXPECT_GE(def_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16));
228    }
229}
230
231#endif /* supported arch */
232