Tencent/tinker · error · ZipException

Filename contains NUL byte: ${Arrays.toString(nameBytes)}

Error message

Filename contains NUL byte: ${Arrays.toString(nameBytes)}

What it means

After reading an entry's name bytes from the central directory, TinkerZipEntry scans them for a NUL (0x00) byte and rejects the archive if one is present, because zip names are NUL-terminated C-style strings and an embedded NUL would truncate/ambiguate the name downstream. This is both a corruption detector and a hardening measure against zip-slip/name-confusion attacks.

Source

Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipEntry.java:168

            charset = Charset.forName("UTF-8");
        }
        compressionMethod = it.readShort() & 0xffff;
        time = it.readShort() & 0xffff;
        modDate = it.readShort() & 0xffff;
        // These are 32-bit values in the file, but 64-bit fields in this object.
        crc = ((long) it.readInt()) & 0xffffffffL;
        compressedSize = ((long) it.readInt()) & 0xffffffffL;
        size = ((long) it.readInt()) & 0xffffffffL;
        int nameLength = it.readShort() & 0xffff;
        int extraLength = it.readShort() & 0xffff;
        int commentByteCount = it.readShort() & 0xffff;
        // This is a 32-bit value in the file, but a 64-bit field in this object.
        it.seek(42);
        localHeaderRelOffset = ((long) it.readInt()) & 0xffffffffL;
        byte[] nameBytes = new byte[nameLength];
        Streams.readFully(cdStream, nameBytes, 0, nameBytes.length);
        if (containsNulByte(nameBytes)) {
            throw new ZipException("Filename contains NUL byte: " + Arrays.toString(nameBytes));
        }
        name = new String(nameBytes, 0, nameBytes.length, charset);
        if (extraLength > 0) {
            extra = new byte[extraLength];
            Streams.readFully(cdStream, extra, 0, extraLength);
        }
        if (commentByteCount > 0) {
            byte[] commentBytes = new byte[commentByteCount];
            Streams.readFully(cdStream, commentBytes, 0, commentByteCount);
            comment = new String(commentBytes, 0, commentBytes.length, charset);
        }
        /*if (isZip64) {
            Zip64.parseZip64ExtendedInfo(this, true *//* from central directory *//*);
        }*/
    }

    private static boolean containsNulByte(byte[] bytes) {
        for (byte b : bytes) {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Verify the archive's integrity before parsing (unzip -t / java.util.zip round-trip) and re-fetch or regenerate it if the central directory is damaged.
  2. For untrusted input, validate entries with a strict pre-pass (name length, allowed characters, no NUL) and quarantine archives that fail.
  3. If you produce archives programmatically, ensure name fields are written with the exact UTF-8 byte length and contain no NUL characters.

Example fix

// before: parsing an untrusted archive directly
TinkerZipFile zf = new TinkerZipFile(untrustedFile); // ZipException: Filename contains NUL byte

// after: pre-validate with the platform reader, then hand off only clean archives
try (java.util.zip.ZipFile probe = new java.util.zip.ZipFile(untrustedFile)) {
    java.util.Enumeration<? extends java.util.zip.ZipEntry> es = probe.entries();
    while (es.hasMoreElements()) { /* name sanity checks here */ }
} catch (java.io.IOException e) {
    throw new IllegalArgumentException("rejected malformed archive", e);
}
TinkerZipFile zf = new TinkerZipFile(untrustedFile);
Defensive patterns

Strategy: validation

Validate before calling

// Reject archives with NUL bytes or unsafe characters in entry names before processing
boolean namesAreClean(java.io.File f) throws java.io.IOException {
    try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(f)) {
        java.util.Enumeration<? extends java.util.zip.ZipEntry> es = zf.entries();
        while (es.hasMoreElements()) {
            String n = es.nextElement().getName();
            if (n.indexOf('\u0000') >= 0 || n.startsWith("/") || n.contains("..")) return false;
        }
        return true;
    }
}

Try / catch

try {
    TinkerZipFile zf = new TinkerZipFile(file);
    zf.close();
} catch (java.util.zip.ZipException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Filename contains NUL byte")) {
        // malformed or hostile archive: quarantine, never retry
        quarantine(file);
        return;
    }
    throw e;
}

Prevention

When it happens

Trigger: Constructing TinkerZipEntry from a central directory stream where the declared nameLength bytes include a 0x00 — caused by a corrupt central directory (wrong nameLength field), truncated downloads, or a deliberately crafted malicious archive.

Common situations: Archives corrupted in transit or by buggy repackaging tools that mis-write the name length field; hostile zips crafted to smuggle paths; processing untrusted uploads where malformed entries must be rejected rather than crash or misroute.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/25d7bc899bc36a2c. Report an issue: GitHub.