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
- 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.
- For untrusted input, validate entries with a strict pre-pass (name length, allowed characters, no NUL) and quarantine archives that fail.
- 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
- Run untrusted archives through a strict validation pass (magic, per-entry name rules, no NUL) before parsing.
- Verify integrity (unzip -t or CRC round-trip) after any transfer.
- When writing archives, set name lengths to the exact UTF-8 byte count and forbid NUL in generated names.
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
- Expected ${DEX_IN_JAR_NAME} in ${file}
- CRC mismatch
- Size mismatch
- Entry already exists: {}
- Too many entries for the zip file format's 16-bit entry coun
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/25d7bc899bc36a2c.
Report an issue: GitHub.