1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include "atexit.h"
30
31 #include <errno.h>
32 #include <pthread.h>
33 #include <stdint.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/mman.h>
37 #include <sys/param.h>
38 #include <sys/prctl.h>
39
40 #include <async_safe/CHECK.h>
41 #include <async_safe/log.h>
42
43 #include "platform/bionic/page.h"
44
45 extern "C" void __libc_stdio_cleanup();
46 extern "C" void __unregister_atfork(void* dso);
47
48 namespace {
49
50 struct AtexitEntry {
51 void (*fn)(void*); // the __cxa_atexit callback
52 void* arg; // argument for `fn` callback
53 void* dso; // shared module handle
54 };
55
56 class AtexitArray {
57 public:
size() const58 size_t size() const { return size_; }
total_appends() const59 uint64_t total_appends() const { return total_appends_; }
operator [](size_t idx) const60 const AtexitEntry& operator[](size_t idx) const { return array_[idx]; }
61
62 bool append_entry(const AtexitEntry& entry);
63 AtexitEntry extract_entry(size_t idx);
64 void recompact();
65
66 private:
67 AtexitEntry* array_;
68 size_t size_;
69 size_t extracted_count_;
70 size_t capacity_;
71
72 // An entry can be appended by a __cxa_finalize callback. Track the number of appends so we
73 // restart concurrent __cxa_finalize passes.
74 uint64_t total_appends_;
75
round_up_to_page_bytes(size_t capacity)76 static size_t round_up_to_page_bytes(size_t capacity) {
77 return PAGE_END(capacity * sizeof(AtexitEntry));
78 }
79
next_capacity(size_t capacity)80 static size_t next_capacity(size_t capacity) {
81 // Double the capacity each time.
82 size_t result = round_up_to_page_bytes(MAX(1, capacity * 2)) / sizeof(AtexitEntry);
83 CHECK(result > capacity);
84 return result;
85 }
86
87 // Recompact the array if it will save at least one page of memory at the end.
needs_recompaction()88 bool needs_recompaction() {
89 return round_up_to_page_bytes(size_ - extracted_count_) < round_up_to_page_bytes(size_);
90 }
91
92 void set_writable(bool writable);
93 bool expand_capacity();
94 };
95
96 } // anonymous namespace
97
append_entry(const AtexitEntry & entry)98 bool AtexitArray::append_entry(const AtexitEntry& entry) {
99 bool result = false;
100
101 set_writable(true);
102 if (size_ < capacity_ || expand_capacity()) {
103 array_[size_++] = entry;
104 ++total_appends_;
105 result = true;
106 }
107 set_writable(false);
108
109 return result;
110 }
111
112 // Extract an entry and return it.
extract_entry(size_t idx)113 AtexitEntry AtexitArray::extract_entry(size_t idx) {
114 AtexitEntry result = array_[idx];
115
116 set_writable(true);
117 array_[idx] = {};
118 ++extracted_count_;
119 set_writable(false);
120
121 return result;
122 }
123
recompact()124 void AtexitArray::recompact() {
125 if (!needs_recompaction()) return;
126
127 set_writable(true);
128
129 // Optimization: quickly skip over the initial non-null entries.
130 size_t src = 0, dst = 0;
131 while (src < size_ && array_[src].fn != nullptr) {
132 ++src;
133 ++dst;
134 }
135
136 // Shift the non-null entries forward, and zero out the removed entries at the end of the array.
137 for (; src < size_; ++src) {
138 const AtexitEntry entry = array_[src];
139 array_[src] = {};
140 if (entry.fn != nullptr) {
141 array_[dst++] = entry;
142 }
143 }
144
145 // If the table uses fewer pages, clean the pages at the end.
146 size_t old_bytes = round_up_to_page_bytes(size_);
147 size_t new_bytes = round_up_to_page_bytes(dst);
148 if (new_bytes < old_bytes) {
149 madvise(reinterpret_cast<char*>(array_) + new_bytes, old_bytes - new_bytes, MADV_DONTNEED);
150 }
151
152 size_ = dst;
153 extracted_count_ = 0;
154
155 set_writable(false);
156 }
157
158 // Use mprotect to make the array writable or read-only. Returns true on success. Making the array
159 // read-only could protect against either unintentional or malicious corruption of the array.
set_writable(bool writable)160 void AtexitArray::set_writable(bool writable) {
161 if (array_ == nullptr) return;
162 const int prot = PROT_READ | (writable ? PROT_WRITE : 0);
163 if (mprotect(array_, round_up_to_page_bytes(capacity_), prot) != 0) {
164 async_safe_fatal("mprotect failed on atexit array: %s", strerror(errno));
165 }
166 }
167
expand_capacity()168 bool AtexitArray::expand_capacity() {
169 const size_t new_capacity = next_capacity(capacity_);
170 const size_t new_capacity_bytes = round_up_to_page_bytes(new_capacity);
171
172 void* new_pages;
173 if (array_ == nullptr) {
174 new_pages = mmap(nullptr, new_capacity_bytes, PROT_READ | PROT_WRITE,
175 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
176 } else {
177 new_pages =
178 mremap(array_, round_up_to_page_bytes(capacity_), new_capacity_bytes, MREMAP_MAYMOVE);
179 }
180 if (new_pages == MAP_FAILED) {
181 async_safe_format_log(ANDROID_LOG_WARN, "libc",
182 "__cxa_atexit: mmap/mremap failed to allocate %zu bytes: %s",
183 new_capacity_bytes, strerror(errno));
184 return false;
185 }
186
187 prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, new_pages, new_capacity_bytes, "atexit handlers");
188 array_ = static_cast<AtexitEntry*>(new_pages);
189 capacity_ = new_capacity;
190 return true;
191 }
192
193 static AtexitArray g_array;
194 static pthread_mutex_t g_atexit_lock = PTHREAD_MUTEX_INITIALIZER;
195
atexit_lock()196 static inline void atexit_lock() {
197 pthread_mutex_lock(&g_atexit_lock);
198 }
199
atexit_unlock()200 static inline void atexit_unlock() {
201 pthread_mutex_unlock(&g_atexit_lock);
202 }
203
204 // Register a function to be called either when a library is unloaded (dso != nullptr), or when the
205 // program exits (dso == nullptr). The `dso` argument is typically the address of a hidden
206 // __dso_handle variable. This function is also used as the backend for the atexit function.
207 //
208 // See https://itanium-cxx-abi.github.io/cxx-abi/abi.html#dso-dtor.
209 //
__cxa_atexit(void (* func)(void *),void * arg,void * dso)210 int __cxa_atexit(void (*func)(void*), void* arg, void* dso) {
211 int result = -1;
212
213 if (func != nullptr) {
214 atexit_lock();
215 if (g_array.append_entry({.fn = func, .arg = arg, .dso = dso})) {
216 result = 0;
217 }
218 atexit_unlock();
219 }
220
221 return result;
222 }
223
__cxa_finalize(void * dso)224 void __cxa_finalize(void* dso) {
225 atexit_lock();
226
227 static uint32_t call_depth = 0;
228 ++call_depth;
229
230 restart:
231 const uint64_t total_appends = g_array.total_appends();
232
233 for (ssize_t i = g_array.size() - 1; i >= 0; --i) {
234 if (g_array[i].fn == nullptr || (dso != nullptr && g_array[i].dso != dso)) continue;
235
236 // Clear the entry in the array because its DSO handle will become invalid, and to avoid calling
237 // an entry again if __cxa_finalize is called recursively.
238 const AtexitEntry entry = g_array.extract_entry(i);
239
240 atexit_unlock();
241 entry.fn(entry.arg);
242 atexit_lock();
243
244 if (g_array.total_appends() != total_appends) goto restart;
245 }
246
247 // Avoid recompaction on recursive calls because it's unnecessary and would require earlier,
248 // concurrent __cxa_finalize calls to restart. Skip recompaction on program exit too
249 // (dso == nullptr), because the memory will be reclaimed soon anyway.
250 --call_depth;
251 if (call_depth == 0 && dso != nullptr) {
252 g_array.recompact();
253 }
254
255 atexit_unlock();
256
257 if (dso != nullptr) {
258 __unregister_atfork(dso);
259 } else {
260 // If called via exit(), flush output of all open files.
261 __libc_stdio_cleanup();
262 }
263 }
264