(root)/
glibc-2.38/
malloc/
tst-interpose-aux.c
       1  /* Minimal malloc implementation for interposition tests.
       2     Copyright (C) 2016-2023 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 (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;
     100    struct timespec ts;
     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");
     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);
     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      };
     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);
     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  }