apache/hadoop · error · IOException

expanding " + entry.getName() + " would create file outside…

Error message

expanding " + entry.getName() + " would create file outside of " + toDir

What it means

unZip(InputStream, File toDir) throws IOException("expanding <entry> would create file outside of <toDir>") when new File(toDir, entry.getName()).getCanonicalPath() does not start with toDir's canonical path plus File.separator. This is the Zip Slip defense: a archive entry containing '../' segments, an absolute path, or a symlink trick would otherwise write outside the extraction directory. Seeing it means the zip is malicious or corrupt and the guard correctly aborted extraction.

Solutions

  1. Treat the archive as untrusted: reject it, log entry.getName(), and alert — do not try to 'fix' the entry by stripping '../' silently
  2. Re-create the artifact with a trusted archiver (relative entry names, no absolute components) and redeploy
  3. Pre-scan entries with ZipArchiveInputStream and reject any whose resolved path leaves the target dir before calling unZip
  4. Extract into a dedicated, low-privilege directory so even a slipped write has minimal blast radius

Example fix

// before
FileUtil.unZip(uploaded, toDir); // aborts: entry escapes toDir

// after: validate before extracting
try (ZipArchiveInputStream z = new ZipArchiveInputStream(Files.newInputStream(uploaded.toPath()))) {
  String root = toDir.getCanonicalPath() + File.separator;
  for (ZipArchiveEntry e = z.getNextZipEntry(); e != null; e = z.getNextZipEntry()) {
    if (!new File(toDir, e.getName()).getCanonicalPath().startsWith(root)) {
      throw new IOException("Rejected unsafe entry " + e.getName());
    }
  }
}
FileUtil.unZip(uploaded, toDir);
Defensive patterns

Strategy: try-catch

Validate before calling

String root = toDir.getCanonicalPath() + File.separator;
try (ZipArchiveInputStream z = new ZipArchiveInputStream(input)) {
  for (ZipArchiveEntry e = z.getNextZipEntry(); e != null; e = z.getNextZipEntry()) {
    if (!new File(toDir, e.getName()).getCanonicalPath().startsWith(root)) {
      throw new SecurityException("Zip slip entry: " + e.getName());
    }
  }
}

Try / catch

try {
  FileUtil.unZip(in, toDir);
} catch (IOException e) {
  if (e.getMessage() != null && e.getMessage().contains("outside of")) {
    // malicious/corrupt archive: quarantine it, alert, do NOT sanitize automatically
    quarantine(archiveFile);
  } else throw e;
}

Prevention

When it happens

Trigger: unZip() on a crafted zip with entries like '../../etc/passwd' or absolute names like '/etc/cron.d/x'; corrupted archives whose entry names contain path separators at odd places; archives produced by tools emitting entries with leading '../' normalization hazards.

Common situations: Processing user-uploaded or third-party bundles (connectors, plugins, nightly artifacts); supply-chain style zip-slip attack payloads; regression after switching archive producers.

Related errors


AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22). Data as JSON: /api/errors/977f821520e868a0. Report an issue: GitHub.

Appendix: source

Thrown at hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java:750

  /**
   * Given a stream input it will unzip the it in the unzip directory.
   * passed as the second parameter
   * @param inputStream The zip file as input
   * @param toDir The unzip directory where to unzip the zip file.
   * @throws IOException an exception occurred
   */
  public static void unZip(InputStream inputStream, File toDir)
      throws IOException {
    try (ZipArchiveInputStream zip = new ZipArchiveInputStream(inputStream)) {
      int numOfFailedLastModifiedSet = 0;
      String targetDirPath = toDir.getCanonicalPath() + File.separator;
      for(ZipArchiveEntry entry = zip.getNextZipEntry();
          entry != null;
          entry = zip.getNextZipEntry()) {
        if (!entry.isDirectory()) {
          File file = new File(toDir, entry.getName());
          if (!file.getCanonicalPath().startsWith(targetDirPath)) {
            throw new IOException("expanding " + entry.getName()
                + " would create file outside of " + toDir);
          }
          File parent = file.getParentFile();
          if (!parent.mkdirs() &&
              !parent.isDirectory()) {
            throw new IOException("Mkdirs failed to create " +
                parent.getAbsolutePath());
          }
          try (OutputStream out = Files.newOutputStream(file.toPath())) {
            IOUtils.copyBytes(zip, out, BUFFER_SIZE);
          }
          if (!file.setLastModified(entry.getTime())) {
            numOfFailedLastModifiedSet++;
          }
          if (entry.getPlatform() == ZipArchiveEntry.PLATFORM_UNIX) {
            Files.setPosixFilePermissions(file.toPath(), permissionsFromMode(entry.getUnixMode()));
          }
        }

View on GitHub (pinned to 2add963021)