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