记一次Java崩溃崩溃问题——IPv6 与 glibc的bug

189 阅读2分钟

最近一段时间,项目组的后端和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的问题导致的。后来参考了下:googleonlinesecurity.blogspot.com/2016/02/cve… 这里面提到,glibc在2.9-2.22的版本,在使用getaddrinfo()时,如果返回的响应过大,会使得应用程序崩溃。

我们可以通过指令:

ldd --version

来查看我们的glibc版本。这次我的系统的版本是2.14,所以可能是存在该问题的。

解决方案目前有三:

  1. 加参数。 在java的启动参数上加上“-Djava.net.preferIPv4Stack=true” ,优先使用IPv4格式的地址,这样就可以避免使用glibc里面那个有bug的解析的IPv6的方法。如果急着上线,可以临时尝试这办法。
  2. 打补丁。这应该是最稳的办法。这个可以查看:googleonlinesecurity.blogspot.jp/2016/02/cve… 或者是搜一下CVE-2015-7547漏洞打补丁的相关方法
  3. 在Linux限制可以接收DNS响应大小,使其不至于过大而引起glibc里面的getaddrinfo()方法堆栈溢出。