Tencent/matrix · error · IllegalStateException

extractZipEntry entry

Error message

extractZipEntry entry ${targetEntry.getName()} failed!

What it means

StreamUtil.extractZipEntry extracts a single zip entry to an output file, but first calls preventZipSlip to verify the entry name doesn't escape the output directory (zip-slip path traversal). If the guard trips, it throws IllegalStateException('extractZipEntry entry <name> failed!'). The library deliberately refuses entries whose resolved path lies outside the target directory.

Solutions

  1. Inspect the offending entry name and sanitize it (strip leading '/', resolve '..') before extraction.
  2. Only extract archives from trusted sources; reject or quarantine suspicious entries.
  3. Extract to a fresh empty directory so entry names can't collide with existing files outside the target.
  4. If you control archive creation, ensure entry names are plain relative paths.

Example fix

// before
StreamUtil.extractZipEntry(zipFile, entry, new File("/data/out")); // entry = "../../evil.so"
// after
String safeName = entry.getName().replace("\\", "/").replaceAll("(^|/)\\.\\.(/|$)", "");
ZipEntry safe = new ZipEntry(safeName);
StreamUtil.extractZipEntry(zipFile, safe, new File("/data/out"));
Defensive patterns

Strategy: validation

Validate before calling

String name = entry.getName().replace('\\', '/');
File outDir = output.getCanonicalFile();
File target = new File(outDir, name).getCanonicalFile();
if (!target.getPath().startsWith(outDir.getPath() + File.separator)) {
    throw new SecurityException("zip-slip entry rejected: " + name);
}

Type guard

static boolean isSafeEntryName(String name) {
    return !name.startsWith("/") && !name.contains("..") && !name.contains("\\");
}

Try / catch

try {
    StreamUtil.extractZipEntry(zipFile, entry, output);
} catch (IllegalStateException e) {
    Log.w(TAG, "Rejected unsafe zip entry", e);
}

Prevention

When it happens

Trigger: Extracting a zip entry whose name contains '..' segments, an absolute path, or a name that resolves outside the given output File's directory — typically from a crafted or third-party hprof/zip file.

Common situations: Processing untrusted or externally supplied archive files; zips created on Windows with backslash separators mis-resolved as separators; renamed entries in repacked archives pointing to parent directories.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Tencent/matrix@3b8293bd65 (2026-09-08). Data as JSON: /api/errors/f8090b3b0af45bed. Report an issue: GitHub.

Appendix: source

Thrown at matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-common/src/main/java/com/tencent/matrix/resource/common/utils/StreamUtil.java:68

    }

    public static boolean preventZipSlip(java.io.File output, String zipEntryName) {

        try {
            if (zipEntryName.contains("..") && new File(output, zipEntryName).getCanonicalPath().startsWith(output.getCanonicalPath() + File.separator)) {
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return true;
        }
        return false;
    }

    public static void extractZipEntry(ZipFile zipFile, ZipEntry targetEntry, File output) throws IOException {

        if (preventZipSlip(output, targetEntry.getName())) {
            throw new IllegalStateException("extractZipEntry entry " + targetEntry.getName() + " failed!");
        }

        InputStream is = null;
        OutputStream os = null;
        try {
            is = new BufferedInputStream(zipFile.getInputStream(targetEntry));
            os = new BufferedOutputStream(new FileOutputStream(output));
            final byte[] buffer = new byte[4096];
            int bytesRead = 0;
            while ((bytesRead = is.read(buffer)) > 0) {
                os.write(buffer, 0, bytesRead);
            }
        } finally {
            closeQuietly(os);
            closeQuietly(is);
        }
    }

View on GitHub (pinned to 3b8293bd65)