【问题标题】:Manually loading libcrypto (dlmopen, dlsym) segfaults; dynamically linked works手动加载 libcrypto (dlmopen, dlsym) segfaults;动态链接作品
【发布时间】:2021-12-29 23:21:46
【问题描述】:

我正在尝试使用来自libcrypto.so.3 的函数EVP_PKEY_new_raw_private_key

当我与-l:libcrypot.so.3 链接时,它可以工作。

当我尝试使用dlmopen+dlsym 打开同一个文件时,调用该函数时是SEGV

MWE 是 here:

main.c

#define _GNU_SOURCE
#include <assert.h>
#include <dlfcn.h>
#include <openssl/evp.h>

// c.f. objects/objects.pl
#define NID_X25519 1034

// some data to call new with
unsigned const char scalar[] = {
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11,
    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22,
    0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32,
};

int main(int argc, char **argv) {

  int keylen = 32;
  assert(keylen == sizeof(scalar));

  // open the library
  void *libhandle = dlmopen(LM_ID_NEWLM, "/usr/local/lib64/libcrypto.so.3",
                            RTLD_LAZY | RTLD_DEEPBIND | RTLD_LOCAL);
  assert(libhandle != NULL);

  // declare the pointer to the function
  EVP_PKEY *(*dl_EVP_PKEY_new_raw_private_key)(
      int type, ENGINE *e, const unsigned char *key, size_t keylen);

  // load from libhandle
  if ((dl_EVP_PKEY_new_raw_private_key =
           dlsym(libhandle, "EVP_PKEY_new_raw_private_key")) == NULL) {
    fprintf(stderr, "dlsym  EVP_PKEY_new_raw_private_key: %s\n", dlerror());
  }

  // create a private key form it.
  EVP_PKEY *skey = NULL;
  printf("about to create the skey\n");

  if (argc > 1) {
    // this segfaults somewhere in the library
    skey = dl_EVP_PKEY_new_raw_private_key(NID_X25519, NULL, scalar, keylen);
  } else {
    // this does not
    skey = EVP_PKEY_new_raw_private_key(NID_X25519, NULL, scalar, keylen);
  }
  printf("created the skey\n");
  assert(skey != NULL);

  dlclose(libhandle);
  return 0;
}

生成文件

LIBRARY_PATH=/usr/local/lib64
LIBRARY_NAME=libcrypto.so.3
all: ok fail

a.out: main.c $(LD_LIBRARY_PATH)/$(LIBRARY_NAME)
    gcc main.c -ldl -L $(LD_LIBRARY_PATH) -l:$(LIBRARY_NAME)

ok: a.out
    @echo sould be ok
    LD_LIBRARY_PATH=$(LIBRARY_PATH) ldd ./${^}
    LD_LIBRARY_PATH=$(LIBRARY_PATH) ./${^}
    @echo ok
    @echo

fail: a.out
    @echo should segfault
    LD_LIBRARY_PATH=$(LIBRARY_PATH) ldd ./${^}
    LD_LIBRARY_PATH=$(LIBRARY_PATH) ./${^} dl
    @echo wont be able to read this
    echo

执行make 时,您可以看到第二个调用,我们调用dlsymd 函数,SIGSEGVs。我们还看到,使用了相同的libcrypto.so.3

$ make
sould be ok
LD_LIBRARY_PATH=/usr/local/lib64 ldd ./a.out
        linux-vdso.so.1 (0x00007ffc0b7b9000)
        libdl.so.2 => /usr/lib/libdl.so.2 (0x00007fd36def3000)
        libcrypto.so.3 => /usr/local/lib64/libcrypto.so.3 (0x00007fd36dad5000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007fd36d909000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fd36df21000)
        libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fd36d8e8000)
LD_LIBRARY_PATH=/usr/local/lib64 ./a.out
about to create the skey
created the skey
ok

should segfault
LD_LIBRARY_PATH=/usr/local/lib64 ldd ./a.out
        linux-vdso.so.1 (0x00007ffc7bbac000)
        libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f9d68d2d000)
        libcrypto.so.3 => /usr/local/lib64/libcrypto.so.3 (0x00007f9d6890f000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007f9d68743000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f9d68d5b000)
        libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f9d68722000)
LD_LIBRARY_PATH=/usr/local/lib64 ./a.out dl
about to create the skey
make: *** [Makefile:18: fail] Segmentation fault (core dumped)

注意事项:

  • 在使用 libcrypto.so.1.1 文件时确实有效
  • 我用./Configure --api=3.0.0 no-deprecated &amp;&amp; make -jhere(提交f87b4c4ea67393c9269663ed40a7ea3463cc59d3)编译OpenSSL,并将生成的libcrypto.so.3复制到/usr/local/lib64/
  • 当使用./Configure --api=3.0.0 no-deprecated no-threads时,一切都完美无缺,这让我觉得libpthread+dlsym有问题...
  • 该线程的堆栈跟踪似乎是(来自coredumpctl debug
Stack trace of thread 1903409:
#0  0x00007fb6d8f8f8ec __pthread_rwlock_rdlock (/usr/lib/libpthread-2.33.so + 0xd8ec)
#1  0x00007fb6d91ba539 CRYPTO_THREAD_read_lock (/usr/local/lib64/libcrypto.so.3 + 0x210539)
#2  0x00007fb6d91a8627 ossl_lib_ctx_get_data (/usr/local/lib64/libcrypto.so.3 + 0x1fe627)
#3  0x00007fb6d91813f4 evp_generic_fetch (/usr/local/lib64/libcrypto.so.3 + 0x1d73f4)
#4  0x00007fb6d918b1c8 EVP_KEYMGMT_fetch (/usr/local/lib64/libcrypto.so.3 + 0x1e11c8)
#5  0x00007fb6d91976f9 EVP_PKEY_CTX_new_from_name (/usr/local/lib64/libcrypto.so.3 + 0x1ed6f9)
#6  0x00007fb6d9192be1 EVP_PKEY_new_raw_private_key (/usr/local/lib64/libcrypto.so.3 + 0x1e8be1)
#7  0x00005575befac2c2 n/a (/home/joel/mwe-dl-libcrypto3/a.out + 0x12c2)
#8  0x00007fb6d9447b25 __libc_start_main (libc.so.6 + 0x27b25)
#9  0x00005575befac0de n/a (/home/joel/mwe-dl-libcrypto3/a.out + 0x10de)

我做错了什么?这是dlmopen 问题还是openssl 问题? (在我看来两者都不太可能)

【问题讨论】:

    标签: openssl pthreads shared-libraries dlopen libcrypto


    【解决方案1】:

    感谢您提供出色的复制说明。

    我做错了什么?

    你正在使用dlmopen,这是一个雷区

    我怀疑您这样做是为了在一个进程中拥有多个不兼容的 OpenSSL 版本。我的建议:不要这样做™️。

    发生了什么……很复杂。

    让我们调用第一个链接到./a.out P1 的libpthread.so 和第二个副本(通过dlmopen 作为libcrypto.so 的依赖项引入)P2。我们将dlmopened 版本称为libcrypto C2。

    P1 和 P2 都有单独的隐藏变量 __pthread_keys。当 C2 调用 P2:pthread_key_create 时,该函数在 P2:__pthread_keys 中查找,并发现 no 键已被使用(这在 P2 中是正确的,但在 P1 中不是 - 加载程序已经使用了一些键来自P1:__pthread_keys)。

    所以C2从P2:pthread_key_create得到答案——使用key==0(P2没有注意到key==0已经在P1中使用了!)。

    现在 C2 调用 P2:pthread_getspecific(0)期望返回一个 NULL -- 它还没有调用 pthread_setspecific(0, ...)

    但是pthread_getspecific查看线程控制块,它对于给定线程是唯一的并且在P1和P2之间共享,这就是灾难:P2没有得到NULL,它获取P1:pthread_setspecific(0, ...) 之前设置的任何内容!

    此时,C2 决定某些其他代码必须已经适当地设置了 C2 的线程本地数据,并继续使用该数据,并生成 SIGSEGV

    那么谁打电话给P1:pthread_setspecific?它发生在这里:

    Breakpoint 2, __GI___pthread_setspecific (key=0, value=value@entry=0x5555555592a0) at pthread_setspecific.c:33
    33      pthread_setspecific.c: No such file or directory.
    (gdb) bt
    #0  __GI___pthread_setspecific (key=0, value=value@entry=0x5555555592a0) at pthread_setspecific.c:33
    #1  0x00007ffff7f9eb3c in _dlerror_run (operate=operate@entry=0x7ffff7f9ee90 <dlmopen_doit>, args=args@entry=0x7fffffffdb60) at dlerror.c:157
    #2  0x00007ffff7f9efd9 in __dlmopen (nsid=<optimized out>, file=<optimized out>, mode=<optimized out>) at dlmopen.c:93
    #3  0x00005555555551f8 in main ()
    

    随后对P2:pthread_getspecific 的调用(注意相同 key==0 被重复使用)发生在这里:

    #0  __GI___pthread_getspecific (key=0) at pthread_getspecific.c:30
    #1  0x00007ffff77985cd in CRYPTO_THREAD_get_local (key=<optimized out>) at crypto/threads_pthread.c:160
    #2  0x00007ffff778a2d2 in get_thread_default_context () at crypto/context.c:166
    #3  0x00007ffff778a2ee in get_default_context () at crypto/context.c:171
    #4  0x00007ffff778a43b in ossl_lib_ctx_get_concrete (ctx=<optimized out>) at crypto/context.c:278
    #5  0x00007ffff778a681 in ossl_lib_ctx_get_data (ctx=<optimized out>, index=index@entry=0, meth=meth@entry=0x7ffff79684e0) at crypto/context.c:356
    #6  0x00007ffff776ab6c in get_evp_method_store (libctx=<optimized out>) at crypto/evp/evp_fetch.c:82
    #7  0x00007ffff776ab9b in inner_evp_generic_fetch (methdata=methdata@entry=0x7fffffffd9c0, prov=<optimized out>, prov@entry=0x0, operation_id=operation_id@entry=10, name_id=name_id@entry=0, name=0x7ffff78aa0ac "X25519", properties=0x0, new_method=0x7ffff7772e68 <keymgmt_from_algorithm>, up_ref_method=0x7ffff7772d7a <EVP_KEYMGMT_up_ref>,
        free_method=0x7ffff7772d88 <EVP_KEYMGMT_free>) at crypto/evp/evp_fetch.c:248
    #8  0x00007ffff776b37a in evp_generic_fetch (libctx=<optimized out>, operation_id=operation_id@entry=10, name=<optimized out>, properties=<optimized out>, new_method=new_method@entry=0x7ffff7772e68 <keymgmt_from_algorithm>, up_ref_method=up_ref_method@entry=0x7ffff7772d7a <EVP_KEYMGMT_up_ref>, free_method=0x7ffff7772d88 <EVP_KEYMGMT_free>)
        at crypto/evp/evp_fetch.c:372
    #9  0x00007ffff77732e8 in EVP_KEYMGMT_fetch (ctx=<optimized out>, algorithm=<optimized out>, properties=<optimized out>) at crypto/evp/keymgmt_meth.c:230
    #10 0x00007ffff777d066 in int_ctx_new (libctx=0x0, pkey=pkey@entry=0x0, e=e@entry=0x0, keytype=0x7ffff78aa0ac "X25519", propquery=0x0, id=<optimized out>, id@entry=-1) at crypto/evp/pmeth_lib.c:280
    #11 0x00007ffff777d299 in EVP_PKEY_CTX_new_from_name (libctx=<optimized out>, name=<optimized out>, propquery=<optimized out>) at crypto/evp/pmeth_lib.c:368
    #12 0x00007ffff7778c70 in new_raw_key_int (libctx=libctx@entry=0x0, strtype=strtype@entry=0x0, propq=propq@entry=0x0, nidtype=1034, e=0x0, key=0x555555556020 <scalar> "\001\002\003\004\005\006\a\b\t\020\021\022\023\024\025\026\027\030\031 !\"#$%&'()012main.c", len=32, key_is_priv=1) at crypto/evp/p_lib.c:406
    #13 0x00007ffff7778f3e in EVP_PKEY_new_raw_private_key (type=<optimized out>, e=<optimized out>, priv=<optimized out>, len=<optimized out>) at crypto/evp/p_lib.c:497
    #14 0x000055555555529d in main ()
    

    附:这只花了我 3 个小时来调试,而且可能只是您可能遇到的许多问题中的第一个。

    附言事实上,这只是许多人的第一个问题。见this GLIBC bug

    【讨论】:

    • 感谢您的深入调查。我正在使用它来加载同一个库的两个版本(好吧,一个稍微修补到不同的文件中),然后比较运行时。我使用dlmopen 来确保符号在不同的LMs 中——我在使用dlopen 时不确定这一点,因为两个副本定义了相同的符号。
    猜你喜欢
    • 1970-01-01
    • 2017-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-07
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    相关资源
    最近更新 更多