手动加载 libcrypto (dlmopen, dlsym) 段错误;动态链接作品

Manually loading libcrypto (dlmopen, dlsym) segfaults; dynamically linked works

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

当我 link 和 -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)

备注:

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 问题? (我觉得两者都不太可能)

感谢您提供出色的重现说明。

What Am I doing wrong?

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

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

发生了什么……很复杂。

我们将第一个 libpthread.so 链接到 ./a.out P1,将第二个副本(通过 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 的调用(注意 same 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> "[=11=]1[=11=]2[=11=]3[=11=]4[=11=]5[=11=]6\a\b\t0123456701 !\"#$%&'()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 ()

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

P.P.S。事实上,这只是许多问题中的第一个。参见 this GLIBC bug