You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by li...@apache.org on 2026/10/06 10:41:23 UTC

(kylin) 03/20: KYLIN-6105 improve OpsService backup file path check

This is an automated email from the ASF dual-hosted git repository.

liyang-kylin pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit f848745a831de8d67846e35404935714fbde8e95
Author: Li Yang <li...@apache.org>
AuthorDate: Fri Oct 2 10:15:41 2026 +0800

    KYLIN-6105 improve OpsService backup file path check
---
 .../org/apache/kylin/rest/service/OpsService.java  | 38 +++++++++++++++-------
 .../apache/kylin/rest/service/OpsServiceTest.java  | 30 +++++++++++++++++
 2 files changed, 57 insertions(+), 11 deletions(-)

diff --git a/src/ops-service/src/main/java/org/apache/kylin/rest/service/OpsService.java b/src/ops-service/src/main/java/org/apache/kylin/rest/service/OpsService.java
index 662aebc5db..b1685b68b3 100644
--- a/src/ops-service/src/main/java/org/apache/kylin/rest/service/OpsService.java
+++ b/src/ops-service/src/main/java/org/apache/kylin/rest/service/OpsService.java
@@ -32,6 +32,7 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.fs.FSDataInputStream;
 import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileStatus;
@@ -170,25 +171,40 @@ public class OpsService {
             Path path = new Path(pathStr);
             FileSystem fs = HadoopUtil.getWorkingFileSystem();
             if (fs.exists(path)) {
-                if (!pathStr.startsWith(META_BACKUP_PATH)) {
-                    return "can not delete path not in metadata backup dir " + META_BACKUP_PATH;
-                }
-                String rootPath = getMetaBackupStoreDir(project);
-                if (!pathStr.startsWith(rootPath) || pathStr.equals(rootPath)) {
+                Path rootPath = normalizePath(fs, new Path(getMetaBackupStoreDir(project)));
+                Path resolvedPath = normalizePath(fs, path);
+                if (!isStrictDescendant(resolvedPath, rootPath)) {
                     return "can not delete path not in metadata backup dir " + rootPath;
                 }
-                fs.delete(path, true);
-                log.info("Delete metadata backup {} succeed.", path.toUri());
-                if (fs.listStatus(path.getParent()).length == 0) {
-                    fs.delete(path.getParent(), true);
-                    log.info("Delete project path {} for no metadata backup exist.", path.getParent().toUri());
+                fs.delete(resolvedPath, true);
+                log.info("Delete metadata backup {} succeed.", resolvedPath.toUri());
+                Path parentPath = resolvedPath.getParent();
+                if (parentPath != null && isDescendantOrSelf(parentPath, rootPath) && fs.exists(parentPath)
+                        && fs.listStatus(parentPath).length == 0) {
+                    fs.delete(parentPath, true);
+                    log.info("Delete project path {} for no metadata backup exist.", parentPath.toUri());
                 }
-                return path + " delete succeed.";
+                return resolvedPath + " delete succeed.";
             }
         }
         return pathStr + " path not exist";
     }
 
+    private static boolean isStrictDescendant(Path candidate, Path root) {
+        return isDescendantOrSelf(candidate, root) && !candidate.equals(root);
+    }
+
+    private static Path normalizePath(FileSystem fs, Path path) throws IOException {
+        Path normalized = fs.exists(path) ? fs.resolvePath(path) : path;
+        return new Path(normalized.toUri().normalize()).makeQualified(fs.getUri(), fs.getWorkingDirectory());
+    }
+
+    private static boolean isDescendantOrSelf(Path candidate, Path root) {
+        return StringUtils.equals(candidate.toUri().getScheme(), root.toUri().getScheme())
+                && StringUtils.equals(candidate.toUri().getAuthority(), root.toUri().getAuthority())
+                && (candidate.equals(root) || candidate.toUri().getPath().startsWith(root.toUri().getPath() + PATH_SEP));
+    }
+
     public void cancelAndDeleteMetadataBackup(String rootPath, String project) throws IOException {
         MetadataBackup.cancelAndAsyncDeleteBackup(rootPath, project);
     }
diff --git a/src/ops-service/src/test/java/org/apache/kylin/rest/service/OpsServiceTest.java b/src/ops-service/src/test/java/org/apache/kylin/rest/service/OpsServiceTest.java
index ab0d6533ae..c325bf0f41 100644
--- a/src/ops-service/src/test/java/org/apache/kylin/rest/service/OpsServiceTest.java
+++ b/src/ops-service/src/test/java/org/apache/kylin/rest/service/OpsServiceTest.java
@@ -187,6 +187,36 @@ public class OpsServiceTest extends NLocalFileMetadataTestCase {
                 .contains("path not exist" + " : " + "can not delete path not in metadata backup dir"));
     }
 
+    @Test
+    public void testDeleteBackupPathConfinement() throws IOException {
+        FileSystem fs = HadoopUtil.getWorkingFileSystem();
+        String project = "default";
+        String siblingProject = "default_extra";
+        Path projectRoot = new Path(OpsService.getMetaBackupStoreDir(project));
+        Path backup = new Path(projectRoot, "backup-id");
+        Path sibling = new Path(OpsService.getMetaBackupStoreDir(siblingProject), "keep");
+        fs.mkdirs(backup);
+        fs.mkdirs(sibling);
+
+        try {
+            Assert.assertTrue(OpsService.deleteMetadataBackup(projectRoot.toString(), project)
+                    .contains("can not delete path not in metadata backup dir"));
+            Assert.assertTrue(fs.exists(projectRoot));
+
+            String traversal = new Path(projectRoot, "backup-id/../../default_extra/keep").toString();
+            Assert.assertTrue(OpsService.deleteMetadataBackup(traversal, project)
+                    .contains("can not delete path not in metadata backup dir"));
+            Assert.assertTrue(fs.exists(sibling));
+
+            Assert.assertTrue(
+                    OpsService.deleteMetadataBackup(backup.toString(), project).contains("delete succeed"));
+            Assert.assertFalse(fs.exists(backup));
+            Assert.assertTrue(fs.exists(sibling));
+        } finally {
+            fs.delete(new Path(OpsService.PROJECT_METADATA_BACKUP_PATH), true);
+        }
+    }
+
     @Test
     public void testMetadataRestoreStatus() throws IOException {
         String backupPath = opsService.backupMetadata(UnitOfWork.GLOBAL_UNIT);