• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 #pragma once
17 
18 //
19 // This file defines low-level primitives for handling SIGBUS signals that get
20 // generated when accessing memory mapped files on IncFS and hitting a missing
21 // page. These primitives provide a way for a process to not crash, but require
22 // careful attention to use properly.
23 //
24 // A slightly safer alternative is in "access.h", you probably want it unless
25 // the performance is so critical that a single function call overhead is
26 // unacceptable.
27 //
28 // Macros:
29 //   SCOPED_SIGBUS_HANDLER(code)
30 //   SCOPED_SIGBUS_HANDLER_CONDITIONAL(condition, code)
31 //
32 // Both macros set up a thread-local handler for SIGBUS, and make the (code)
33 // passed as a parameter run when it happens; _CONDITIONAL version only sets the
34 // handler up if the (condition) is true, making it easier to write generic
35 // code. (code) runs in a context of a current function. Macros work via a
36 // setjmp/longjmp machinery, and those result in the following pitfalls:
37 //
38 //   - Last action of the (code) MUST BE a return from the function. Current
39 //   state is too badly corrupted to continue execution.
40 //      -- Macro checks for code that failed to return and kills the process.
41 //   - C++ destructors of the objects created since the macro DO NOT RUN. (code)
42 //   has to manually clean up all of those.
43 //      -- this means ALL allocated memory, locked mutexes, created temp files,
44 //      etc.
45 //      -- what this really means is it's better to not do anything that needs
46 //      cleanup in the code protected with the macro
47 //   - Signal handler jumps out of any level of a nested function call straight
48 //   into (code); stack unwinding doesn't happen in a regular way - only the SP
49 //   gets reset. Nested functions have even stronger restrictions: no destructor
50 //   will run in them.
51 //      -- under no circumstance one may call a user-supplied callback in a code
52 //      protected with these macros. It's a recipe for a huge disaster.
53 //   - If some other code overrides the signal handler, protections cease to
54 //   work. Unfortunately, this is just the way signal handling works in Linux.
55 //
56 // Usage:
57 //
58 // In a function that will access mapped memory, as close to the access point as
59 // possible:
60 //
61 // int foo(char* mapped, long size) {
62 //      ...
63 //      SCOPED_SIGBUS_HANDLER(return -1);
64 //      for (...) {
65 //          <access |mapped|>
66 //      }
67 //      return 0;
68 // }
69 //
70 // If you need to perform some non-trivial cleanup:
71 //
72 //      ...
73 //      int fd = -1;
74 //      SCOPED_SIGBUS_HANDLER({
75 //          if (fd >= 0) close(fd);
76 //          return -1;
77 //      });
78 //      ...
79 //      <access |mapped|>
80 //      fd = open(...);
81 //      <access |mapped|>
82 //      ...
83 //
84 // Cleanup:
85 //  Pay attention when releasing the allocated resources - some functions may appear
86 //  to do that while in reality they aren't; use the function(s) from incfs_support/util.h to
87 //  do it safely, e.g.:
88 //
89 //      std::vector<int> numbers;
90 //      SCOPED_SIGBUS_HANDLER({
91 //          incfs::util::clearAndFree(numbers);
92 //          // putting 'numbers.clear()' here instead would leak it as vector doesn't free its
93 //          // capacity on that call, and SIGBUS may prevent its destructor from running.
94 //          return -1;
95 //      });
96 
97 // Performance: each macro sets up a couple of thread-local variables and calls
98 // a single syscall,
99 //  setjmp(). The only serious consideration is to  not call it on each
100 //  iteration of a tight loop, as syscalls aren't particularly instant. See
101 //  "incfs-support-benchmark" project for more details.
102 //
103 
104 #include <sys/types.h>
105 
106 #if !defined(__BIONIC__) || INCFS_SUPPORT_DISABLED
107 
108 // IncFS signal handling isn't needed anywhere but on Android as of now;
109 // or if we're told it's not desired.
110 #define SCOPED_SIGBUS_HANDLER(code)
111 #define SCOPED_SIGBUS_HANDLER_CONDITIONAL(condition, code)
112 
113 #else
114 
115 #ifndef LOG_TAG
116 #define LOG_TAG "incfs:hardening"
117 #endif
118 
119 #include <log/log.h>
120 #include <setjmp.h>
121 #include <signal.h>
122 #include <string.h>
123 
124 namespace incfs {
125 
126 struct JmpBufState final {
127   jmp_buf buf;
128   bool armed = false;
129 
130   JmpBufState() = default;
JmpBufStatefinal131   JmpBufState(const JmpBufState& other) {
132     if (other.armed) {
133       memcpy(&buf, &other.buf, sizeof(buf));
134       armed = true;
135     }
136   }
137 
138   JmpBufState& operator=(const JmpBufState& other) {
139     if (&other != this) {
140       if (other.armed) {
141         memcpy(&buf, &other.buf, sizeof(buf));
142         armed = true;
143       } else {
144         armed = false;
145       }
146     }
147     return *this;
148   }
149 };
150 
151 class ScopedJmpBuf final {
152  public:
ScopedJmpBuf(const JmpBufState & prev)153   ScopedJmpBuf(const JmpBufState& prev) : mPrev(prev) {}
154   ~ScopedJmpBuf();
155 
156   ScopedJmpBuf(const ScopedJmpBuf&) = delete;
157 
158  private:
159   const JmpBufState& mPrev;
160 };
161 
162 #define SCOPED_SIGBUS_HANDLER_CONDITIONAL(condition, code)                     \
163   (void)incfs::SignalHandler::instance();                                      \
164   auto& tlsBuf_macro = incfs::SignalHandler::mJmpBuf;                          \
165   incfs::JmpBufState oldBuf_macro = tlsBuf_macro;                              \
166   const bool condition_macro_val = (condition);                                \
167   if (condition_macro_val && setjmp(tlsBuf_macro.buf) != 0) {                  \
168     ALOGI("%s: handling SIGBUS at line %d", __func__, __LINE__);               \
169     tlsBuf_macro = oldBuf_macro;                                               \
170     do {                                                                       \
171       code;                                                                    \
172     } while (0);                                                               \
173     LOG_ALWAYS_FATAL("%s(): signal handler was supposed to return", __func__); \
174   }                                                                            \
175   tlsBuf_macro.armed |= (condition_macro_val);                                 \
176   incfs::ScopedJmpBuf oldBufRestore_macro(oldBuf_macro)
177 
178 #define SCOPED_SIGBUS_HANDLER(code) \
179   SCOPED_SIGBUS_HANDLER_CONDITIONAL(true, code)
180 
181 class SignalHandler final {
182  public:
183   static SignalHandler& instance();
184 
185  private:
186   SignalHandler();
187   inline static struct sigaction mOldSigaction = {};
188 
189   static void handler(int sig, siginfo_t* info, void* ucontext);
190 
191  public:
192   inline static thread_local JmpBufState mJmpBuf = {};
193 };
194 
195 }  // namespace incfs
196 
197 #endif
198