Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

segfault when shared library uses getrandom() in library initialization and FAKERANDOM_SEED is unset #295

Closed
dkg opened this issue Feb 23, 2021 · 6 comments · Fixed by #299
Assignees

Comments

@dkg
Copy link
Collaborator

dkg commented Feb 23, 2021

I'm using the 0.9.8+git20210209-1 snapshot from debian experimental, which is basically 0.9.9, but with FAKE_RANDOM enabled during the build.

looks like my testing isn't complete, though, because:

$ LD_PRELOAD=../src/libfaketime.so.1 certtool --help
Caught Segmentation fault
1 $

but this succeeds:

$ FAKERANDOM_SEED=0x0000000000000000 LD_PRELOAD=../src/libfaketime.so.1 certtool --help

I thought maybe the issue was multiple calls to getrandom, but i did some additional testing (see #294) and that doesn't appear to trigger the failure in getrandom.c either -- the getrandom stuff in tests/ still succeeds, even though certtool is segfaulting.

fwiw, LD_PRELOAD=../src/libfaketime.so.1 curl --help is segfaulting too. so i don't think this is a problem peculiar to certtool.

looking at strace output between curl without the LD_PRELOAD and with the LD_PRELOAD, the segfault does show up right when the program should be invoking the getrandom() syscall, but i'm also having trouble getting any sort of debugging info because gdb itself also segfaults if i have the LD_PRELOAD set when launching gdb.

@dkg dkg changed the title segfault in programs that use getrandom() when FAKERANDOM_SEED is unset segfault in some programs that use getrandom() when FAKERANDOM_SEED is unset Feb 23, 2021
@dkg
Copy link
Collaborator Author

dkg commented Feb 23, 2021

The above report is from a debian amd64 (x86_64) testing/unstable, fwiw. all of the segfaulting executables i've found thus far are linking against libgnutls, which is (i think) where they're invoking getrandom. my test program that invokes gnutls_rnd() (see #290) is also crashing if i LD_PRELOAD without setting FAKERANDOM_SEED. Sorry for not catching this earlier!

If i permit a crash from the test to dump core, and then try to load the corefile into gdb, i get an unannotated backtrace, even though test program is not stripped, and i have the debug symbols for gnutls installed:

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x00007f851502be81 in ?? ()
#2  0x0000562b8781dce0 in ?? ()
#3  0x0000000000000003 in ?? ()
#4  0x0000562b8781dc20 in ?? ()
#5  0xdf01d77d1e61f800 in ?? ()
#6  0x000000000000001e in ?? ()
#7  0x0000000000000000 in ?? ()
(gdb) 

@wolfcw wolfcw self-assigned this Feb 23, 2021
@wolfcw
Copy link
Owner

wolfcw commented Feb 23, 2021

I've traced this back to dlsym(RTLD_NEXT, "getrandom") failing in these cases. Apparently, for passing through calls to the original getrandom(), things work fine in the getrandom_test binary: real_getrandom from libfaketime.c points to a valid memory location. However, when used with curl or certtool, as you posted, this is just a (nil) pointer, resulting in a segfault when trying to call the original getrandom() function.

I have no explanation for this yet and need to investigate further. If you have a minimum example of a GnuTLS-based C program (which crashes) lying around somewhere, that'd be useful. I'll try my luck with curl and certtool meanwhile.

@dkg
Copy link
Collaborator Author

dkg commented Feb 23, 2021

sample gnutls_rnd program:

/* gnutls_rnd.c */
#include <stdio.h>
#include <string.h>
#include <gnutls/crypto.h>

int main() {
  unsigned char buf[10];
  int rc;

  struct { int level; const char* name; } steps[] = {
    {GNUTLS_RND_NONCE, "NONCE"},
    {GNUTLS_RND_RANDOM, "RANDOM"},
    {GNUTLS_RND_KEY, "KEY"},
  };

  for (int step = 0; step < sizeof(steps)/sizeof(steps[0]); step++) {
    memset(buf, 0x12, sizeof(buf));
    rc = gnutls_rnd (steps[step].level, buf, sizeof(buf));
    if (rc) {
      gnutls_perror (rc);
      return rc;
    }

    printf ("GnuTLS %10s: ", steps[step].name);
    for (int i = 0; i < sizeof(buf); i++)
      printf ("%02x", buf[i]);
    printf ("\n");
  }
  return 0;
}

build with:

dkg@alice:~$ gcc -g -Wall -pedantic -Werror -O2 gnutls_rnd.c $(pkg-config --libs --cflags gnutls) -o gnutls_rnd

@dkg
Copy link
Collaborator Author

dkg commented Feb 23, 2021

i suspect the issue is that this crash is happening during the initialization of libgnutls -- so ld is still sorting out the dynamic linking, and main() has not yet been called. Indeed, if i modify the above program to do explicit library initialization:

/* gnutls_rnd.c */
#include <stdio.h>
#include <string.h>
#include <gnutls/crypto.h>

int _real_main();

int main() {
  int rc;
  gnutls_global_init();
  rc = _real_main();
  gnutls_global_deinit();
  return rc;
}

int _real_main() {
  unsigned char buf[10];
  int rc;

  struct { int level; const char* name; } steps[] = {
    {GNUTLS_RND_NONCE, "NONCE"},
    {GNUTLS_RND_RANDOM, "RANDOM"},
    {GNUTLS_RND_KEY, "KEY"},
  };

  for (int step = 0; step < sizeof(steps)/sizeof(steps[0]); step++) {
    memset(buf, 0x12, sizeof(buf));
    rc = gnutls_rnd (steps[step].level, buf, sizeof(buf));
    if (rc) {
      gnutls_perror (rc);
      return rc;
    }

    printf ("GnuTLS %10s: ", steps[step].name);
    for (int i = 0; i < sizeof(buf); i++)
      printf ("%02x", buf[i]);
    printf ("\n");
  }
  return 0;
}

Then i can trigger the crash (or not) by avoiding the implicit library initialization using a (misnamed) env var:

dkg@alice:~$ GNUTLS_NO_EXPLICIT_INIT=1 LD_PRELOAD=libfaketime.so.1 ./gnutls_rnd
GnuTLS      NONCE: a7d24c4d5279cfefa9a7
GnuTLS     RANDOM: c77778afb396dab4aa7a
GnuTLS        KEY: dc916fcee2c2a8109c1a
dkg@alice:~$ LD_PRELOAD=libfaketime.so.1 ./gnutls_rnd 
Segmentation fault
dkg@alice:~$

dkg added a commit to dkg/libfaketime that referenced this issue Feb 23, 2021
Running "make randomtest" should demonstrates the segfault described
in wolfcw#295
@dkg
Copy link
Collaborator Author

dkg commented Feb 23, 2021

better testing, without introducing any additional build dependencies can be found in #296, which demonstrates that it is indeed a problem with calls to getrandom() in library initialization.

To be clear, I haven't solved the problem, just crafted a reproducer.

@dkg dkg changed the title segfault in some programs that use getrandom() when FAKERANDOM_SEED is unset segfault when shared library uses getrandom() in library initialization and FAKERANDOM_SEED is unset Feb 23, 2021
@dkg
Copy link
Collaborator Author

dkg commented Feb 23, 2021

It occurs to me that this same problem might happen if any shared library invokes time-related functions (not only getrandom) during library initialization when the LD_PRELOADing but with FAKETIME unset. I don't know whether we have any tests for that.

This was referenced Feb 24, 2021
dkg added a commit to dkg/libfaketime that referenced this issue Feb 24, 2021
This avoids potential failure if another library calls getrandom()
within its constructor before we are loaded.

For me, it lets "make randomtest" succeed in tests/

Closes: wolfcw#295
dkg added a commit to dkg/libfaketime that referenced this issue Feb 24, 2021
This is an attempt to ensure that an external library invocation of
getpid doesn't trigger a crash (e.g. wolfcw#295) or an infinite loop
(e.g. wolfcw#297).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants