1 /******************************************************************************
2  *
3  *  Copyright (C) 2014 Google, Inc.
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at:
8  *
9  *  http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ******************************************************************************/
18 
19 #define LOG_TAG "bt_osi_allocation_tracker"
20 
21 #include "osi/include/allocation_tracker.h"
22 
23 #include <assert.h>
24 #include <pthread.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "osi/include/allocator.h"
29 #include "osi/include/hash_functions.h"
30 #include "osi/include/hash_map.h"
31 #include "osi/include/log.h"
32 #include "osi/include/osi.h"
33 
34 typedef struct {
35   uint8_t allocator_id;
36   void *ptr;
37   size_t size;
38   bool freed;
39 } allocation_t;
40 
41 // Hidden constructor for hash map for our use only. Everything else should use the
42 // normal interface.
43 hash_map_t *hash_map_new_internal(
44     size_t size,
45     hash_index_fn hash_fn,
46     key_free_fn key_fn,
47     data_free_fn,
48     key_equality_fn equality_fn,
49     const allocator_t *zeroed_allocator);
50 
51 static bool allocation_entry_freed_checker(hash_map_entry_t *entry, void *context);
52 static void *untracked_calloc(size_t size);
53 
54 static const size_t allocation_hash_map_size = 1024;
55 static const char *canary = "tinybird";
56 static const allocator_t untracked_calloc_allocator = {
57   untracked_calloc,
58   free
59 };
60 
61 static size_t canary_size;
62 static hash_map_t *allocations;
63 static pthread_mutex_t lock;
64 
allocation_tracker_init(void)65 void allocation_tracker_init(void) {
66   if (allocations)
67     return;
68 
69   canary_size = strlen(canary);
70 
71   pthread_mutex_init(&lock, NULL);
72 
73   pthread_mutex_lock(&lock);
74   allocations = hash_map_new_internal(
75     allocation_hash_map_size,
76     hash_function_pointer,
77     NULL,
78     free,
79     NULL,
80     &untracked_calloc_allocator);
81   pthread_mutex_unlock(&lock);
82 }
83 
84 // Test function only. Do not call in the normal course of operations.
allocation_tracker_uninit(void)85 void allocation_tracker_uninit(void) {
86   if (!allocations)
87     return;
88 
89   pthread_mutex_lock(&lock);
90   hash_map_free(allocations);
91   allocations = NULL;
92   pthread_mutex_unlock(&lock);
93 }
94 
allocation_tracker_reset(void)95 void allocation_tracker_reset(void) {
96   if (!allocations)
97     return;
98 
99   pthread_mutex_lock(&lock);
100   hash_map_clear(allocations);
101   pthread_mutex_unlock(&lock);
102 }
103 
allocation_tracker_expect_no_allocations(void)104 size_t allocation_tracker_expect_no_allocations(void) {
105   if (!allocations)
106     return 0;
107 
108   pthread_mutex_lock(&lock);
109 
110   size_t unfreed_memory_size = 0;
111   hash_map_foreach(allocations, allocation_entry_freed_checker, &unfreed_memory_size);
112 
113   pthread_mutex_unlock(&lock);
114 
115   return unfreed_memory_size;
116 }
117 
allocation_tracker_notify_alloc(uint8_t allocator_id,void * ptr,size_t requested_size)118 void *allocation_tracker_notify_alloc(uint8_t allocator_id, void *ptr, size_t requested_size) {
119   if (!allocations || !ptr)
120     return ptr;
121 
122   char *return_ptr = (char *)ptr;
123 
124   return_ptr += canary_size;
125 
126   pthread_mutex_lock(&lock);
127 
128   allocation_t *allocation = (allocation_t *)hash_map_get(allocations, return_ptr);
129   if (allocation) {
130     assert(allocation->freed); // Must have been freed before
131   } else {
132     allocation = (allocation_t *)calloc(1, sizeof(allocation_t));
133     hash_map_set(allocations, return_ptr, allocation);
134   }
135 
136   allocation->allocator_id = allocator_id;
137   allocation->freed = false;
138   allocation->size = requested_size;
139   allocation->ptr = return_ptr;
140 
141   pthread_mutex_unlock(&lock);
142 
143   // Add the canary on both sides
144   memcpy(return_ptr - canary_size, canary, canary_size);
145   memcpy(return_ptr + requested_size, canary, canary_size);
146 
147   return return_ptr;
148 }
149 
allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id,void * ptr)150 void *allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id, void *ptr) {
151   if (!allocations || !ptr)
152     return ptr;
153 
154   pthread_mutex_lock(&lock);
155 
156   allocation_t *allocation = (allocation_t *)hash_map_get(allocations, ptr);
157   assert(allocation);                               // Must have been tracked before
158   assert(!allocation->freed);                       // Must not be a double free
159   assert(allocation->allocator_id == allocator_id); // Must be from the same allocator
160   allocation->freed = true;
161 
162   UNUSED_ATTR const char *beginning_canary = ((char *)ptr) - canary_size;
163   UNUSED_ATTR const char *end_canary = ((char *)ptr) + allocation->size;
164 
165   for (size_t i = 0; i < canary_size; i++) {
166     assert(beginning_canary[i] == canary[i]);
167     assert(end_canary[i] == canary[i]);
168   }
169 
170   // Free the hash map entry to avoid unlimited memory usage growth.
171   // Double-free of memory is detected with "assert(allocation)" above
172   // as the allocation entry will not be present.
173   hash_map_erase(allocations, ptr);
174 
175   pthread_mutex_unlock(&lock);
176 
177   return ((char *)ptr) - canary_size;
178 }
179 
allocation_tracker_resize_for_canary(size_t size)180 size_t allocation_tracker_resize_for_canary(size_t size) {
181   return (!allocations) ? size : size + (2 * canary_size);
182 }
183 
allocation_entry_freed_checker(hash_map_entry_t * entry,void * context)184 static bool allocation_entry_freed_checker(hash_map_entry_t *entry, void *context) {
185   allocation_t *allocation = (allocation_t *)entry->data;
186   if (!allocation->freed) {
187     *((size_t *)context) += allocation->size; // Report back the unfreed byte count
188     LOG_ERROR(LOG_TAG, "%s found unfreed allocation. address: 0x%zx size: %zd bytes", __func__, (uintptr_t)allocation->ptr, allocation->size);
189   }
190 
191   return true;
192 }
193 
untracked_calloc(size_t size)194 static void *untracked_calloc(size_t size) {
195   return calloc(size, 1);
196 }
197