Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ba5d9ff6 authored by Colin Cross's avatar Colin Cross
Browse files

Handle segfaults while walking native heap

Vendor blobs on ryu mprotect heap pages, causing segfaults when dumping
unreachable memory.  Handle segfaults within HeapWalker by mapping a
zero page over any unreadable pages.  HeapWalker runs in the forked
process, so the mapping will not affect the original process.

Bug: 28269332
Change-Id: I16245af722123f2ad467cbc6f245a70666c55544
parent 721c9ce4
Loading
Loading
Loading
Loading
+40 −3
Original line number Diff line number Diff line
@@ -14,7 +14,10 @@
 * limitations under the License.
 */

#include <errno.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <unistd.h>

#include <map>
#include <utility>
@@ -22,6 +25,7 @@
#include "Allocator.h"
#include "HeapWalker.h"
#include "LeakFolding.h"
#include "ScopedSignalHandler.h"
#include "log.h"

bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) {
@@ -46,9 +50,15 @@ bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) {
  }
}

bool HeapWalker::IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info) {
  if (ptr >= valid_allocations_range_.begin && ptr < valid_allocations_range_.end) {
    AllocationMap::iterator it = allocations_.find(Range{ptr, ptr + 1});
bool HeapWalker::WordContainsAllocationPtr(uintptr_t word_ptr, Range* range, AllocationInfo** info) {
  walking_ptr_ = word_ptr;
  // This access may segfault if the process under test has done something strange,
  // for example mprotect(PROT_NONE) on a native heap page.  If so, it will be
  // caught and handled by mmaping a zero page over the faulting page.
  uintptr_t value = *reinterpret_cast<uintptr_t*>(word_ptr);
  walking_ptr_ = 0;
  if (value >= valid_allocations_range_.begin && value < valid_allocations_range_.end) {
    AllocationMap::iterator it = allocations_.find(Range{value, value + 1});
    if (it != allocations_.end()) {
      *range = it->first;
      *info = &it->second;
@@ -135,3 +145,30 @@ bool HeapWalker::Leaked(allocator::vector<Range>& leaked, size_t limit,

  return true;
}

static bool MapOverPage(void* addr) {
  const size_t page_size = sysconf(_SC_PAGE_SIZE);
  void *page = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & ~(page_size-1));

  void* ret = mmap(page, page_size, PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0);
  if (ret == MAP_FAILED) {
    ALOGE("failed to map page at %p: %s", page, strerror(errno));
    return false;
  }

  return true;
}

void HeapWalker::HandleSegFault(ScopedSignalHandler& handler, int signal, siginfo_t* si, void* /*uctx*/) {
  uintptr_t addr = reinterpret_cast<uintptr_t>(si->si_addr);
  if (addr != walking_ptr_) {
    handler.reset();
    return;
  }
  ALOGW("failed to read page at %p, signal %d", si->si_addr, signal);
  if (!MapOverPage(si->si_addr)) {
    handler.reset();
  }
}

ScopedSignalHandler::SignalFn ScopedSignalHandler::handler_;
+17 −3
Original line number Diff line number Diff line
@@ -17,9 +17,12 @@
#ifndef LIBMEMUNREACHABLE_HEAP_WALKER_H_
#define LIBMEMUNREACHABLE_HEAP_WALKER_H_

#include <signal.h>

#include "android-base/macros.h"

#include "Allocator.h"
#include "ScopedSignalHandler.h"
#include "Tarjan.h"

// A range [begin, end)
@@ -41,10 +44,17 @@ class HeapWalker {
 public:
  HeapWalker(Allocator<HeapWalker> allocator) : allocator_(allocator),
    allocations_(allocator), allocation_bytes_(0),
	roots_(allocator), root_vals_(allocator) {
	roots_(allocator), root_vals_(allocator),
	segv_handler_(allocator), walking_ptr_(0) {
    valid_allocations_range_.end = 0;
    valid_allocations_range_.begin = ~valid_allocations_range_.end;

    segv_handler_.install(SIGSEGV,
        [=](ScopedSignalHandler& handler, int signal, siginfo_t* siginfo, void* uctx) {
          this->HandleSegFault(handler, signal, siginfo, uctx);
      });
  }

  ~HeapWalker() {}
  bool Allocation(uintptr_t begin, uintptr_t end);
  void Root(uintptr_t begin, uintptr_t end);
@@ -70,7 +80,8 @@ class HeapWalker {
 private:

  void RecurseRoot(const Range& root);
  bool IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info);
  bool WordContainsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info);
  void HandleSegFault(ScopedSignalHandler&, int, siginfo_t*, void*);

  DISALLOW_COPY_AND_ASSIGN(HeapWalker);
  Allocator<HeapWalker> allocator_;
@@ -81,6 +92,9 @@ class HeapWalker {

  allocator::vector<Range> roots_;
  allocator::vector<uintptr_t> root_vals_;

  ScopedSignalHandler segv_handler_;
  uintptr_t walking_ptr_;
};

template<class F>
@@ -92,7 +106,7 @@ inline void HeapWalker::ForEachPtrInRange(const Range& range, F&& f) {
  for (uintptr_t i = begin; i < range.end; i += sizeof(uintptr_t)) {
    Range ref_range;
    AllocationInfo* ref_info;
    if (IsAllocationPtr(*reinterpret_cast<uintptr_t*>(i), &ref_range, &ref_info)) {
    if (WordContainsAllocationPtr(i, &ref_range, &ref_info)) {
      f(ref_range, ref_info);
    }
  }
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_
#define LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_

#include <errno.h>
#include <signal.h>

#include <functional>

#include "android-base/macros.h"

#include "log.h"

class ScopedSignalHandler {
 public:
  using Fn = std::function<void(ScopedSignalHandler&, int, siginfo_t*, void*)>;

  ScopedSignalHandler(Allocator<Fn> allocator) : allocator_(allocator), signal_(-1) {}
  ~ScopedSignalHandler() {
    reset();
  }

  template <class F>
  void install(int signal, F&& f) {
    LOG_ALWAYS_FATAL_IF(signal_ != -1, "ScopedSignalHandler already installed");

    handler_ = SignalFn(std::allocator_arg, allocator_,
        [=](int signal, siginfo_t* si, void* uctx) {
          f(*this, signal, si, uctx);
        });

    struct sigaction act{};
    act.sa_sigaction = [](int signal, siginfo_t* si, void* uctx) {
      handler_(signal, si, uctx);
    };
    act.sa_flags = SA_SIGINFO;

    int ret = sigaction(signal, &act, &old_act_);
    if (ret < 0) {
      LOG_ALWAYS_FATAL("failed to install segfault handler: %s", strerror(errno));
    }

    signal_ = signal;
  }

  void reset() {
    if (signal_ != -1) {
      int ret = sigaction(signal_, &old_act_, NULL);
      if (ret < 0) {
        ALOGE("failed to uninstall segfault handler");
      }
      handler_ = SignalFn{};
      signal_ = -1;
    }
  }


 private:
  using SignalFn = std::function<void(int, siginfo_t*, void*)>;
  DISALLOW_COPY_AND_ASSIGN(ScopedSignalHandler);
  Allocator<Fn> allocator_;
  int signal_;
  struct sigaction old_act_;
  // TODO(ccross): to support multiple ScopedSignalHandlers handler_ would need
  // to be a static map of signals to handlers, but allocated with Allocator.
  static SignalFn handler_;
};

#endif // LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_
+27 −0
Original line number Diff line number Diff line
@@ -14,6 +14,9 @@
 * limitations under the License.
 */

#include <sys/mman.h>
#include <unistd.h>

#include "HeapWalker.h"

#include <gtest/gtest.h>
@@ -172,3 +175,27 @@ TEST_F(HeapWalkerTest, cycle) {
  EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes);
  ASSERT_EQ(2U, leaked.size());
}

TEST_F(HeapWalkerTest, segv) {
  const size_t page_size = sysconf(_SC_PAGE_SIZE);
  void* buffer1 = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
  ASSERT_NE(buffer1, nullptr);
  void* buffer2;

  buffer2 = &buffer1;

  HeapWalker heap_walker(heap_);
  heap_walker.Allocation(buffer_begin(buffer1), buffer_begin(buffer1)+page_size);
  heap_walker.Root(buffer_begin(buffer2), buffer_end(buffer2));

  ASSERT_EQ(true, heap_walker.DetectLeaks());

  allocator::vector<Range> leaked(heap_);
  size_t num_leaks = 0;
  size_t leaked_bytes = 0;
  ASSERT_EQ(true, heap_walker.Leaked(leaked, 100, &num_leaks, &leaked_bytes));

  EXPECT_EQ(0U, num_leaks);
  EXPECT_EQ(0U, leaked_bytes);
  ASSERT_EQ(0U, leaked.size());
}