Search code examples
linuxgccglibcld-preload

Using LD_PRELOAD on Fedora 25 causes Segmentation Fault


I have found a strange behavior while I was trying to use a library that I wrote a long time ago. The main problem is, when the program is executed on Fedora 25 and linked to my library by using LD_PRELOAD, the system raises a Segmentation Fault. I've made a small sample of my old library to easily understand the problem.

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

extern void *__libc_malloc(size_t size);

void *malloc(size_t size) 
{
  void *ptr = __libc_malloc(size);
  fprintf(stdout, "Malloc(%d)->(%p)\n", (int) size, ptr);
  return ptr;
}

This code was compiled by using these parameters:

gcc -c -fPIC -O3 -Wall -o libtest.o libtest.c
gcc -shared -o libtest.so libtest.o

The program was executed as follows:

LD_PRELOAD=./libtest.so ./progtest

I found out that the "fprintf" line was causing the Segfault problem. So I've changed the "stdout" file descriptor to "stderr", and the problem was gone.

Then I tested the same code using the "stdout" file descriptor as output for "fprintf" on another machine running CentOS 7, and it worked fine in both cases using "stdout" and "stderr".

By observing these results, I wonder what I am missing and why this is happening.

GLibc and GCC versions installed on Fedora 25:

GNU ld version 2.26.1-1.fc25

gcc (GCC) 6.3.1 20161221 (Red Hat 6.3.1-1)

GLibc and GCC versions installed on CentOS 7:

GNU ld version 2.25.1-22.base.el7

gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11)


Solution

  • fprintf itself may need to use malloc under some circumstances. One possible reason is that stderr is unbuffered while stdout is line buffered. fprintf(stdout) might have a buffer already, or it may try to allocate one, ending up calling your malloc, which calls into fprintf again, but it is not re-entrant on the same FILE*.

    You can prevent re-entrancy with a flag, such as (C11):

    #include <stdbool.h>
    #include <threads.h>
    thread_local bool inside_malloc = false;
    void *malloc(size_t size) {
        void *ptr = __libc_malloc(size);
        if (!inside_malloc) {
            inside_malloc = true;
            fprintf(stdout, "Malloc(%zd)->(%p)\n", size, ptr);
            inside_malloc = false;
        }
        return ptr;
    }