【发布时间】:2019-05-29 22:04:39
【问题描述】:
我有一个现有的预构建.so 共享库(我们称之为libjniopenssl.so)。它是由第三方在Debian Linux 和OpenSSL 1.0.1k-3+deb8u4 上构建的。
我必须使用OpenSSL 1.0.2k 包在CentOS 上运行libjniopenssl.so。
尽管OpenSSL 版本略有不同,libjniopenssl.so 使用的 API 在1.0.1k 和1.0.2k 之间没有变化——所以我希望它们在我的场景中是源代码和二进制兼容的。
不幸的是,仅在 CentOS 上运行 libjniopenssl.so 不起作用。
libjniopenssl.so 由JVM 通过System.loadLibrary 加载,但在CentOS 上运行时失败并出现以下错误:
Unable to load libjniopenssl: java.lang.UnsatisfiedLinkError: /tmp/jna-112200956/jna6604950569974562639.tmp:
libcrypto.so.1.0.0: cannot open shared object file: No such file or directory
原因很简单,CentOS 上没有libcrypto.so.1.0.0 这样的文件,因为OpenSSL 1.0.2k 16.el7 只提供了以下.so 的:
$ ls -l /lib64/libcrypto*
lrwxrwxrwx. 1 root root 19 Jun 5 2018 /lib64/libcrypto.so.10 -> libcrypto.so.1.0.2k
-rwxr-xr-x. 1 root root 2512832 Apr 11 2018 /lib64/libcrypto.so.1.0.2k
出于某种原因,CentOS 包 intentionally renamed .so 文件名从默认 libcrypto.so.1.0.0 到 libcrypto.so.1.0.2k,即使从 source 编译该版本时,它仍使用名称 libcrypto.so.1.0.0。
此错误消息建议尝试创建一个名称为 /lib64/libcrypto.so.1.0.0 的符号链接,该链接指向 /lib64/libcrypto.so.1.0.2k
这导致运行时出现以下错误:
Unable to load libjniopenssl: java.lang.UnsatisfiedLinkError: /tmp/jna-112200956/jna2564265718506275007.tmp:
/lib64/libcrypto.so.1.0.0: version `OPENSSL_1.0.0' not found (required by /tmp/jna-112200956/jna2564265718506275007.tmp)
检查libjniopenssl.so 发现OpenSSL 引用了以下符号:
$ readelf -Ws libjniopenssl.so
Symbol table '.dynsym' contains 40 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000e98 0 SECTION LOCAL DEFAULT 9
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND EVP_CIPHER_CTX_init@OPENSSL_1.0.0 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND malloc@GLIBC_2.2.5 (3)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND OPENSSL_add_all_algorithms_noconf@OPENSSL_1.0.0 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND HMAC_CTX_cleanup@OPENSSL_1.0.0 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND EVP_CipherUpdate@OPENSSL_1.0.0 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ERR_load_crypto_strings@OPENSSL_1.0.0 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (3)
12: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
13: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (3)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND HMAC_CTX_init@OPENSSL_1.0.0 (2)
15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND HMAC_Final@OPENSSL_1.0.0 (2)
16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND EVP_sha1@OPENSSL_1.0.0 (2)
17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND HMAC_Init_ex@OPENSSL_1.0.0 (2)
18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND HMAC_Update@OPENSSL_1.0.0 (2)
19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND EVP_CIPHER_CTX_cleanup@OPENSSL_1.0.0 (2)
20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND EVP_MD_size@OPENSSL_1.0.0 (2)
21: 0000000000000000 0 FUNC GLOBAL DEFAULT UND EVP_aes_128_ctr@OPENSSL_1.0.1 (4)
但是CentOS 上的libcrypto.so.1.0.2k 有不同后缀的符号,例如:
readelf -Ws /lib64/libcrypto.so.1.0.2k | grep -iE "EVP_CIPHER_CTX_init"
705: 000000000012a6b0 151 FUNC GLOBAL DEFAULT 13 EVP_CIPHER_CTX_init@@libcrypto.so.10
所以,CentOS 上的后缀是 @@libcrypto.so.10,但 libjniopenssl.so 链接到共享库,该库具有后缀 @OPENSSL_1.0.0 的符号。这可能就是为什么仅添加符号链接不起作用的原因。
进一步调查显示Debian版本的OpenSSL 1.0.1k有included many patches,其中一个adds symbol versioning。它仅在 OpenSSL 1.1.0 中引入,但由向 1.0.1 提供 Debian 软件包的人反向移植。
相反,CentOS OpenSSL 1.0.2k 软件包不包含用于符号版本控制的补丁(但它们包含来自 1.1.0 的其他补丁,根据 .so 中的符号)。
我可以根据OpenSSL 的副本在CentOS 上重新构建libjniopenssl.so,但它实际上涉及维护(构建、存储、部署)自己的libjniopenssl.so 副本,它实际上来自第三方git 存储库.我不喜欢重建解决方案,所以我正在寻找更优雅的解决方案。但是我不得不说re-building是CentOS论坛上推荐的。
我能想到的一个解决方案是在构建或部署期间生成一个特殊的 shim libcrypto.so.1.0.0(基于目标系统提供的 libcrypto 副本),它已根据 libjniopenssl.so 的需要重命名符号,但在后台它将所有调用转换为原始系统提供的libcrypto.so.1.0.2k。
一般来说,我正在寻找一些工具或一组工具,它们能够从指定的“实现”.so 中自动生成这样的“垫片/适配器/代理”.so,但能够重新定义一些或所有符号。
我发现有objcopy 工具有--redefine-symbol old=new,但它并不完全符合我的要求:我不想从原始.so 复制任何代码,我确实想将调用转换为原来的“实现”.so。
UPD:运行本地实验导致objcopy不支持动态库中符号的重命名。从这个mail thread确认了问题。
总结一下,我的问题是:
- 是否有现有的工具能够为指定的
.so文件生成这样的共享 shim 库? - 或者也许有更好的方法来解决
CentOS和DebianOpenSSL包之间的这种不兼容性,假设我不想修改第三方提供的libjniopenssl.so和操作系统提供的@987654397 的副本@?
【问题讨论】:
-
Debian 和 CentOS 的 OpenSSL 似乎是二进制不兼容的。通过重命名符号来强制链接可能会导致运行时错误。
-
他们建议重建它是有充分理由的。这是一个合理的建议,无论你是否想做这件事,你都应该遵循。如果它们不兼容,不要指望任何实用程序来“解决”问题。在编程中记住这一点:“工作”的外观并不意味着它是正确的。有很大的不同。不兼容就是不兼容,无论你多么不喜欢它。简而言之,答案是你走错了路,事情就是这样。
-
@Vi。这是真的,但实际上简单的符号链接通常就足够了,因为应用程序不会调用不兼容的 ABI 子集。我知道这是有风险的、不正确的等等,但这仍然是一种常见的做法。
标签: java linux openssl shared-libraries