1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // A set of common helper functions used by crazy_linker tests.
6 // IMPORTANT: ALL FUNCTIONS HERE ARE INLINED. This avoids adding a
7 // dependency on another source file for all tests that include this
8 // header.
9 
10 #ifndef TEST_UTIL_H
11 #define TEST_UTIL_H
12 
13 #include <crazy_linker.h>
14 
15 #include <dirent.h>
16 #include <errno.h>
17 #include <limits.h>
18 #include <stdarg.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/mman.h>
22 #include <sys/socket.h>
23 #include <sys/stat.h>
24 #include <sys/uio.h>
25 #include <unistd.h>
26 #ifndef __STDC_FORMAT_MACROS
27 #define __STDC_FORMAT_MACROS // to get PRI and SCN in 32-bit inttypes.h
28 #endif
29 #include <inttypes.h>
30 
31 namespace {
32 
33 // Print an error message and exit the process.
34 // Message must be terminated by a newline.
Panic(const char * fmt,...)35 inline void Panic(const char* fmt, ...) {
36   va_list args;
37   fprintf(stderr, "PANIC: ");
38   va_start(args, fmt);
39   vfprintf(stderr, fmt, args);
40   va_end(args);
41   exit(1);
42 }
43 
44 // Print an error message, the errno message, then exit the process.
45 // Message must not be terminated by a newline.
PanicErrno(const char * fmt,...)46 inline void PanicErrno(const char* fmt, ...) {
47   int old_errno = errno;
48   va_list args;
49   fprintf(stderr, "PANIC: ");
50   va_start(args, fmt);
51   vfprintf(stderr, fmt, args);
52   va_end(args);
53   fprintf(stderr, ": %s\n", strerror(old_errno));
54   exit(1);
55 }
56 
57 // Simple string class.
58 class String {
59  public:
String()60   String() : str_(NULL), len_(0) {}
61 
String(const String & other)62   String(const String& other) { String(other.str_, other.len_); }
63 
String(const char * str)64   String(const char* str) { String(str, strlen(str)); }
65 
String(const char * str,size_t len)66   String(const char* str, size_t len) : str_(NULL), len_(0) {
67     Append(str, len);
68   }
69 
~String()70   ~String() {
71     if (str_) {
72       free(str_);
73       str_ = NULL;
74     }
75   }
76 
77   String& operator+=(const char* str) {
78     Append(str, strlen(str));
79     return *this;
80   }
81 
82   String& operator+=(const String& other) {
83     Append(other.str_, other.len_);
84     return *this;
85   }
86 
87   String& operator+=(char ch) {
88     Append(&ch, 1);
89     return *this;
90   }
91 
c_str()92   const char* c_str() const { return len_ ? str_ : ""; }
ptr()93   char* ptr() { return str_; }
size()94   size_t size() const { return len_; }
95 
Append(const char * str,size_t len)96   void Append(const char* str, size_t len) {
97     size_t old_len = len_;
98     Resize(len_ + len);
99     memcpy(str_ + old_len, str, len);
100   }
101 
Resize(size_t len)102   void Resize(size_t len) {
103     str_ = reinterpret_cast<char*>(realloc(str_, len + 1));
104     if (len > len_)
105       memset(str_ + len_, '\0', len - len_);
106     str_[len] = '\0';
107     len_ = len;
108   }
109 
Format(const char * fmt,...)110   void Format(const char* fmt, ...) {
111     va_list args;
112     va_start(args, fmt);
113     Resize(128);
114     for (;;) {
115       va_list args2;
116       va_copy(args2, args);
117       int ret = vsnprintf(str_, len_ + 1, fmt, args2);
118       va_end(args2);
119       if (ret < static_cast<int>(len_ + 1))
120         break;
121 
122       Resize(len_ * 2);
123     }
124   }
125 
126  private:
127   char* str_;
128   size_t len_;
129 };
130 
131 // Helper class to create a temporary directory that gets deleted on scope exit,
132 // as well as all regular files it contains.
133 class TempDirectory {
134  public:
TempDirectory()135   TempDirectory() {
136     snprintf(path_, sizeof path_, "/data/local/tmp/temp-XXXXXX");
137     if (!mktemp(path_))
138       Panic("Could not create temporary directory name: %s\n", strerror(errno));
139     if (mkdir(path_, 0700) < 0)
140       Panic("Could not create temporary directory %s: %s\n", strerror(errno));
141   }
142 
~TempDirectory()143   ~TempDirectory() {
144     // Remove any file in this directory.
145     DIR* d = opendir(path_);
146     if (!d)
147       Panic("Could not open directory %s: %s\n", strerror(errno));
148 
149     struct dirent* entry;
150     while ((entry = readdir(d)) != NULL) {
151       if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
152         continue;
153       String file_path;
154       file_path.Format("%s/%s", path_, entry->d_name);
155       if (unlink(file_path.c_str()) < 0)
156         Panic("Could not remove %s: %s\n", file_path.c_str(), strerror(errno));
157     }
158     closedir(d);
159 
160     if (rmdir(path_) < 0)
161       Panic("Could not remove dir %s: %s\n", path_, strerror(errno));
162   }
163 
path()164   const char* path() const { return path_; }
165 
166  private:
167   char path_[PATH_MAX];
168 };
169 
170 // Scoped FILE* class. Always closed on destruction.
171 class ScopedFILE {
172  public:
ScopedFILE()173   ScopedFILE() : file_(NULL) {}
174 
~ScopedFILE()175   ~ScopedFILE() {
176     if (file_) {
177       fclose(file_);
178       file_ = NULL;
179     }
180   }
181 
Open(const char * path,const char * mode)182   void Open(const char* path, const char* mode) {
183     file_ = fopen(path, mode);
184     if (!file_)
185       Panic("Could not open file for reading: %s: %s\n", path, strerror(errno));
186   }
187 
file()188   FILE* file() const { return file_; }
189 
190  private:
191   FILE* file_;
192 };
193 
194 // Retrieve current executable path as a String.
GetCurrentExecutable()195 inline String GetCurrentExecutable() {
196   String path;
197   path.Resize(PATH_MAX);
198   ssize_t ret =
199       TEMP_FAILURE_RETRY(readlink("/proc/self/exe", path.ptr(), path.size()));
200   if (ret < 0)
201     Panic("Could not read /proc/self/exe: %s\n", strerror(errno));
202 
203   return path;
204 }
205 
206 // Retrieve current executable directory as a String.
GetCurrentExecutableDirectory()207 inline String GetCurrentExecutableDirectory() {
208   String path = GetCurrentExecutable();
209   // Find basename.
210   const char* p = reinterpret_cast<const char*>(strrchr(path.c_str(), '/'));
211   if (p == NULL)
212     Panic("Current executable does not have directory root?: %s\n",
213           path.c_str());
214 
215   path.Resize(p - path.c_str());
216   return path;
217 }
218 
219 // Copy a file named |src_file_name| in directory |src_file_dir| into
220 // a file named |dst_file_name| in directory |dst_file_dir|. Panics on error.
CopyFile(const char * src_file_name,const char * src_file_dir,const char * dst_file_name,const char * dst_file_dir)221 inline void CopyFile(const char* src_file_name,
222                      const char* src_file_dir,
223                      const char* dst_file_name,
224                      const char* dst_file_dir) {
225   String src_path;
226   src_path.Format("%s/%s", src_file_dir, src_file_name);
227 
228   ScopedFILE src_file;
229   src_file.Open(src_path.c_str(), "rb");
230 
231   String dst_path;
232   dst_path.Format("%s/%s", dst_file_dir, dst_file_name);
233   ScopedFILE dst_file;
234   dst_file.Open(dst_path.c_str(), "wb");
235 
236   char buffer[8192];
237   for (;;) {
238     size_t read = fread(buffer, 1, sizeof buffer, src_file.file());
239     if (read > 0) {
240       size_t written = fwrite(buffer, 1, read, dst_file.file());
241       if (written != read)
242         Panic("Wrote %d bytes instead of %d into %s\n",
243               written,
244               read,
245               dst_path.c_str());
246     }
247     if (read < sizeof buffer)
248       break;
249   }
250 }
251 
252 // Send a file descriptor |fd| through |socket|.
253 // Return 0 on success, -1/errno on failure.
SendFd(int socket,int fd)254 inline int SendFd(int socket, int fd) {
255   struct iovec iov;
256 
257   char buffer[1];
258   buffer[0] = 0;
259 
260   iov.iov_base = buffer;
261   iov.iov_len = 1;
262 
263   struct msghdr msg;
264   struct cmsghdr* cmsg;
265   char cms[CMSG_SPACE(sizeof(int))];
266 
267   ::memset(&msg, 0, sizeof(msg));
268   msg.msg_iov = &iov;
269   msg.msg_iovlen = 1;
270   msg.msg_control = reinterpret_cast<caddr_t>(cms);
271   msg.msg_controllen = CMSG_LEN(sizeof(int));
272 
273   cmsg = CMSG_FIRSTHDR(&msg);
274   cmsg->cmsg_len = CMSG_LEN(sizeof(int));
275   cmsg->cmsg_level = SOL_SOCKET;
276   cmsg->cmsg_type = SCM_RIGHTS;
277   ::memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
278 
279   int ret = sendmsg(socket, &msg, 0);
280   if (ret < 0)
281     return -1;
282 
283   if (ret != iov.iov_len) {
284     errno = EIO;
285     return -1;
286   }
287 
288   return 0;
289 }
290 
ReceiveFd(int socket,int * fd)291 inline int ReceiveFd(int socket, int* fd) {
292   char buffer[1];
293   struct iovec iov;
294 
295   iov.iov_base = buffer;
296   iov.iov_len = 1;
297 
298   struct msghdr msg;
299   struct cmsghdr* cmsg;
300   char cms[CMSG_SPACE(sizeof(int))];
301 
302   ::memset(&msg, 0, sizeof msg);
303   msg.msg_name = 0;
304   msg.msg_namelen = 0;
305   msg.msg_iov = &iov;
306   msg.msg_iovlen = 1;
307 
308   msg.msg_control = reinterpret_cast<caddr_t>(cms);
309   msg.msg_controllen = sizeof(cms);
310 
311   int ret = recvmsg(socket, &msg, 0);
312   if (ret < 0)
313     return -1;
314   if (ret == 0) {
315     errno = EIO;
316     return -1;
317   }
318 
319   cmsg = CMSG_FIRSTHDR(&msg);
320   ::memcpy(fd, CMSG_DATA(cmsg), sizeof(int));
321   return 0;
322 }
323 
324 // Check that there are exactly |expected_count| memory mappings in
325 // /proc/self/maps that point to a RELRO ashmem region.
CheckRelroMaps(int expected_count)326 inline void CheckRelroMaps(int expected_count) {
327   printf("Checking for %d RELROs in /proc/self/maps\n", expected_count);
328 
329   FILE* file = fopen("/proc/self/maps", "rb");
330   if (!file)
331     Panic("Could not open /proc/self/maps (pid %d): %s\n",
332           getpid(),
333           strerror(errno));
334 
335   char line[512];
336   int count_relros = 0;
337   printf("proc/%d/maps:\n", getpid());
338   while (fgets(line, sizeof line, file)) {
339     if (strstr(line, "with_relro")) {
340       // The supported library names are "lib<name>_with_relro.so".
341       printf("%s", line);
342       if (strstr(line, "/dev/ashmem/RELRO:")) {
343         count_relros++;
344         // Check that they are read-only mappings.
345         if (!strstr(line, " r--"))
346           Panic("Shared RELRO mapping is not readonly!\n");
347         // Check that they can't be remapped read-write.
348         uint64_t vma_start, vma_end;
349         if (sscanf(line, "%" SCNx64 "-%" SCNx64, &vma_start, &vma_end) != 2)
350           Panic("Could not parse VM address range!\n");
351         int ret = ::mprotect(
352             (void*)vma_start, vma_end - vma_start, PROT_READ | PROT_WRITE);
353         if (ret == 0)
354           Panic("Could remap shared RELRO as writable, should not happen!\n");
355 
356         if (errno != EACCES)
357           Panic("remapping shared RELRO to writable failed with: %s\n",
358                 strerror(errno));
359       }
360     }
361   }
362   fclose(file);
363 
364   if (count_relros != expected_count)
365     Panic(
366         "Invalid shared RELRO sections in /proc/self/maps: %d"
367         " (expected %d)\n",
368         count_relros,
369         expected_count);
370 
371   printf("RELRO count check ok!\n");
372 }
373 
374 struct RelroInfo {
375   size_t start;
376   size_t size;
377   int fd;
378 };
379 
380 struct RelroLibrary {
381   const char* name;
382   crazy_library_t* library;
383   RelroInfo relro;
384 
InitRelroLibrary385   void Init(const char* name, crazy_context_t* context) {
386     printf("Loading %s\n", name);
387     this->name = name;
388     if (!crazy_library_open(&this->library, name, context)) {
389       Panic("Could not open %s: %s\n", name, crazy_context_get_error(context));
390     }
391   }
392 
CloseRelroLibrary393   void Close() { crazy_library_close(this->library); }
394 
CreateSharedRelroRelroLibrary395   void CreateSharedRelro(crazy_context_t* context, size_t load_address) {
396     if (!crazy_library_create_shared_relro(this->library,
397                                            context,
398                                            load_address,
399                                            &this->relro.start,
400                                            &this->relro.size,
401                                            &this->relro.fd)) {
402       Panic("Could not create shared RELRO for %s: %s",
403             this->name,
404             crazy_context_get_error(context));
405     }
406 
407     printf("Parent %s relro info relro_start=%p relro_size=%p relro_fd=%d\n",
408            this->name,
409            (void*)this->relro.start,
410            (void*)this->relro.size,
411            this->relro.fd);
412   }
413 
EnableSharedRelroRelroLibrary414   void EnableSharedRelro(crazy_context_t* context) {
415     CreateSharedRelro(context, 0);
416     UseSharedRelro(context);
417   }
418 
SendRelroInfoRelroLibrary419   void SendRelroInfo(int fd) {
420     if (SendFd(fd, this->relro.fd) < 0) {
421       Panic("Could not send %s RELRO fd: %s", this->name, strerror(errno));
422     }
423 
424     int ret =
425         TEMP_FAILURE_RETRY(::write(fd, &this->relro, sizeof(this->relro)));
426     if (ret != static_cast<int>(sizeof(this->relro))) {
427       Panic("Parent could not send %s RELRO info: %s",
428             this->name,
429             strerror(errno));
430     }
431   }
432 
ReceiveRelroInfoRelroLibrary433   void ReceiveRelroInfo(int fd) {
434     // Receive relro information from parent.
435     int relro_fd = -1;
436     if (ReceiveFd(fd, &relro_fd) < 0) {
437       Panic("Could not receive %s relro descriptor from parent", this->name);
438     }
439 
440     printf("Child received %s relro fd %d\n", this->name, relro_fd);
441 
442     int ret = TEMP_FAILURE_RETRY(::read(fd, &this->relro, sizeof(this->relro)));
443     if (ret != static_cast<int>(sizeof(this->relro))) {
444       Panic("Could not receive %s relro information from parent", this->name);
445     }
446 
447     this->relro.fd = relro_fd;
448     printf("Child received %s relro start=%p size=%p\n",
449            this->name,
450            (void*)this->relro.start,
451            (void*)this->relro.size);
452   }
453 
UseSharedRelroRelroLibrary454   void UseSharedRelro(crazy_context_t* context) {
455     if (!crazy_library_use_shared_relro(this->library,
456                                         context,
457                                         this->relro.start,
458                                         this->relro.size,
459                                         this->relro.fd)) {
460       Panic("Could not use %s shared RELRO: %s\n",
461             this->name,
462             crazy_context_get_error(context));
463     }
464   }
465 };
466 
467 }  // namespace
468 
469 #endif  // TEST_UTIL_H
470