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.

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)

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.