1 /*
2  * Copyright (C) 2015 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 #define LOG_TAG "crash_collector"
18 
19 #include "coredump_writer.h"
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <sys/statvfs.h>
24 #include <unistd.h>
25 
26 #include <android-base/file.h>
27 #include <log/logger.h>
28 #include <nativehelper/ScopedFd.h>
29 
30 // From external/google-breakpad.
31 #include "common/linux/elf_core_dump.h"
32 
33 namespace {
34 
35 const size_t kMaxCoredumpSize = 256 * 1024 * 1024;
36 
GetFreeDiskSpace(const std::string & path)37 int64_t GetFreeDiskSpace(const std::string& path) {
38   struct statvfs stats;
39   if (TEMP_FAILURE_RETRY(statvfs(path.c_str(), &stats)) != 0) {
40     ALOGE("statvfs() failed. errno = %d", errno);
41     return -1;
42   }
43   return static_cast<int64_t>(stats.f_bavail) * stats.f_frsize;
44 }
45 
Seek(int fd,off_t offset)46 bool Seek(int fd, off_t offset) {
47   return lseek(fd, offset, SEEK_SET) == offset;
48 }
49 
50 template<typename T>
GetValueFromNote(const google_breakpad::ElfCoreDump::Note & note,size_t offset,T default_value)51 T GetValueFromNote(const google_breakpad::ElfCoreDump::Note& note,
52                    size_t offset,
53                    T default_value) {
54   const T* p = note.GetDescription().GetData<T>(offset);
55   return p ? *p : default_value;
56 }
57 
58 }  // namespace
59 
60 class CoredumpWriter::FdReader {
61  public:
FdReader(int fd)62   explicit FdReader(int fd) : fd_(fd), bytes_read_(0) {}
63 
64   // Reads the given number of bytes.
Read(void * buf,size_t num_bytes)65   bool Read(void* buf, size_t num_bytes) {
66     if (!android::base::ReadFully(fd_, buf, num_bytes))
67       return false;
68     bytes_read_ += num_bytes;
69     return true;
70   }
71 
72   // Reads the given number of bytes and writes it to fd_dest.
CopyTo(int fd_dest,size_t num_bytes)73   bool CopyTo(int fd_dest, size_t num_bytes) {
74     const size_t kBufSize = 32768;
75     char buf[kBufSize];
76     while (num_bytes > 0) {
77       int rv = TEMP_FAILURE_RETRY(
78           read(fd_, buf, std::min(kBufSize, num_bytes)));
79       if (rv == 0)
80         break;
81       if (rv == -1)
82         return false;
83       if (fd_dest != -1 && !android::base::WriteFully(fd_dest, buf, rv))
84         return false;
85       num_bytes -= rv;
86       bytes_read_ += rv;
87     }
88     return num_bytes == 0;
89   }
90 
91   // Reads data and discards it to get to the specified position.
Seek(size_t offset)92   bool Seek(size_t offset) {
93     if (offset < bytes_read_)  // Cannot move backward.
94       return false;
95     return CopyTo(-1, offset - bytes_read_);
96   }
97 
98  private:
99   int fd_;
100   size_t bytes_read_;
101 
102   DISALLOW_COPY_AND_ASSIGN(FdReader);
103 };
104 
CoredumpWriter(int fd_src,const std::string & coredump_filename,const std::string & proc_files_dir)105 CoredumpWriter::CoredumpWriter(int fd_src,
106                                const std::string& coredump_filename,
107                                const std::string& proc_files_dir)
108     : fd_src_(fd_src),
109       coredump_filename_(coredump_filename),
110       proc_files_dir_(proc_files_dir) {
111 }
112 
~CoredumpWriter()113 CoredumpWriter::~CoredumpWriter() {
114 }
115 
WriteCoredump()116 ssize_t CoredumpWriter::WriteCoredump() {
117   ScopedFd fd_dest(TEMP_FAILURE_RETRY(open(coredump_filename_.c_str(),
118                                            O_WRONLY | O_CREAT | O_EXCL,
119                                            S_IRUSR | S_IWUSR)));
120   if (fd_dest.get() == -1) {
121     ALOGE("Failed to open: %s, errno = %d", coredump_filename_.c_str(), errno);
122     return -1;
123   }
124   ssize_t result = WriteCoredumpToFD(fd_dest.get());
125   fd_dest.reset();
126   if (result == -1)
127     unlink(coredump_filename_.c_str());
128   return result;
129 }
130 
WriteCoredumpToFD(int fd_dest)131 ssize_t CoredumpWriter::WriteCoredumpToFD(int fd_dest) {
132   // Input coredump is generated by kernel's fs/binfmt_elf.c and formatted like:
133   //
134   //   ELF Header
135   //   Program Header 1
136   //   Program Header 2
137   //   ...
138   //   Program Header n
139   //   Segment 1 (This segment's type should be PT_NOTE)
140   //   Segment 2
141   //   ...
142   //   Segment n
143 
144   // First, read ELF Header, all program headers, and the first segment whose
145   // type is PT_NOTE.
146   FdReader reader(fd_src_);
147   Ehdr elf_header;
148   std::vector<Phdr> program_headers;
149   std::vector<char> note_buf;
150   if (!ReadUntilNote(&reader, &elf_header, &program_headers, &note_buf)) {
151     return -1;
152   }
153   // Get a set of address ranges occupied by mapped files from NOTE.
154   FileMappings file_mappings;
155   if (!GetFileMappings(note_buf, &file_mappings)) {
156     return -1;
157   }
158   // Filter out segments backed by mapped files as they are useless when
159   // generating minidump.
160   std::vector<Phdr> program_headers_filtered;
161   FilterSegments(program_headers, file_mappings, &program_headers_filtered);
162 
163   // Calculate the coredump size limit.
164   const int64_t free_disk_space = GetFreeDiskSpace(coredump_filename_);
165   if (free_disk_space < 0) {
166     return -1;
167   }
168   coredump_size_limit_ = std::min(static_cast<size_t>(free_disk_space / 20),
169                                   kMaxCoredumpSize);
170 
171   // Calculate the output file size.
172   expected_coredump_size_ = program_headers_filtered.back().p_offset +
173       program_headers_filtered.back().p_filesz;
174   if (expected_coredump_size_ > coredump_size_limit_) {
175     ALOGE("Coredump too large: %zu", expected_coredump_size_);
176     return -1;
177   }
178 
179   // Write proc files.
180   if (!WriteAuxv(note_buf, proc_files_dir_ + "/auxv") ||
181       !WriteMaps(program_headers, file_mappings, proc_files_dir_ + "/maps")) {
182     return -1;
183   }
184 
185   // Write ELF header.
186   if (!android::base::WriteFully(fd_dest, &elf_header, sizeof(elf_header))) {
187     ALOGE("Failed to write ELF header.");
188     return -1;
189   }
190   // Write program headers.
191   for (size_t i = 0; i < program_headers_filtered.size(); ++i) {
192     const Phdr& program_header = program_headers_filtered[i];
193     const size_t offset = sizeof(elf_header) + i * elf_header.e_phentsize;
194     if (!Seek(fd_dest, offset) ||
195         !android::base::WriteFully(fd_dest, &program_header,
196                                    sizeof(program_header))) {
197       ALOGE("Failed to write program header: i = %zu", i);
198       return -1;
199     }
200   }
201   // Write NOTE segment.
202   if (!Seek(fd_dest, program_headers_filtered[0].p_offset) ||
203       !android::base::WriteFully(fd_dest, note_buf.data(), note_buf.size())) {
204     ALOGE("Failed to write NOTE.");
205     return -1;
206   }
207   // Read all remaining segments and write some of them.
208   for (size_t i = 1; i < program_headers_filtered.size(); ++i) {
209     const Phdr& program_header = program_headers_filtered[i];
210     if (program_header.p_filesz > 0) {
211       const Phdr& program_header_original = program_headers[i];
212       if (!reader.Seek(program_header_original.p_offset)) {
213         ALOGE("Failed to seek segment: i = %zu", i);
214         return -1;
215       }
216       if (!Seek(fd_dest, program_header.p_offset) ||
217           !reader.CopyTo(fd_dest, program_header.p_filesz)) {
218         ALOGE("Failed to write segment: i = %zu", i);
219         return -1;
220       }
221     }
222   }
223   return expected_coredump_size_;
224 }
225 
ReadUntilNote(FdReader * reader,Ehdr * elf_header,std::vector<Phdr> * program_headers,std::vector<char> * note_buf)226 bool CoredumpWriter::ReadUntilNote(FdReader* reader,
227                                    Ehdr* elf_header,
228                                    std::vector<Phdr>* program_headers,
229                                    std::vector<char>* note_buf) {
230   // Read ELF header.
231   if (!reader->Read(elf_header, sizeof(*elf_header)) ||
232       memcmp(elf_header->e_ident, ELFMAG, SELFMAG) != 0 ||
233       elf_header->e_ident[EI_CLASS] != google_breakpad::ElfCoreDump::kClass ||
234       elf_header->e_version != EV_CURRENT ||
235       elf_header->e_type != ET_CORE ||
236       elf_header->e_ehsize != sizeof(Ehdr) ||
237       elf_header->e_phentsize != sizeof(Phdr)) {
238     ALOGE("Failed to read ELF header.");
239     return false;
240   }
241 
242   // Read program headers;
243   program_headers->resize(elf_header->e_phnum);
244   if (!reader->Seek(elf_header->e_phoff) ||
245       !reader->Read(program_headers->data(),
246                     sizeof(Phdr) * program_headers->size())) {
247     ALOGE("Failed to read program headers.");
248     return false;
249   }
250 
251   // The first segment should be NOTE.
252   if (program_headers->size() < 1 ||
253       (*program_headers)[0].p_type != PT_NOTE) {
254     ALOGE("Failed to locate NOTE.");
255     return false;
256   }
257   const Phdr& note_program_header = (*program_headers)[0];
258 
259   // Read NOTE segment.
260   note_buf->resize(note_program_header.p_filesz);
261   if (!reader->Seek(note_program_header.p_offset) ||
262       !reader->Read(note_buf->data(), note_buf->size())) {
263     ALOGE("Failed to read NOTE.");
264     return false;
265   }
266   return true;
267 }
268 
GetFileMappings(const std::vector<char> & note_buf,FileMappings * file_mappings)269 bool CoredumpWriter::GetFileMappings(const std::vector<char>& note_buf,
270                                      FileMappings* file_mappings) {
271   // Locate FILE note.
272   google_breakpad::ElfCoreDump::Note note(
273       google_breakpad::MemoryRange(note_buf.data(), note_buf.size()));
274   while (note.IsValid() && note.GetType() != NT_FILE) {
275     note = note.GetNextNote();
276   }
277   if (!note.IsValid()) {
278     ALOGE("Failed to locate NT_FILE.");
279     return false;
280   }
281 
282   // NT_FILE note format: (see kernel's fs/binfmt_elf.c for details)
283   //   Number of mapped files
284   //   Page size
285   //   Start address of file 1
286   //   End address of file 1
287   //   Offset of file 1
288   //   Start address of file 2
289   //   ...
290   //   Offset of file n
291   //   File name 1 (null-terminated)
292   //   File name 2
293   //   ...
294   //   File name n
295   const long kInvalidValue = -1;
296   const long file_count = GetValueFromNote<long>(note, 0, kInvalidValue);
297   const long page_size = GetValueFromNote<long>(note, sizeof(long),
298                                                 kInvalidValue);
299   if (file_count == kInvalidValue || page_size == kInvalidValue) {
300     ALOGE("Invalid FILE note.");
301     return false;
302   }
303   // Read contents of FILE note.
304   size_t filename_pos = sizeof(long) * (2 + 3 * file_count);
305   for (long i = 0; i < file_count; ++i) {
306     const long start = GetValueFromNote<long>(
307         note, sizeof(long) * (2 + 3 * i), kInvalidValue);
308     const long end = GetValueFromNote<long>(
309         note, sizeof(long) * (2 + 3 * i + 1), kInvalidValue);
310     const long offset = GetValueFromNote<long>(
311         note, sizeof(long) * (2 + 3 * i + 2), kInvalidValue);
312     if (start == kInvalidValue || end == kInvalidValue ||
313         offset == kInvalidValue) {
314       ALOGE("Invalid FILE Note.");
315       return false;
316     }
317     // Add a new mapping.
318     FileInfo& info = (*file_mappings)[std::make_pair(start, end)];
319     info.offset = offset * page_size;
320     // Read file name.
321     while (true) {
322       const char c = GetValueFromNote<char>(note, filename_pos++, 0);
323       if (!c)
324         break;
325       info.path.push_back(c);
326     }
327   }
328   return true;
329 }
330 
FilterSegments(const std::vector<Phdr> & program_headers,const FileMappings & file_mappings,std::vector<Phdr> * program_headers_filtered)331 void CoredumpWriter::FilterSegments(
332     const std::vector<Phdr>& program_headers,
333     const FileMappings& file_mappings,
334     std::vector<Phdr>* program_headers_filtered) {
335   program_headers_filtered->resize(program_headers.size());
336 
337   // The first segment is NOTE. Use the original data unchanged.
338   (*program_headers_filtered)[0] = program_headers[0];
339 
340   for (size_t i = 1; i < program_headers.size(); ++i) {
341     Phdr& out = (*program_headers_filtered)[i];
342     out = program_headers[i];
343 
344     // If the type is PT_LOAD and the range is found in the set, it means the
345     // segment is backed by a file.  So it can be excluded as it doesn't cotnain
346     // stack data useful to generate minidump.
347     const FileRange range(out.p_vaddr, out.p_vaddr + out.p_memsz);
348     if (out.p_type == PT_LOAD && file_mappings.count(range)) {
349       out.p_filesz = 0;
350     }
351     // Calculate offset.
352     const Phdr& prev_program_header = (*program_headers_filtered)[i - 1];
353     out.p_offset = prev_program_header.p_offset + prev_program_header.p_filesz;
354     // Offset alignment.
355     if (out.p_align != 0 && out.p_offset % out.p_align != 0) {
356       out.p_offset += out.p_align - out.p_offset % out.p_align;
357     }
358   }
359 }
360 
WriteAuxv(const std::vector<char> & note_buf,const std::string & output_path)361 bool CoredumpWriter::WriteAuxv(const std::vector<char>& note_buf,
362                                const std::string& output_path) {
363   // Locate AUXV note.
364   google_breakpad::ElfCoreDump::Note note(
365       google_breakpad::MemoryRange(note_buf.data(), note_buf.size()));
366   while (note.IsValid() && note.GetType() != NT_AUXV) {
367     note = note.GetNextNote();
368   }
369   if (!note.IsValid()) {
370     ALOGE("Failed to locate NT_AUXV.");
371     return false;
372   }
373 
374   ScopedFd fd(TEMP_FAILURE_RETRY(open(
375       output_path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | O_EXCL,
376       S_IRUSR | S_IWUSR)));
377   if (fd.get() == -1) {
378     ALOGE("Failed to open %s", output_path.c_str());
379     return false;
380   }
381   // The contents of NT_AUXV is in the same format as that of /proc/[pid]/auxv.
382   return android::base::WriteFully(
383       fd.get(), note.GetDescription().data(), note.GetDescription().length());
384 }
385 
WriteMaps(const std::vector<Phdr> & program_headers,const FileMappings & file_mappings,const std::string & output_path)386 bool CoredumpWriter::WriteMaps(const std::vector<Phdr>& program_headers,
387                                const FileMappings& file_mappings,
388                                const std::string& output_path) {
389   ScopedFd fd(TEMP_FAILURE_RETRY(open(
390       output_path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | O_EXCL,
391       S_IRUSR | S_IWUSR)));
392   if (fd.get() == -1) {
393     ALOGE("Failed to open %s", output_path.c_str());
394     return false;
395   }
396   for (const auto& program_header : program_headers) {
397     if (program_header.p_type != PT_LOAD)
398       continue;
399     const FileRange range(program_header.p_vaddr,
400                           program_header.p_vaddr + program_header.p_memsz);
401     // If a mapping is found for the range, the range is mapped to a file.
402     const auto it = file_mappings.find(range);
403     const long offset = it != file_mappings.end() ? it->second.offset : 0;
404     const std::string path = it != file_mappings.end() ? it->second.path : "";
405 
406     const int kBufSize = 1024;
407     char buf[kBufSize];
408     const int len = snprintf(
409         buf, kBufSize, "%08lx-%08lx %c%c%c%c %08lx %02x:%02x %d %s\n",
410         range.first, range.second,
411         program_header.p_flags & PF_R ? 'r' : '-',
412         program_header.p_flags & PF_W ? 'w' : '-',
413         program_header.p_flags & PF_X ? 'x' : '-',
414         'p',  // Fake value: We can't know if the mapping is shared or private.
415         offset,
416         0,  // Fake device (major) value.
417         0,  // Fake device (minor) value.
418         0,  // Fake inode value.
419         path.c_str());
420     if (len < 0 || len > kBufSize ||
421         !android::base::WriteFully(fd.get(), buf, len)) {
422       return false;
423     }
424   }
425   return true;
426 }
427