From 989fac860a187b8b212ed8eb107dbedb30f5b895 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Csahvx655-wq=E2=80=9D?= <“sahvx655@gmail.com”>
Date: Tue, 2 Jun 2026 12:20:07 +0530
Subject: [PATCH 1/2] Fix: Mitigate Zip Slip / Path Traversal (CWE-22)
vulnerabilities during archive extraction
---
bundlerepository/pom.xml | 4 ++--
.../org/apache/felix/bundlerepository/impl/FileUtil.java | 7 +++++++
.../apache/felix/bundlerepository/impl/ObrGogoCommand.java | 7 +++++++
.../src/main/java/org/apache/felix/gogo/command/Util.java | 7 +++++++
4 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/bundlerepository/pom.xml b/bundlerepository/pom.xml
index be6c3ebf8a..ba938c8462 100644
--- a/bundlerepository/pom.xml
+++ b/bundlerepository/pom.xml
@@ -107,8 +107,8 @@
org.apache.maven.plugins
maven-compiler-plugin
- 1.5
- 1.5
+ 1.8
+ 1.8
diff --git a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FileUtil.java b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FileUtil.java
index 9a27e3ff49..c5876a561b 100644
--- a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FileUtil.java
+++ b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FileUtil.java
@@ -101,6 +101,8 @@ public static void unjar(JarInputStream jis, File dir)
// Reusable buffer.
byte[] buffer = new byte[4096];
+ String canonicalBase = dir.getCanonicalPath();
+
// Loop through JAR entries.
for (JarEntry je = jis.getNextJarEntry();
je != null;
@@ -112,6 +114,11 @@ public static void unjar(JarInputStream jis, File dir)
}
File target = new File(dir, je.getName());
+ String canonicalTarget = target.getCanonicalPath();
+ if (!canonicalTarget.startsWith(canonicalBase + File.separator) && !canonicalTarget.equals(canonicalBase))
+ {
+ throw new IOException("JAR entry resolves outside the target directory: " + je.getName());
+ }
// Check to see if the JAR entry is a directory.
if (je.isDirectory())
diff --git a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/ObrGogoCommand.java b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/ObrGogoCommand.java
index 4d3f7a6ebe..81c5e4864b 100644
--- a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/ObrGogoCommand.java
+++ b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/ObrGogoCommand.java
@@ -754,6 +754,8 @@ public static void unjar(JarInputStream jis, File dir)
// Reusable buffer.
byte[] buffer = new byte[4096];
+ String canonicalBase = dir.getCanonicalPath();
+
// Loop through JAR entries.
for (JarEntry je = jis.getNextJarEntry();
je != null;
@@ -765,6 +767,11 @@ public static void unjar(JarInputStream jis, File dir)
}
File target = new File(dir, je.getName());
+ String canonicalTarget = target.getCanonicalPath();
+ if (!canonicalTarget.startsWith(canonicalBase + File.separator) && !canonicalTarget.equals(canonicalBase))
+ {
+ throw new IOException("JAR entry resolves outside the target directory: " + je.getName());
+ }
// Check to see if the JAR entry is a directory.
if (je.isDirectory())
diff --git a/gogo/command/src/main/java/org/apache/felix/gogo/command/Util.java b/gogo/command/src/main/java/org/apache/felix/gogo/command/Util.java
index 21a84272a1..fc0ac73a47 100644
--- a/gogo/command/src/main/java/org/apache/felix/gogo/command/Util.java
+++ b/gogo/command/src/main/java/org/apache/felix/gogo/command/Util.java
@@ -155,6 +155,8 @@ public static void unjar(JarInputStream jis, File dir)
// Reusable buffer.
byte[] buffer = new byte[4096];
+ String canonicalBase = dir.getCanonicalPath();
+
// Loop through JAR entries.
for (JarEntry je = jis.getNextJarEntry();
je != null;
@@ -166,6 +168,11 @@ public static void unjar(JarInputStream jis, File dir)
}
File target = new File(dir, je.getName());
+ String canonicalTarget = target.getCanonicalPath();
+ if (!canonicalTarget.startsWith(canonicalBase + File.separator) && !canonicalTarget.equals(canonicalBase))
+ {
+ throw new IOException("JAR entry resolves outside the target directory: " + je.getName());
+ }
// Check to see if the JAR entry is a directory.
if (je.isDirectory())
From 3d0f04977565fdf5bd004b25b15281469c48c583 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Csahvx655-wq=E2=80=9D?= <“sahvx655@gmail.com”>
Date: Tue, 2 Jun 2026 12:27:29 +0530
Subject: [PATCH 2/2] Test: Add regression tests for Zip Slip path traversal
mitigation
---
.../bundlerepository/impl/FileUtilTest.java | 82 +++++++++++++++++++
.../apache/felix/gogo/command/UtilTest.java | 36 ++++++++
2 files changed, 118 insertions(+)
create mode 100644 bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/FileUtilTest.java
diff --git a/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/FileUtilTest.java b/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/FileUtilTest.java
new file mode 100644
index 0000000000..128bd05fd7
--- /dev/null
+++ b/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/FileUtilTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.bundlerepository.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+
+import junit.framework.TestCase;
+
+public class FileUtilTest extends TestCase
+{
+ public void testUnjarZipSlip() throws Exception
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ JarOutputStream jos = new JarOutputStream(baos);
+ try
+ {
+ JarEntry entry = new JarEntry("../../evil.txt");
+ jos.putNextEntry(entry);
+ jos.write("malicious content".getBytes());
+ jos.closeEntry();
+ }
+ finally
+ {
+ jos.close();
+ }
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ JarInputStream jis = new JarInputStream(bais);
+
+ File targetDir = new File("target/extraction-test-fileutil");
+ targetDir.mkdirs();
+
+ try
+ {
+ FileUtil.unjar(jis, targetDir);
+ fail("Expected IOException due to Zip Slip path traversal");
+ }
+ catch (IOException e)
+ {
+ assertTrue(e.getMessage().contains("resolves outside the target directory"));
+ }
+ finally
+ {
+ deleteDirectory(targetDir);
+ }
+ }
+
+ private void deleteDirectory(File dir)
+ {
+ File[] files = dir.listFiles();
+ if (files != null)
+ {
+ for (File f : files)
+ {
+ deleteDirectory(f);
+ }
+ }
+ dir.delete();
+ }
+}
diff --git a/gogo/command/src/test/java/org/apache/felix/gogo/command/UtilTest.java b/gogo/command/src/test/java/org/apache/felix/gogo/command/UtilTest.java
index 89008cf744..e779771fc8 100644
--- a/gogo/command/src/test/java/org/apache/felix/gogo/command/UtilTest.java
+++ b/gogo/command/src/test/java/org/apache/felix/gogo/command/UtilTest.java
@@ -46,4 +46,40 @@ public void relativeUri() throws Exception {
u = Util.resolveUri(session, "three");
assertEquals(new File("./three").getCanonicalFile().toURI().toString(), u);
}
+
+ @Test
+ public void testUnjarZipSlip() throws Exception {
+ java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
+ try (java.util.jar.JarOutputStream jos = new java.util.jar.JarOutputStream(baos)) {
+ java.util.jar.JarEntry entry = new java.util.jar.JarEntry("../../evil.txt");
+ jos.putNextEntry(entry);
+ jos.write("malicious content".getBytes());
+ jos.closeEntry();
+ }
+
+ java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(baos.toByteArray());
+ java.util.jar.JarInputStream jis = new java.util.jar.JarInputStream(bais);
+
+ File targetDir = new File("target/extraction-test-util");
+ targetDir.mkdirs();
+
+ try {
+ Util.unjar(jis, targetDir);
+ org.junit.Assert.fail("Expected IOException due to Zip Slip path traversal");
+ } catch (java.io.IOException e) {
+ org.junit.Assert.assertTrue(e.getMessage().contains("resolves outside the target directory"));
+ } finally {
+ deleteDirectory(targetDir);
+ }
+ }
+
+ private void deleteDirectory(File dir) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ deleteDirectory(f);
+ }
+ }
+ dir.delete();
+ }
}