tpm2-software / tpm2-tss-engine

OpenSSL Engine for TPM2 devices
https://tpm2-software.github.io
BSD 3-Clause "New" or "Revised" License
151 stars 100 forks source link

Programmatically read generated key results in segfault #165

Closed geobra closed 4 years ago

geobra commented 4 years ago

Hello

I tried the following by use of tpm2-tss-engine-1.0.1:

Create a new tss key via command line: tpm2tss-genkey /tmp/privkey.tss

Now i tried to use the engine with openssl to read in this key. Here are the basic steps. I don't know if I missed some fundamental stuff, but what I read from the documentation, it should work:

ENGINE_load_dynamic();

ENGINE *tpm_engine = ENGINE_by_id("tpm2tss");
if (!tpm_engine)
    tpm_engine = ENGINE_by_id("libtpm2tss");
if (tpm_engine == nullptr)
{
    ERR("Could not load tpm2tss engine\n");
    return 1;
}

int init_res = ENGINE_init(tpm_engine);
VERB("Engine name: %s\nInit result: %d \n", ENGINE_get_name(tpm_engine), init_res);
if (!init_res)
    return 1;

EVP_PKEY* evp_key = ENGINE_load_private_key(tpm_engine, "/tmp/privkey.tss", UI_OpenSSL(), NULL);

The last step causes the SEGFAULT. Here is the stack trace:

0 0x00007ffff7e8c3a4 in RSA_set_method () from /lib/x86_64-linux-gnu/libcrypto.so.1.1

1 0x00007ffff7cc3ae3 in tpm2tss_rsa_makekey () from /home/developer/Documents/KeyPairTpm/kpt/tpm2-tss-engine-1.0.1/.libs/libtpm2tss.so

2 0x00007ffff7fc7cb8 in loadkey (e=, key_id=, ui=, cb_data=0x0) at src/tpm2-tss-engine.c:230

3 0x00007ffff7e37cbf in ENGINE_load_private_key () from /lib/x86_64-linux-gnu/libcrypto.so.1.1

And the specific sourc in from /tpm2-tss-engine-rsa.c: tpm2tss_rsa_makekey

if OPENSSL_VERSION_NUMBER < 0x10100000

rsa->meth = &rsa_methods;

else / OPENSSL_VERSION_NUMBER < 0x10100000 /

RSA_set_method(rsa, rsa_methods);

endif / OPENSSL_VERSION_NUMBER < 0x10100000 /

it seems to me that rsa_methods is not propperly set up. But maybe I am wrong here and I use the functionality in a wrong way? Any hints on that issue is appreciated :-).

Tested on a Debian 10.

Best regards Geobra

AndreasFuchsTPM commented 4 years ago

Maybe you could run your program with valgrind ? That should give us some hint on whether the write or the read leads to the segfault.

geobra commented 4 years ago

here you go:

developer@debian10:~/Documents/KeyPairTpm/build-kpt-Desktop-Default$ valgrind --leak-check=full ./kpt ==19966== Memcheck, a memory error detector ==19966== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==19966== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info ==19966== Command: ./kpt ==19966== Engine name: TPM2-TSS engine for OpenSSL Init result: 1 Creating RSA key object. ==19966== Invalid read of size 8 ==19966== at 0x4A173A4: RSA_set_method (in /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1) ==19966== by 0x4B50AE2: tpm2tss_rsa_makekey (in /home/developer/Documents/KeyPairTpm/kpt/tpm2-tss-engine-1.0.1/.libs/libtpm2tss.so) ==19966== by 0x4848CB7: loadkey (tpm2-tss-engine.c:230) ==19966== by 0x49C2CBE: ENGINE_load_private_key (in /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1) ==19966== by 0x10AC2C: OpensslTk::generatePubPemFromPrivTss(std::cxx11::basic_string<char, std::char_traits, std::allocator > const&, std::cxx11::basic_string<char, std::char_traits, std::allocator > const&) (OpensslTk.cpp:53) ==19966== by 0x10A3AE: main (main.cpp:26) ==19966== Address 0x38 is not stack'd, malloc'd or (recently) free'd ==19966== ==19966== ==19966== Process terminating with default action of signal 11 (SIGSEGV) ==19966== Access not within mapped region at address 0x38 ==19966== at 0x4A173A4: RSA_set_method (in /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1) ==19966== by 0x4B50AE2: tpm2tss_rsa_makekey (in /home/developer/Documents/KeyPairTpm/kpt/tpm2-tss-engine-1.0.1/.libs/libtpm2tss.so) ==19966== by 0x4848CB7: loadkey (tpm2-tss-engine.c:230) ==19966== by 0x49C2CBE: ENGINE_load_private_key (in /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1) ==19966== by 0x10AC2C: OpensslTk::generatePubPemFromPrivTss(std::cxx11::basic_string<char, std::char_traits, std::allocator > const&, std::cxx11::basic_string<char, std::char_traits, std::allocator > const&) (OpensslTk.cpp:53) ==19966== by 0x10A3AE: main (main.cpp:26) ... cut off valgrind usage stuff ... ==19966== HEAP SUMMARY: ==19966== in use at exit: 82,263 bytes in 2,515 blocks ==19966== total heap usage: 2,563 allocs, 48 frees, 182,098 bytes allocated ==19966== ==19966== LEAK SUMMARY: ==19966== definitely lost: 0 bytes in 0 blocks ==19966== indirectly lost: 0 bytes in 0 blocks ==19966== possibly lost: 0 bytes in 0 blocks ==19966== still reachable: 82,263 bytes in 2,515 blocks ==19966== suppressed: 0 bytes in 0 blocks ==19966== Reachable blocks (those to which a pointer was found) are not shown. ==19966== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==19966== ==19966== For counts of detected and suppressed errors, rerun with: -v ==19966== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) Segmentation fault

AndreasFuchsTPM commented 4 years ago

Address 0x38 looks like junk data... Could you check and validate that init_rsa() is executed ?

Also I'm wondering where this read could come from. The code in openssl is

int RSA_set_method(RSA *rsa, const RSA_METHOD *meth)
{
    /*
     * NB: The caller is specifically setting a method, so it's not up to us
     * to deal with which ENGINE it comes from.
     */
    const RSA_METHOD *mtmp;
    mtmp = rsa->meth;
    if (mtmp->finish)
        mtmp->finish(rsa);
#ifndef OPENSSL_NO_ENGINE
    ENGINE_finish(rsa->engine);
    rsa->engine = NULL;
#endif
    rsa->meth = meth;
    if (meth->init)
        meth->init(rsa);
    return 1;
}

which seems harmless enough...

geobra commented 4 years ago

Yes, a breakpoint in the #else branch get hit. The branch is complete and successfull executed.

int
init_rsa(ENGINE *e)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000
    default_rsa = RSA_PKCS1_SSLeay();
    if (default_rsa == NULL)
        return 0;

    rsa_methods.rsa_pub_enc = default_rsa->rsa_pub_enc;
    rsa_methods.rsa_pub_dec = default_rsa->rsa_pub_dec;
    rsa_methods.rsa_mod_exp = default_rsa->rsa_mod_exp;
    rsa_methods.bn_mod_exp = default_rsa->bn_mod_exp;

    return ENGINE_set_RSA(e, &rsa_methods);
#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */
    default_rsa = RSA_PKCS1_OpenSSL();
    if (default_rsa == NULL)
        return 0;

    rsa_methods = RSA_meth_dup(default_rsa);
    RSA_meth_set1_name(rsa_methods, "TPM2TSS RSA methods");
    RSA_meth_set_priv_enc(rsa_methods, rsa_priv_enc);
    RSA_meth_set_priv_dec(rsa_methods, rsa_priv_dec);

    return ENGINE_set_RSA(e, rsa_methods);
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */
}
AndreasFuchsTPM commented 4 years ago

Ok, that's even more weird. So you know how to use a debugger ? Would you single-step through the RSA_set_method function and maybe find the problematic line ?

geobra commented 4 years ago

This was my very first idea. But there is not -dbg package available for openssl in buster. This requires me to build openssl by myself with debug symbols and link against this version. Or do you know of any 'quicker' solution?

AndreasFuchsTPM commented 4 years ago

Unfortunately not. But openssl building is not as painful as one might think, thankfully. I've build it into a separate prefix and set the LD_LIBRARY_PATH and that worked. So you don't have to clutter your system.

geobra commented 4 years ago

Are there any special ./config options for openssl? If I just use './config' it determinse my system correct as:

Operating system: x86_64-whatever-linux2 Configuring OpenSSL version 1.1.1d (0x1010104fL) for linux-x86_64

But if I use LD_LIBRARY_PATH to point to the compiled libcrypto.so.1.1, the tpm2tss engine refuses to load at all. I verified the used libs by ldd. Without the LD_LIB... path I get the obvious segfault again.

AndreasFuchsTPM commented 4 years ago

You need to set the LD_LIBARAY_PATH before building the engine already so it links to the correct version of openssl.

geobra commented 4 years ago

That does not seem to work, despite ldd shows the correct file in the correct path. Does I 'only' have to recompile the engine? Or do I need to recompile the complete tpm2-tss stack?

AndreasFuchsTPM commented 4 years ago

right, tpm2-tss also uses libcrypto in the backend...

geobra commented 4 years ago

Okay, I set up a new VM with debian 10, compiled openssl with debug info. Compiled the complete tss stack against my openssl lib. tpmsim, abrmd, and tpm2tools running fine. tpm2tss-genkey as well as my own application refuses to load the engine. I am stuck here :-(.

AndreasFuchsTPM commented 4 years ago

Ok.... maybe strace -e file <youapplication> 2>&1 | grep open will help you to see where it searches for which file. That's what I usually use.

geobra commented 4 years ago

Yes, that does the trick.

Here are my lessosns learned:

int RSA_set_method(RSA *rsa, const RSA_METHOD *meth)
{
    /*
     * NB: The caller is specifically setting a method, so it's not up to us
     * to deal with which ENGINE it comes from.
     */
    const RSA_METHOD *mtmp;
    mtmp = rsa->meth;
    if (mtmp->finish)
        mtmp->finish(rsa);
#ifndef OPENSSL_NO_ENGINE
    ENGINE_finish(rsa->engine);
    rsa->engine = NULL;
#endif
    rsa->meth = meth;   // segfault, because meth = 0x0
    if (meth->init)
        meth->init(rsa);
    return 1;
}

I double checked that init_rsa is called and made some debbugging there. All should be ok. Then I tried to make an additional copy of the RSA_METHOD struct with RSA_dup. In the init func, the structure is valid. But also that structure is NULL if I use it at the call to RSA_set_method.

Obviously, if I create the structure again before the call to RSA_set_method and pass it in, the segfault is gone. But that is not a solution.

What I had observed during the strace hunting: My application is linked to libtpm2tss and is searching for it during start. But this is not enough. It searches it again at /usr/local/lib/engines-1.1/. Only if there is also a copy of that lib, my programm is able to start.

My assumption: The ENGINE_load_dynamic and ENGINE_by_id forces the lib to get loaded a second time? And during that second loading, the init function does not get triggered?

AndreasFuchsTPM commented 4 years ago

Why is your application linked against libtpm2tss anyways ? The calling of init should happen. However you can set the init=0/1 parameter for each engine individually in the openssl config file.

geobra commented 4 years ago

If I remove libtpm2tss.so as a linkage dependency and only use the ENGINE* calls, which loads the tpm2tss engine during runtime, the segfault is gone.

I used the tpm2tss-genkey.c file as a base for my use case. This application also links against libtpm2tss and uses ENGINE* calls. What is the difference there?

AndreasFuchsTPM commented 4 years ago

I have no idea... :-(

geobra commented 4 years ago

Okay, thank you for your support here! From this point on I try to get forward by myself :-).

AndreasFuchsTPM commented 4 years ago

Anytime