JVM crash while memcpy during class load

本秂侑毒 提交于 2019-11-30 08:31:33

we've seen similar errors. our current suspect is jar files which are re-written (by an upgrade process) while the process is running.

Stuart Caie

Answer 1 is correct. The implementation of java.util.zip.* is at fault.

If you replace a zip/jar file that a Java program currently has "open" (has cached the ZipFile/JarFile object), it will use cached table-of-contents (TOC) data it read from the original file, and will try and use that to unpack data in the replaced file. The inflation code is not robust and will outright crash when presented with bad data.

Normal unix programs keep files open while they're working with them. If you overwrite the file, the program using it still has access to the original that it opened, by virtue of the open file descriptor.

OpenJDK's java.util.zip.* implementation chose not to keep file descriptors open for zip/jar files. One reason for this could be that Java is often invoked with hundreds of jar files in the class path, and the designers didn't want to use up hundreds of file descriptors on the jar files alone, leaving none left for the program itself. So they close file descriptors as soon as they've read the jar/zip table of contents, and permanently lose access to the original jar/zip file, should its contents change.

For whatever reason, ZipFile does not or cannot tell a zip/jar file has changed. If it did, it could re-read the TOC or throw some kind of Error if that's not possible.

Furthermore, even if the TOC remained valid, it's a problem that the inflater crashes on faulty data. What if the ZIP table-of-contents was valid but the deflated data stream was deliberately wrong?

Here's a test program that proves java.util.zip.* doesn't keep file descriptors open for zip/jar files and doesn't detect that the zip file has changed.

import java.util.zip.*;
import java.io.*;

public class ZipCrashTest {
    public static void main(String args[]) throws Exception {
        // create some test data
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100000; i++) sb.append("Hello, World\n");
        final byte[] data = sb.toString().getBytes();

        // create a zip file
        try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream("test1.zip"))) {
            zo.putNextEntry(new ZipEntry("world.txt")); zo.write(data, 0, data.length); zo.closeEntry();
            zo.putNextEntry(new ZipEntry("hello.txt")); zo.write(data, 0, data.length); zo.closeEntry();
        }

        // create a second valid zip file, but with different contents
        try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream("test2.zip"))) {
            zo.putNextEntry(new ZipEntry("hello.txt")); zo.write(data, 0, data.length); zo.closeEntry();
            zo.putNextEntry(new ZipEntry("world.txt")); zo.write(data, 0, data.length); zo.closeEntry();
        }

        // open the zip file
        final ZipFile zf = new ZipFile("test1.zip");

        // read the first file from it
        try (InputStream is = zf.getInputStream(zf.getEntry("hello.txt"))) {
            while (is.read() != -1) { /* do nothing with the data */ }
        }

        // replace the contents of test1.zip with the different-but-still-valid test2.zip
        Runtime.getRuntime().exec("cp test2.zip test1.zip");

        // read another entry from test1.zip: it does not detect that the file has changed.
        // the program will crash here
        try (InputStream is = zf.getInputStream(zf.getEntry("world.txt"))) {
            while (is.read() != -1) { /* do nothing */ }
        }
    }
}

Running this program should give you a JVM crash:

# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGBUS (0x7) at pc=0x00007fb0fbbeef72, pid=4140, tid=140398238095104
...
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [libzip.so+0x4f72]  Java_java_util_zip_ZipFile_getZipMessage+0x1132
C  [libzip.so+0x5d7f]  ZIP_GetEntry+0xcf
C  [libzip.so+0x3904]  Java_java_util_zip_ZipFile_getEntry+0xb4
j  java.util.zip.ZipFile.getEntry(J[BZ)J+0
j  java.util.zip.ZipFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;+38
j  ZipCrashTest.main([Ljava/lang/String;)V+476

The main bug filed against the JVM for this is JDK-4425695 : Updating jar files crashes running programs.

RFE 6929479: Add a system property sun.zip.disableMemoryMapping to disable mmap use in ZipFile implements a system property in JDK7 - sun.zip.disableMemoryMapping - which you could use as a workaround.

Issue is zip/JAR file is being overwritten while in use. OpenJDK code for ZIP file format is in native C code any entry lookup, creation requires multiple round-trip of expensive jni invocations. The current native C implementation code uses mmap to map in the central directory table which is a big risk of vm crash when the underlying jar file gets overwritten with new contents while it is still being used by other ZipFile, that is what is happening. Using - Dsun.zip.disableMemoryMapping=true will solve the problem,

JDK9 early access builds are available that has solution for this.

Check the original issue https://bugs.openjdk.java.net/browse/JDK-8142508 that has been fixed in 9 early access build 97.

Other than a plain ol. bug in the JVM(upgrade to the latest version and hope it doesn't happen again) - or some buggy 3. party libraries using JNI, there's 2 other "interesting" things that could cause this.

  1. Hardware failure - bad RAM is often a good candidate ot a corrupted filesystem could cause of a flaky drive could be a culprit too.

  2. If you're running on Solaris, you can get SIGBUS errors if somehow the class/jar file was truncated just when the JVM needs to access it in the cases the JVM mmaps the jar/class file.

I don't know if you have solved the problem because I encountered exactly the same problem. I only use mmap to map a 64KB file into a memory and use memcpy to copy data to/from the buffer. The same error occurred either when I use JNI or when I use JNA. I'm an experienced JNI programmer for years and I'm exactly implemented the same logic in a pure C program which works pretty well.

I assume it's a JDK's bug which trap some SIGs. But I really don't have time to dig it any more. For now I decided to abandon this approach and try other way to do something I wanna do.

If you're interested in why I'm doing this, I just wanna implement a thread safe memory mapped file in JNI. Because in JDK's implementation, the position is a global variable in ByteBuffer(which is mapped from FileChannel). I just wanna use one MemoryMappedBuffer instance to access the content in multiple threads which I've done in a C++ program.

BTW, I'm using JDK 7 in Mac OS X 10.10.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!