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