summaryrefslogtreecommitdiffstats
path: root/mozglue/misc/MmapFaultHandler.cpp
blob: a73b9bd24b7134480ea52c013c671281f1d79366 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "MmapFaultHandler.h"

#if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(__wasi__)

#  include "mozilla/Assertions.h"
#  include "mozilla/Atomics.h"
#  include "mozilla/ThreadLocal.h"
#  include <signal.h>
#  include <cstring>

static MOZ_THREAD_LOCAL(MmapAccessScope*) sMmapAccessScope;

static struct sigaction sPrevSIGBUSHandler;

static void MmapSIGBUSHandler(int signum, siginfo_t* info, void* context) {
  MOZ_RELEASE_ASSERT(signum == SIGBUS);

  MmapAccessScope* mas = sMmapAccessScope.get();

  if (mas && mas->IsInsideBuffer(info->si_addr)) {
    // Temporarily instead of handling the signal, we crash intentionally and
    // send some diagnostic information to find out why the signal is received.
    mas->CrashWithInfo(info->si_addr);

    // The address is inside the buffer, handle the failure.
    siglongjmp(mas->mJmpBuf, signum);
  }

  // This signal is not caused by accessing region protected by MmapAccessScope.
  // Forward the signal to the next handler.
  if (sPrevSIGBUSHandler.sa_flags & SA_SIGINFO) {
    sPrevSIGBUSHandler.sa_sigaction(signum, info, context);
  } else if (sPrevSIGBUSHandler.sa_handler == SIG_DFL ||
             sPrevSIGBUSHandler.sa_handler == SIG_IGN) {
    // There is no next handler. Uninstalling our handler and returning will
    // cause a crash.
    sigaction(signum, &sPrevSIGBUSHandler, nullptr);
  } else {
    sPrevSIGBUSHandler.sa_handler(signum);
  }
}

mozilla::Atomic<bool> gSIGBUSHandlerInstalled(false);
mozilla::Atomic<bool> gSIGBUSHandlerInstalling(false);

void InstallMmapFaultHandler() {
  // This function is called from MmapAccessScope's constructor because there is
  // no single point where we could install the handler during startup. This
  // means that it's called quite often, so to minimize using of the mutex we
  // first check the atomic variable outside the lock.
  if (gSIGBUSHandlerInstalled) {
    return;
  }

  if (gSIGBUSHandlerInstalling.compareExchange(false, true)) {
    sMmapAccessScope.infallibleInit();

    struct sigaction busHandler;
    busHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
    busHandler.sa_sigaction = MmapSIGBUSHandler;
    sigemptyset(&busHandler.sa_mask);
    if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler)) {
      MOZ_CRASH("Unable to install SIGBUS handler");
    }

    MOZ_ASSERT(!gSIGBUSHandlerInstalled);
    gSIGBUSHandlerInstalled = true;
  } else {
    // Just spin lock here. It should not take a substantial amount
    // of time, so a mutex would likely be a spin lock anyway, and
    // this avoids the need to new up a static mutex from within
    // mozglue/misc, which complicates things with
    // check_vanilla_allocations.py
    while (!gSIGBUSHandlerInstalled) {
    }
  }
}

MmapAccessScope::MmapAccessScope(void* aBuf, uint32_t aBufLen,
                                 const char* aFilename) {
  // Install signal handler if it wasn't installed yet.
  InstallMmapFaultHandler();

  // We'll handle the signal only if the crashing address is inside this buffer.
  mBuf = aBuf;
  mBufLen = aBufLen;
  mFilename = aFilename;

  SetThreadLocalScope();
}

MmapAccessScope::~MmapAccessScope() {
  MOZ_RELEASE_ASSERT(sMmapAccessScope.get() == this);
  sMmapAccessScope.set(mPreviousScope);
}

void MmapAccessScope::SetThreadLocalScope() {
  // mJmpBuf is set outside of this classs for reasons mentioned in the header
  // file, but we need to initialize the member here too to make Coverity happy.
  memset(mJmpBuf, 0, sizeof(sigjmp_buf));

  // If MmapAccessScopes are nested, save the previous one and restore it in
  // the destructor.
  mPreviousScope = sMmapAccessScope.get();

  // MmapAccessScope is now set up (except mJmpBuf for reasons mentioned in the
  // header file). Store the pointer in a thread-local variable sMmapAccessScope
  // so we can use it in the handler if the signal is triggered.
  sMmapAccessScope.set(this);
}

bool MmapAccessScope::IsInsideBuffer(void* aPtr) {
  return aPtr >= mBuf && aPtr < (void*)((char*)mBuf + mBufLen);
}

void MmapAccessScope::CrashWithInfo(void* aPtr) {
  // All we have is the buffer and the crashing address.
  MOZ_CRASH_UNSAFE_PRINTF(
      "SIGBUS received when accessing mmaped file [buffer=%p, "
      "buflen=%u, address=%p, filename=%s]",
      mBuf, mBufLen, aPtr, mFilename);
}

#endif