java libjli.so_解决setcap导致Java加载libjli.so 失败问题

背景

最近碰到一个问题,有个应用在启动的时候一直报错,错误信息如下:

java: error while loading shared libraries: libjli.so: cannot open shared object file: No such file or directory

错误信息是说 java 应用加载不到 libjli.so 文件,我们使用 java -version 命令,同样的错误又出现了。使用 ldd 命令查看一下 java 应用是否加载了这个 so 文件,发现 java 应用加载的 so 文件中存在 libjli.so。

$ ldd java

linux-vdso.so.1 => (0x00007ffe2a9c7000)

/usr/local/lib/libsysconfcpus.so (0x00002ac503ca8000)

libz.so.1 => /lib64/libz.so.1 (0x00002ac503eaa000)

libjli.so => /apps/svr/jdk-14.0.1/bin/./../lib/libjli.so (0x00002ac5040c0000)

libpthread.so.0 => /lib64/libpthread.so.0 (0x00002ac5042d1000)

libdl.so.2 => /lib64/libdl.so.2 (0x00002ac5044ee000)

libc.so.6 => /lib64/libc.so.6 (0x00002ac5046f2000)

/lib64/ld-linux-x86-64.so.2 (0x00002ac503883000)

我们接着查看了 LD_LIBRARY_PATH 和 /etc/ld.so.conf.d/xxx.conf 文件的配置,发现都是正常的。通过对比其他应用的启动配置,发现该应用使用了 80 端口启动,但是我们的容器只能使用 apps 权限登录,所以在启动前使用 setcap 命令提升了 java 应用的权限,允许其使用 80 端口,会不会是这个操作导致的呢?在查看原因之前,我们需要先理解几个概念。

Linux 动态库

动态库(共享库)的代码在可执行程序运行时才载入内存,在编译过程中仅简单的引用,不同的应用程序如果调用相同的库,那么在内存中只需要有一份该动态库(共享库)的实例。这类库的名字一般是libxxx.so,其中so是 Shared Object 的缩写,即可以共享的目标文件。在链接动态库生成可执行文件时,并不会把动态库的代码复制到执行文件中,而是在执行文件中记录对动态库的引用。

Linux下生成和使用动态库的步骤如下:

编写源文件。

将一个或几个源文件编译链接,生成共享库。

通过 -L -lxxx 的gcc选项链接生成的libxxx.so。例如gcc -fPIC -shared -o libmax.so max.c , -fPIC 是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性; -shared 是链接选项,告诉gcc生成动态库而不是可执行文件

把libxxx.so放入链接库的标准路径,或指定 LD_LIBRARY_PATH,才能运行链接了libxxx.so的程序。

Linux是通过 /etc/ld.so.cache 文件搜寻要链接的动态库的。而 /etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的。

(注意, /etc/ld.so.conf 中并不必包含 /lib 和 /usr/lib,ldconfig程序会自动搜索这两个目录)

我们把要用的 libxx.so 文件所在的路径添加到 /etc/ld.so.conf 中,再以root权限运行 ldconfig 程序,更新 /etc/ld.so.cache ,程序运行时,就可以找到 libxx.so。另外就是通过配置 LD_LIBRARY_PATH 的方式来指定通过某些路径寻找链接的动态库。

ldd 查看程序依赖

理解了动态库的概念之后,当碰到某个程序报错缺少某个库文件时,我们应该怎么查看该程序当前加载了哪些库文件呢?可以用 ldd 命令。

ldd 命令的作用是用来查看程式运行所需的共享库,常用来解决程式因缺少某个库文件而不能运行的一些问题。

例如:查看test程序运行所依赖的库:

[[email protected] testso]# ldd /etc/alternatives/java

linux-vdso.so.1 => (0x00007ffde15f8000)

libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f03f2f8d000)

libdl.so.2 => /lib64/libdl.so.2 (0x00007f03f2d89000)

libc.so.6 => /lib64/libc.so.6 (0x00007f03f29bb000)

/lib64/ld-linux-x86-64.so.2 (0x00007f03f33ab000)

第一列:程序需要依赖什么库

第二列: 系统提供的与程序需要的库所对应的库

第三列:库加载的开始地址

通过上面的信息,我们可以得到以下几个信息:

通过对比第一列和第二列,我们可以分析程序需要依赖的库和系统实际提供的,是否相匹配

通过观察第三列,我们可以知道在当前的库中的符号在对应的进程的地址空间中的开始位置

如果依赖的某个库找不到,通过这个命令可以迅速定位问题所在.

Linux capability

从内核 2.2 开始,Linux 将传统上与超级用户 root 关联的特权划分为不同的单元,称为 capabilites。Capabilites 作为线程(Linux 并不真正区分进程和线程)的属性存在,每个单元可以独立启用和禁用。如此一来,权限检查的过程就变成了:在执行特权操作时,如果进程的有效身份不是 root,就去检查是否具有该特权操作所对应的 capabilites,并以此决定是否可以进行该特权操作。

下面是从 capabilities man page 中摘取的 capabilites 列表:

capability 名称

描述

CAP_AUDIT_CONTROL

启用和禁用内核审计;改变审计过滤规则;检索审计状态和过滤规则

CAP_AUDIT_READ

允许通过 multicast netlink 套接字读取审计日志

CAP_AUDIT_WRITE

将记录写入内核审计日志

CAP_BLOCK_SUSPEND

使用可以阻止系统挂起的特性

CAP_CHOWN

修改文件所有者的权限

CAP_DAC_OVERRIDE

忽略文件的 DAC 访问限制

CAP_DAC_READ_SEARCH

忽略文件读及目录搜索的 DAC 访问限制

CAP_FOWNER

忽略文件属主 ID 必须和进程用户 ID 相匹配的限制

CAP_FSETID

允许设置文件的 setuid 位

CAP_IPC_LOCK

允许锁定共享内存片段

CAP_IPC_OWNER

忽略 IPC 所有权检查

CAP_KILL

允许对不属于自己的进程发送信号

CAP_LEASE

允许修改文件锁的 FL_LEASE 标志

CAP_LINUX_IMMUTABLE

允许修改文件的 IMMUTABLE 和 APPEND 属性标志

CAP_MAC_ADMIN

允许 MAC 配置或状态更改

CAP_MAC_OVERRIDE

覆盖 MAC(Mandatory Access Control)

CAP_MKNOD

允许使用 mknod() 系统调用

CAP_NET_ADMIN

允许执行网络管理任务

CAP_NET_BIND_SERVICE

允许绑定到小于 1024 的端口

CAP_NET_BROADCAST

允许网络广播和多播访问

CAP_NET_RAW

允许使用原始套接字

CAP_SETGID

允许改变进程的 GID

CAP_SETFCAP

允许为文件设置任意的 capabilities

CAP_SETUID

允许改变进程的 UID

CAP_SYS_ADMIN

允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等

CAP_SYS_BOOT

允许重新启动系统

CAP_SYS_CHROOT

允许使用 chroot() 系统调用

CAP_SYS_MODULE

允许插入和删除内核模块

CAP_SYS_NICE

允许提升优先级及设置其他进程的优先级

CAP_SYS_PACCT

允许执行进程的 BSD 式审计

CAP_SYS_PTRACE

允许跟踪任何进程

CAP_SYS_RAWIO

允许直接访问 /devport、/dev/mem、/dev/kmem 及原始块设备

CAP_SYS_RESOURCE

忽略资源限制

CAP_SYS_TIME

允许改变系统时钟

CAP_SYS_TTY_CONFIG

允许配置 TTY 设备

CAP_SYSLOG

允许使用 syslog() 系统调用

CAP_WAKE_ALARM

允许触发一些能唤醒系统的东西(比如 CLOCK_BOOTTIME_ALARM 计时器)

getcap 命令和 setcap 命令分别用来查看和设置程序文件的 capabilities 属性。

例如为 ping 命令文件添加 capabilities

执行 ping 命令所需的 capabilities 为 cap_net_admin 和 cap_net_raw,通过 setcap 命令可以添加它们:

$ sudo setcap cap_net_admin,cap_net_raw+ep /bin/ping

移除添加的 capabilities ,执行下面的命令:

$ sudo setcap cap_net_admin,cap_net_raw-ep /bin/ping

命令中的 ep 分别表示 Effective 和 Permitted 集合(接下来会介绍),+ 号表示把指定的 capabilities 添加到这些集合中,- 号表示从集合中移除(对于 Effective 来说是设置或者清除位)。

解决问题

回到我们开始的问题,由于我们为非 root 用户赋予了使用 80 端口的权限,调用了如下命令:

setcap cap_net_bind_service=+ep /usr/bin/java

当一个可执行文件提升了权限后,运行时加载程序(rtld)— ld.so,它不会与不受信任路径中的库链接。Linux 会为使用了 setcap 或 suid 的程序禁用掉 LD_LIBRARY_PATH。所以就出现了 java 程序加载不到 libjli.so 的情况了,这是 JDK 的一个 bug。

那么既然使用 setcap 后不会加载链接库,我们就可以将 libjli.so 所在的路径添加到 /etc/ld.so.conf/xxx.conf中,例如:

% cat /etc/ld.so.conf.d/java.conf

/usr/java/jdk1.8.0_261-amd64/lib/amd64/jli

使用 ldconfig 重载 so 文件。

[[email protected] jli]# ldconfig -p | grep libjli

libjli.so (libc6,x86-64) => /usr/java/jdk1.8.0_261-amd64/lib/amd64/jli/libjli.so% ldconfig | grep libjli

libjli.so -> libjli.so

.......

这样再次测试就可以了。

参考文章

原文:https://www.cnblogs.com/chenxueqiang/p/13653623.html

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值