Skip to content

ManifestVersionReader leaks a ZipFile Inflater per JAR (openStream never closed) #6120

Description

@Khang-NT

Description

ManifestVersionReader.readManifestFiles() enumerates every META-INF/MANIFEST.MF on the classpath and passes URL.openStream() into new Manifest(InputStream) without closing the stream.

java.util.jar.Manifest(InputStream) reads the stream and does not close it. For jar: URLs that stream is a ZipFileInflaterInputStream. Closing it is what calls ZipFile$CleanableResource.releaseInflater(). If it is never closed, each JAR leaves a live java.util.zip.Inflater (~64 KiB zlib window).

This still matches main and 8.41.0 / 8.52.0.

Call site

final Enumeration<URL> resources =
    ClassLoader.getSystemClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
  try {
    final Manifest manifest = new Manifest(resources.nextElement().openStream());
    // ...
  } catch (Exception e) {
    // ignore
  }
}

Triggered from ManifestVersionDetector.checkForMixedVersions()InitUtil.shouldInit()Sentry.init().

Observed

Compose Desktop / packaged JVM app with ~80 JARs on the classpath (sentry-java 8.41.0):

  • GC.class_histogram: 168 live java.util.zip.Inflater
  • async-profiler event=java.util.zip.Inflater.<init> from process start: 79 / 174 constructors (45%) are
java.util.zip.Inflater.<init>
java.util.zip.ZipFile$CleanableResource.getInflater
java.util.zip.ZipFile$ZipFileInflaterInputStream.<init>
java.util.zip.ZipFile.getInputStream
java.util.jar.JarFile.getBytes
java.util.jar.JarFile.checkForSpecialAttributes
java.util.jar.JarFile.isMultiRelease
java.util.jar.JarFile.getEntry
sun.net.www.protocol.jar.URLJarFile.getEntry
sun.net.www.protocol.jar.JarURLConnection.connect
sun.net.www.protocol.jar.JarURLConnection.getInputStream
java.net.URL.openStream
io.sentry.internal.ManifestVersionReader.readManifestFiles
io.sentry.ManifestVersionDetector.checkForMixedVersions
io.sentry.util.InitUtil.shouldInit
io.sentry.Sentry.init

Suggested fix

try (InputStream is = resources.nextElement().openStream()) {
  final Manifest manifest = new Manifest(is);
  // existing attribute handling
} catch (Exception e) {
  // ignore
}

Manifest does not take ownership of the stream, so try-with-resources is required even on the success path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions