]>
Commit | Line | Data |
---|---|---|
ef4f9764 | 1 | /* Minimal malloc implementation for interposition tests. |
dff8da6b | 2 | Copyright (C) 2016-2024 Free Software Foundation, Inc. |
ef4f9764 FW |
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 | |
5a82c748 | 17 | not, see <https://www.gnu.org/licenses/>. */ |
ef4f9764 FW |
18 | |
19 | #include "tst-interpose-aux.h" | |
20 | ||
21 | #include <errno.h> | |
22 | #include <stdarg.h> | |
23 | #include <stddef.h> | |
ceaa9889 | 24 | #include <stdint.h> |
ef4f9764 FW |
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> | |
1bdda52f | 31 | #include <time.h> |
ef4f9764 FW |
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 (message, sizeof (message), format, 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, iov, 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)))) allocation_header | |
97 | { | |
98 | size_t allocation_index; | |
99 | size_t allocation_size; | |
1bdda52f | 100 | struct timespec ts; |
ef4f9764 FW |
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 ("ptr"); | |
ef4f9764 FW |
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"); | |
e4e26210 | 122 | free (ptr); |
ef4f9764 FW |
123 | /* If the allocation count is still zero, it means we did not |
124 | interpose malloc successfully. */ | |
125 | if (allocation_index == 0) | |
126 | fail ("malloc does not seem to have been interposed"); | |
127 | } | |
128 | } | |
129 | ||
130 | static struct allocation_header *get_header (const char *op, void *ptr) | |
131 | { | |
132 | struct allocation_header *header = ((struct allocation_header *) ptr) - 1; | |
133 | if (header->allocation_index >= allocation_index) | |
134 | fail ("%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 ("%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, allocation_size, PROT_READ | PROT_WRITE, | |
162 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 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 | }; | |
1bdda52f AZ |
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, &allocations[index]->ts); | |
ef4f9764 FW |
174 | return allocations[index] + 1; |
175 | } | |
176 | ||
177 | static void | |
178 | free_internal (const char *op, struct allocation_header *header) | |
179 | { | |
180 | size_t index = header->allocation_index; | |
181 | int result = mprotect (header, header->allocation_size, PROT_NONE); | |
182 | if (result != 0) | |
183 | fail ("%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 *header = get_header ("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 (new_size); | |
198 | if (newptr == NULL) | |
199 | return NULL; | |
200 | memcpy (newptr, ptr, old_size); | |
201 | free_internal ("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 *header = get_header ("free", ptr); | |
240 | free_internal ("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 (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 (n); | |
269 | else | |
270 | { | |
271 | lock (); | |
272 | void *result = realloc_internal (ptr, n); | |
273 | unlock (); | |
274 | return result; | |
275 | } | |
276 | } |