1 | /* Minimal malloc implementation for interposition tests. |
2 | Copyright (C) 2016-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public License as |
7 | published by the Free Software Foundation; either version 2.1 of the |
8 | License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; see the file COPYING.LIB. If |
17 | not, see <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include "tst-interpose-aux.h" |
20 | |
21 | #include <errno.h> |
22 | #include <stdarg.h> |
23 | #include <stddef.h> |
24 | #include <stdint.h> |
25 | #include <stdio.h> |
26 | #include <stdlib.h> |
27 | #include <string.h> |
28 | #include <sys/mman.h> |
29 | #include <sys/uio.h> |
30 | #include <unistd.h> |
31 | #include <time.h> |
32 | |
33 | #if INTERPOSE_THREADS |
34 | #include <pthread.h> |
35 | #endif |
36 | |
37 | /* Print the error message and terminate the process with status 1. */ |
38 | __attribute__ ((noreturn)) |
39 | __attribute__ ((format (printf, 1, 2))) |
40 | static void * |
41 | fail (const char *format, ...) |
42 | { |
43 | /* This assumes that vsnprintf will not call malloc. It does not do |
44 | so for the format strings we use. */ |
45 | char message[4096]; |
46 | va_list ap; |
47 | va_start (ap, format); |
48 | vsnprintf (s: message, maxlen: sizeof (message), format: format, arg: ap); |
49 | va_end (ap); |
50 | |
51 | enum { count = 3 }; |
52 | struct iovec iov[count]; |
53 | |
54 | iov[0].iov_base = (char *) "error: " ; |
55 | iov[1].iov_base = (char *) message; |
56 | iov[2].iov_base = (char *) "\n" ; |
57 | |
58 | for (int i = 0; i < count; ++i) |
59 | iov[i].iov_len = strlen (iov[i].iov_base); |
60 | |
61 | int unused __attribute__ ((unused)); |
62 | unused = writev (STDOUT_FILENO, iovec: iov, count: count); |
63 | _exit (1); |
64 | } |
65 | |
66 | #if INTERPOSE_THREADS |
67 | static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
68 | #endif |
69 | |
70 | static void |
71 | lock (void) |
72 | { |
73 | #if INTERPOSE_THREADS |
74 | int ret = pthread_mutex_lock (&mutex); |
75 | if (ret != 0) |
76 | { |
77 | errno = ret; |
78 | fail ("pthread_mutex_lock: %m" ); |
79 | } |
80 | #endif |
81 | } |
82 | |
83 | static void |
84 | unlock (void) |
85 | { |
86 | #if INTERPOSE_THREADS |
87 | int ret = pthread_mutex_unlock (&mutex); |
88 | if (ret != 0) |
89 | { |
90 | errno = ret; |
91 | fail ("pthread_mutex_unlock: %m" ); |
92 | } |
93 | #endif |
94 | } |
95 | |
96 | struct __attribute__ ((aligned (__alignof__ (max_align_t)))) |
97 | { |
98 | size_t ; |
99 | size_t ; |
100 | struct timespec ; |
101 | }; |
102 | |
103 | /* Array of known allocations, to track invalid frees. */ |
104 | enum { max_allocations = 65536 }; |
105 | static struct allocation_header *allocations[max_allocations]; |
106 | static size_t allocation_index; |
107 | static size_t deallocation_count; |
108 | |
109 | /* Sanity check for successful malloc interposition. */ |
110 | __attribute__ ((destructor)) |
111 | static void |
112 | check_for_allocations (void) |
113 | { |
114 | if (allocation_index == 0) |
115 | { |
116 | /* Make sure that malloc is called at least once from libc. */ |
117 | void *volatile ptr = strdup (s: "ptr" ); |
118 | /* Compiler barrier. The strdup function calls malloc, which |
119 | updates allocation_index, but strdup is marked __THROW, so |
120 | the compiler could optimize away the reload. */ |
121 | __asm__ volatile ("" ::: "memory" ); |
122 | free (ptr: ptr); |
123 | /* If the allocation count is still zero, it means we did not |
124 | interpose malloc successfully. */ |
125 | if (allocation_index == 0) |
126 | fail (format: "malloc does not seem to have been interposed" ); |
127 | } |
128 | } |
129 | |
130 | static struct allocation_header * (const char *op, void *ptr) |
131 | { |
132 | struct allocation_header * = ((struct allocation_header *) ptr) - 1; |
133 | if (header->allocation_index >= allocation_index) |
134 | fail (format: "%s: %p: invalid allocation index: %zu (not less than %zu)" , |
135 | op, ptr, header->allocation_index, allocation_index); |
136 | if (allocations[header->allocation_index] != header) |
137 | fail (format: "%s: %p: allocation pointer does not point to header, but %p" , |
138 | op, ptr, allocations[header->allocation_index]); |
139 | return header; |
140 | } |
141 | |
142 | /* Internal helper functions. Those must be called while the lock is |
143 | acquired. */ |
144 | |
145 | static void * |
146 | malloc_internal (size_t size) |
147 | { |
148 | if (allocation_index == max_allocations) |
149 | { |
150 | errno = ENOMEM; |
151 | return NULL; |
152 | } |
153 | size_t allocation_size = size + sizeof (struct allocation_header); |
154 | if (allocation_size < size) |
155 | { |
156 | errno = ENOMEM; |
157 | return NULL; |
158 | } |
159 | |
160 | size_t index = allocation_index++; |
161 | void *result = mmap (NULL, len: allocation_size, PROT_READ | PROT_WRITE, |
162 | MAP_PRIVATE | MAP_ANONYMOUS, fd: -1, offset: 0); |
163 | if (result == MAP_FAILED) |
164 | return NULL; |
165 | allocations[index] = result; |
166 | *allocations[index] = (struct allocation_header) |
167 | { |
168 | .allocation_index = index, |
169 | .allocation_size = allocation_size |
170 | }; |
171 | /* BZ#24967: Check if calling a symbol which may use the vDSO does not fail. |
172 | The CLOCK_REALTIME should be supported on all systems. */ |
173 | clock_gettime (CLOCK_REALTIME, tp: &allocations[index]->ts); |
174 | return allocations[index] + 1; |
175 | } |
176 | |
177 | static void |
178 | free_internal (const char *op, struct allocation_header *) |
179 | { |
180 | size_t index = header->allocation_index; |
181 | int result = mprotect (addr: header, len: header->allocation_size, PROT_NONE); |
182 | if (result != 0) |
183 | fail (format: "%s: mprotect (%p, %zu): %m" , op, header, header->allocation_size); |
184 | /* Catch double-free issues. */ |
185 | allocations[index] = NULL; |
186 | ++deallocation_count; |
187 | } |
188 | |
189 | static void * |
190 | realloc_internal (void *ptr, size_t new_size) |
191 | { |
192 | struct allocation_header * = get_header (op: "realloc" , ptr); |
193 | size_t old_size = header->allocation_size - sizeof (struct allocation_header); |
194 | if (old_size >= new_size) |
195 | return ptr; |
196 | |
197 | void *newptr = malloc_internal (size: new_size); |
198 | if (newptr == NULL) |
199 | return NULL; |
200 | memcpy (newptr, ptr, old_size); |
201 | free_internal (op: "realloc" , header); |
202 | return newptr; |
203 | } |
204 | |
205 | /* Public interfaces. These functions must perform locking. */ |
206 | |
207 | size_t |
208 | malloc_allocation_count (void) |
209 | { |
210 | lock (); |
211 | size_t count = allocation_index; |
212 | unlock (); |
213 | return count; |
214 | } |
215 | |
216 | size_t |
217 | malloc_deallocation_count (void) |
218 | { |
219 | lock (); |
220 | size_t count = deallocation_count; |
221 | unlock (); |
222 | return count; |
223 | } |
224 | void * |
225 | malloc (size_t size) |
226 | { |
227 | lock (); |
228 | void *result = malloc_internal (size); |
229 | unlock (); |
230 | return result; |
231 | } |
232 | |
233 | void |
234 | free (void *ptr) |
235 | { |
236 | if (ptr == NULL) |
237 | return; |
238 | lock (); |
239 | struct allocation_header * = get_header (op: "free" , ptr); |
240 | free_internal (op: "free" , header); |
241 | unlock (); |
242 | } |
243 | |
244 | void * |
245 | calloc (size_t a, size_t b) |
246 | { |
247 | if (b > 0 && a > SIZE_MAX / b) |
248 | { |
249 | errno = ENOMEM; |
250 | return NULL; |
251 | } |
252 | lock (); |
253 | /* malloc_internal uses mmap, so the memory is zeroed. */ |
254 | void *result = malloc_internal (size: a * b); |
255 | unlock (); |
256 | return result; |
257 | } |
258 | |
259 | void * |
260 | realloc (void *ptr, size_t n) |
261 | { |
262 | if (n ==0) |
263 | { |
264 | free (ptr); |
265 | return NULL; |
266 | } |
267 | else if (ptr == NULL) |
268 | return malloc (size: n); |
269 | else |
270 | { |
271 | lock (); |
272 | void *result = realloc_internal (ptr, new_size: n); |
273 | unlock (); |
274 | return result; |
275 | } |
276 | } |
277 | |