From f6812f151878c48fc62fb0f40b8926de5a93a7bd Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Mon, 27 Jul 2026 13:04:19 +0000
Subject: [PATCH] Fix java/zipslip CodeQL alerts by validating archive entry paths (#761)

---
 opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java |   45 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java
index 31b7883..9c80de3 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java
@@ -12,6 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.util;
 
@@ -28,6 +29,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import org.forgerock.opendj.ldap.DN;
 import org.opends.server.DirectoryServerTestCase;
@@ -35,6 +38,7 @@
 import org.opends.server.api.Backupable;
 import org.opends.server.types.BackupConfig;
 import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.DirectoryException;
 import org.opends.server.types.RestoreConfig;
 import org.testng.Reporter;
 import org.testng.annotations.BeforeClass;
@@ -214,6 +218,47 @@
     cleanDirectories(sourceDirectory, backupPath);
   }
 
+  /**
+   * A restore must reject archive entries which resolve outside of the restore
+   * directory (zip slip) and must not write any file outside of it.
+   */
+  @Test
+  public void testRestoreRejectsZipSlipEntries() throws Exception
+  {
+    String label = "zipslip";
+    Path sourceDirectory = createSourceDirectory(label);
+    Backupable backupable = buildBackupable(sourceDirectory, 1);
+    BackupDirectory backupDir = buildBackupDir(label);
+    BackupConfig backupConfig = new BackupConfig(backupDir, BACKUP_ID, false);
+    RestoreConfig restoreConfig = new RestoreConfig(backupDir, BACKUP_ID, false);
+
+    BackupManager backupManager = new BackupManager(BACKEND_ID);
+    backupManager.createBackup(backupable, backupConfig);
+
+    // replace the archive with one containing an entry escaping the restore directory
+    File archiveFile = new File(backupDir.getPath(), getArchiveFileName(BACKUP_ID));
+    try (ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(archiveFile)))
+    {
+      zipOutput.putNextEntry(new ZipEntry("../evil.txt"));
+      zipOutput.write(getBytes("evil"));
+      zipOutput.closeEntry();
+    }
+
+    File escapedFile = new File(sourceDirectory.toFile().getParentFile(), "evil.txt");
+    try
+    {
+      backupManager.restoreBackup(backupable, restoreConfig);
+      fail("Expected restoreBackup to reject a zip entry escaping the restore directory");
+    }
+    catch (DirectoryException expected)
+    {
+      assertThat(expected.getMessage()).contains("evil.txt");
+    }
+    assertThat(escapedFile).doesNotExist();
+
+    cleanDirectories(sourceDirectory, backupDir.getPath());
+  }
+
   @Test
   public void testCreateDirectoryWithNumericSuffix() throws Exception
   {

--
Gitblit v1.10.0