记一次Java崩溃崩溃问题——IPv6 与 glibc的bug
最近一段时间,项目组的后端和APP端进行联调的时候,会发现测试服务器的后端服务器会经常莫名其妙地崩溃,最后会生成一份崩溃日志(hs_err_pid.log)。日志的大概信息如下:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=xxxxxxxxxxxxxxxxx, pid=xxxxxxxxxxxxx, tid=xxxxxxxxxxxxxxxxx
#
# JRE version: Java(TM) SE Runtime Environment (8.0_121-b13) (build 1.8.0_121-b13)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C [libresolv.so.2+0x7a91] __libc_res_nquery+0x1c1
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
……
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j java.net.Inet6AddressImpl.lookupAllHostAddr(Ljava/lang/String;)[Ljava/net/InetAddress;+0
j java.net.InetAddress$2.lookupAllHostAddr(Ljava/lang/String;)[Ljava/net/InetAddress;+4
j java.net.InetAddress.getAddressesFromNameService(Ljava/lang/String;Ljava/net/InetAddress;)[Ljava/net/InetAddress;+51
J 20054 C1 java.net.InetAddress.getAllByName0(Ljava/lang/String;Ljava/net/InetAddress;Z)[Ljava/net/InetAddress; (57 bytes)
J 19009 C1 java.net.InetAddress.getAllByName(Ljava/lang/String;Ljava/net/InetAddress;)[Ljava/net/InetAddress; (387 bytes)
J 19451 C1 java.net.InetSocketAddress.<init>(Ljava/lang/String;I)V (47 bytes)
J 19459 C1 sun.net.NetworkClient.doConnect(Ljava/lang/String;I)Ljava/net/Socket; (176 bytes)
根据日志中的“java.net.Inet6AddressImpl.lookupAllHostAddr”,可以初步怀疑是跟JVM在接收跟IPv6的时候出的错误导致的崩溃。再结合“C [libresolv.so.2+0x7a91] __libc_res_nquery+0x1c1” 这部分日志,可以认为,是 由于JVM的本地方法导致的问题。
libresolv.so.2 是glibc编译后的的库。由此我们可能可以怀疑是由于glibc的问题导致的。后来参考了下:https://googleonlinesecurity.blogspot.com/2016/02/cve-2015-7547-glibc-getaddrinfo-stack.html
这里面提到,glibc在2.9-2.22的版本,在使用getaddrinfo()时,如果返回的响应过大,会使得应用程序崩溃。
我们可以通过指令:
ldd --version
来查看我们的glibc版本。这次我的系统的版本是2.14,所以可能是存在该问题的。
解决方案目前有三:
- 加参数。 在java的启动参数上加上“-Djava.net.preferIPv4Stack=true” ,优先使用IPv4格式的地址,这样就可以避免使用glibc里面那个有bug的解析的IPv6的方法。如果急着上线,可以临时尝试这办法。
- 打补丁。这应该是最稳的办法。这个可以查看:https://googleonlinesecurity.blogspot.jp/2016/02/cve-2015-7547-glibc-getaddrinfo-stack.html
或者是搜一下CVE-2015-7547漏洞打补丁的相关方法 - 在Linux限制可以接收DNS响应大小,使其不至于过大而引起glibc里面的getaddrinfo()方法堆栈溢出。