This is an automated email from the ASF dual-hosted git repository. Caideyipi pushed a commit to branch ger-shop-2 in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 7510f23a3277d72efc1df589e784821a1ba89e17 Author: Caideyipi <[email protected]> AuthorDate: Tue May 12 17:11:44 2026 +0800 Load: Harden LOAD TSFILE source path validation (#17624) * Load pri * sp * MAINTAIN * rollback * Add * change * canonical * line * Pre --- .../java/org/apache/iotdb/db/conf/IoTDBConfig.java | 83 ++++++++++++ .../org/apache/iotdb/db/conf/IoTDBDescriptor.java | 28 +++++ .../protocol/legacy/loader/TsFileLoader.java | 2 +- .../protocol/thrift/IoTDBDataNodeReceiver.java | 3 +- .../plan/analyze/load/LoadTsFileAnalyzer.java | 2 +- .../plan/scheduler/load/LoadTsFileScheduler.java | 2 +- .../plan/statement/crud/LoadTsFileStatement.java | 70 ++++++++++- .../load/active/ActiveLoadTsFileLoader.java | 3 +- .../statement/crud/LoadTsFileStatementTest.java | 139 +++++++++++++++++++++ .../conf/iotdb-system.properties.template | 11 ++ 10 files changed, 333 insertions(+), 10 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java index d3f13d14392..07c73b43d87 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java @@ -61,8 +61,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Field; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -324,6 +326,14 @@ public class IoTDBConfig { tierDataDirs[0][0] + File.separator + IoTDBConstant.LOAD_TSFILE_FOLDER_NAME }; + private String[] loadTsFileAllowedDirs = new String[0]; + + private CanonicalPaths loadTsFileDirCanonicalPaths = canonicalPaths(loadTsFileDirs); + + private CanonicalPaths loadTsFileAllowedDirCanonicalPaths = canonicalPaths(loadTsFileAllowedDirs); + + private boolean loadTsFileSourcePathCheckEnabled = false; + /** Strategy of multiple directories. */ private String multiDirStrategyClassName = null; @@ -1394,6 +1404,10 @@ public class IoTDBConfig { for (int i = 0; i < loadActiveListeningDirs.length; i++) { loadActiveListeningDirs[i] = addDataHomeDir(loadActiveListeningDirs[i]); } + for (int i = 0; i < loadTsFileAllowedDirs.length; i++) { + loadTsFileAllowedDirs[i] = addDataHomeDir(loadTsFileAllowedDirs[i]); + } + loadTsFileAllowedDirCanonicalPaths = canonicalPaths(loadTsFileAllowedDirs); loadActiveListeningPipeDir = addDataHomeDir(loadActiveListeningPipeDir); loadActiveListeningFailDir = addDataHomeDir(loadActiveListeningFailDir); udfDir = addDataHomeDir(udfDir); @@ -1589,6 +1603,36 @@ public class IoTDBConfig { return this.loadTsFileDirs; } + public String[] getLoadTsFileAllowedDirs() { + return this.loadTsFileAllowedDirs.length == 0 + ? getLoadTsFileDirs() + : this.loadTsFileAllowedDirs; + } + + public Path[] getLoadTsFileAllowedDirCanonicalPaths() throws FileNotFoundException { + return (this.loadTsFileAllowedDirs.length == 0 + ? this.loadTsFileDirCanonicalPaths + : this.loadTsFileAllowedDirCanonicalPaths) + .getPaths(); + } + + public boolean isLoadTsFileSourcePathCheckEnabled() { + return loadTsFileSourcePathCheckEnabled; + } + + public void setLoadTsFileSourcePathCheckEnabled(boolean loadTsFileSourcePathCheckEnabled) { + this.loadTsFileSourcePathCheckEnabled = loadTsFileSourcePathCheckEnabled; + } + + public void setLoadTsFileAllowedDirs(String[] loadTsFileAllowedDirs) { + final String[] newLoadTsFileAllowedDirs = new String[loadTsFileAllowedDirs.length]; + for (int i = 0; i < loadTsFileAllowedDirs.length; i++) { + newLoadTsFileAllowedDirs[i] = addDataHomeDir(loadTsFileAllowedDirs[i]); + } + this.loadTsFileAllowedDirs = newLoadTsFileAllowedDirs; + this.loadTsFileAllowedDirCanonicalPaths = canonicalPaths(newLoadTsFileAllowedDirs); + } + public void formulateLoadTsFileDirs(String[][] tierDataDirs) { if (tierDataDirs.length < 1) { logger.warn("No data directory is set. loadTsFileDirs is kept as the default value."); @@ -1606,6 +1650,45 @@ public class IoTDBConfig { // or the newLoadTsFileDirs will be used in the middle of the process // and cause the undefined behavior. this.loadTsFileDirs = newLoadTsFileDirs; + this.loadTsFileDirCanonicalPaths = canonicalPaths(newLoadTsFileDirs); + } + + private static CanonicalPaths canonicalPaths(final String[] dirs) { + final Path[] paths = new Path[dirs.length]; + for (int i = 0; i < dirs.length; i++) { + try { + paths[i] = new File(dirs[i]).getCanonicalFile().toPath(); + } catch (final IOException e) { + return new CanonicalPaths( + String.format( + "Failed to resolve canonical path for Load TsFile allowed directory %s: %s", + dirs[i], e.getMessage())); + } + } + return new CanonicalPaths(paths); + } + + private static class CanonicalPaths { + + private final Path[] paths; + private final String errorMessage; + + private CanonicalPaths(final Path[] paths) { + this.paths = paths; + this.errorMessage = null; + } + + private CanonicalPaths(final String errorMessage) { + this.paths = new Path[0]; + this.errorMessage = errorMessage; + } + + private Path[] getPaths() throws FileNotFoundException { + if (errorMessage != null) { + throw new FileNotFoundException(errorMessage); + } + return paths; + } } public String getSchemaDir() { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java index b01fca1a88f..3cec197ac0f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java @@ -2449,6 +2449,17 @@ public class IoTDBDescriptor { properties.getProperty( "load_write_throughput_bytes_per_second", String.valueOf(conf.getLoadWriteThroughputBytesPerSecond())))); + + conf.setLoadTsFileAllowedDirs( + Arrays.stream(properties.getProperty("load_tsfile_allowed_dirs", "").trim().split(",")) + .filter(dir -> !dir.isEmpty()) + .toArray(String[]::new)); + conf.setLoadTsFileSourcePathCheckEnabled( + Boolean.parseBoolean( + properties.getProperty( + "load_tsfile_source_path_check_enable", + Boolean.toString(conf.isLoadTsFileSourcePathCheckEnabled())))); + conf.setLoadTabletConversionThresholdBytes( Long.parseLong( properties.getProperty( @@ -2560,6 +2571,23 @@ public class IoTDBDescriptor { ConfigurationFileUtils.getConfigurationDefaultValue( "load_write_throughput_bytes_per_second")))); + conf.setLoadTsFileAllowedDirs( + Arrays.stream( + properties + .getProperty( + "load_tsfile_allowed_dirs", + ConfigurationFileUtils.getConfigurationDefaultValue( + "load_tsfile_allowed_dirs")) + .trim() + .split(",")) + .filter(dir -> !dir.isEmpty()) + .toArray(String[]::new)); + conf.setLoadTsFileSourcePathCheckEnabled( + Boolean.parseBoolean( + properties.getProperty( + "load_tsfile_source_path_check_enable", + Boolean.toString(conf.isLoadTsFileSourcePathCheckEnabled())))); + conf.setLoadActiveListeningEnable( Boolean.parseBoolean( properties.getProperty( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/legacy/loader/TsFileLoader.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/legacy/loader/TsFileLoader.java index f88510a0ca8..8459e22f1ee 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/legacy/loader/TsFileLoader.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/legacy/loader/TsFileLoader.java @@ -54,7 +54,7 @@ public class TsFileLoader implements ILoader { @Override public void load() { try { - LoadTsFileStatement statement = new LoadTsFileStatement(tsFile.getAbsolutePath()); + LoadTsFileStatement statement = LoadTsFileStatement.createUnchecked(tsFile.getAbsolutePath()); statement.setDeleteAfterLoad(true); statement.setConvertOnTypeMismatch(true); statement.setDatabaseLevel(parseSgLevel()); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java index 334c3530414..58d4c29eddc 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java @@ -490,8 +490,7 @@ public class IoTDBDataNodeReceiver extends IoTDBFileReceiver { } private TSStatus loadTsFileSync(final String fileAbsolutePath) throws FileNotFoundException { - final LoadTsFileStatement statement = new LoadTsFileStatement(fileAbsolutePath); - + final LoadTsFileStatement statement = LoadTsFileStatement.createUnchecked(fileAbsolutePath); statement.setDeleteAfterLoad(true); statement.setConvertOnTypeMismatch(true); statement.setVerifySchema(validateTsFile.get()); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java index 5a346fdf8e0..b6d4fb0043b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java @@ -372,7 +372,7 @@ public class LoadTsFileAnalyzer implements AutoCloseable { final TSStatus status = loadTsFileDataTypeConverter .convertForTreeModel( - new LoadTsFileStatement(tsFiles.get(i).getPath()) + LoadTsFileStatement.createUnchecked(tsFiles.get(i).getPath()) .setDeleteAfterLoad(isDeleteAfterLoad) .setConvertOnTypeMismatch(isConvertOnTypeMismatch)) .orElse(null); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/scheduler/load/LoadTsFileScheduler.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/scheduler/load/LoadTsFileScheduler.java index 4a85b6d0ebc..5cafe3b8384 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/scheduler/load/LoadTsFileScheduler.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/scheduler/load/LoadTsFileScheduler.java @@ -551,7 +551,7 @@ public class LoadTsFileScheduler implements IScheduler { final TSStatus status = loadTsFileDataTypeConverter .convertForTreeModel( - new LoadTsFileStatement(filePath) + LoadTsFileStatement.createUnchecked(filePath) .setDeleteAfterLoad(failedNode.isDeleteAfterLoad()) .setConvertOnTypeMismatch(true)) .orElse(null); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatement.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatement.java index 3b10d3733e1..6e74ceed206 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatement.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatement.java @@ -21,6 +21,7 @@ package org.apache.iotdb.db.queryengine.plan.statement.crud; import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.conf.IoTDBDescriptor; import org.apache.iotdb.db.queryengine.plan.statement.Statement; import org.apache.iotdb.db.queryengine.plan.statement.StatementType; @@ -34,7 +35,9 @@ import org.apache.tsfile.common.constant.TsFileConstant; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -56,6 +59,15 @@ public class LoadTsFileStatement extends Statement { private List<Long> writePointCountList; public LoadTsFileStatement(String filePath) throws FileNotFoundException { + this(filePath, true); + } + + public static LoadTsFileStatement createUnchecked(String filePath) throws FileNotFoundException { + return new LoadTsFileStatement(filePath, false); + } + + private LoadTsFileStatement(String filePath, boolean validateSourcePath) + throws FileNotFoundException { this.file = new File(filePath).getAbsoluteFile(); this.databaseLevel = IoTDBDescriptor.getInstance().getConfig().getDefaultDatabaseLevel(); this.verifySchema = true; @@ -65,13 +77,22 @@ public class LoadTsFileStatement extends Statement { IoTDBDescriptor.getInstance().getConfig().getLoadTabletConversionThresholdBytes(); this.autoCreateDatabase = IoTDBDescriptor.getInstance().getConfig().isAutoCreateSchemaEnabled(); - this.tsFiles = processTsFile(file); + this.tsFiles = processTsFile(file, validateSourcePath); this.resources = new ArrayList<>(); this.writePointCountList = new ArrayList<>(); this.statementType = StatementType.MULTI_BATCH_INSERT; } public static List<File> processTsFile(final File file) throws FileNotFoundException { + return processTsFile(file, true); + } + + public static List<File> processTsFile(final File file, final boolean validateSourcePath) + throws FileNotFoundException { + if (validateSourcePath) { + validateLoadSourcePath(file); + } + final List<File> tsFiles = new ArrayList<>(); if (file.isFile()) { tsFiles.add(file); @@ -82,7 +103,7 @@ public class LoadTsFileStatement extends Statement { "Can not find %s on this machine, notice that load can only handle files on this machine.", file.getPath())); } - tsFiles.addAll(findAllTsFile(file)); + tsFiles.addAll(findAllTsFile(file, validateSourcePath)); } sortTsFiles(tsFiles); return tsFiles; @@ -101,7 +122,8 @@ public class LoadTsFileStatement extends Statement { this.statementType = StatementType.MULTI_BATCH_INSERT; } - private static List<File> findAllTsFile(File file) { + private static List<File> findAllTsFile(File file, boolean validateSourcePath) + throws FileNotFoundException { final File[] files = file.listFiles(); if (files == null) { return Collections.emptyList(); @@ -109,15 +131,55 @@ public class LoadTsFileStatement extends Statement { final List<File> tsFiles = new ArrayList<>(); for (File nowFile : files) { + if (validateSourcePath) { + validateLoadSourcePath(nowFile); + } if (nowFile.getName().endsWith(TsFileConstant.TSFILE_SUFFIX)) { tsFiles.add(nowFile); } else if (nowFile.isDirectory()) { - tsFiles.addAll(findAllTsFile(nowFile)); + tsFiles.addAll(findAllTsFile(nowFile, validateSourcePath)); } } return tsFiles; } + public static void validateLoadSourcePath(final String filePath) throws FileNotFoundException { + validateLoadSourcePath(new File(filePath)); + } + + private static void validateLoadSourcePath(final File file) throws FileNotFoundException { + final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig(); + if (!config.isLoadTsFileSourcePathCheckEnabled()) { + return; + } + + final Path sourcePath = canonicalPath(file); + final String[] allowedDirs = config.getLoadTsFileAllowedDirs(); + final Path[] allowedDirCanonicalPaths = config.getLoadTsFileAllowedDirCanonicalPaths(); + + for (final Path allowedDirCanonicalPath : allowedDirCanonicalPaths) { + if (sourcePath.startsWith(allowedDirCanonicalPath)) { + return; + } + } + + throw new FileNotFoundException( + String.format( + "Load TsFile source path %s is outside allowed directories %s.", + sourcePath, Arrays.toString(allowedDirs))); + } + + private static Path canonicalPath(final File file) throws FileNotFoundException { + try { + return file.getCanonicalFile().toPath(); + } catch (final IOException e) { + throw new FileNotFoundException( + String.format( + "Failed to resolve canonical path for Load TsFile source %s: %s", + file.getPath(), e.getMessage())); + } + } + private static void sortTsFiles(List<File> files) { files.sort( (o1, o2) -> { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadTsFileLoader.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadTsFileLoader.java index 8a1f305e4f2..a228ef6c651 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadTsFileLoader.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/active/ActiveLoadTsFileLoader.java @@ -218,7 +218,8 @@ public class ActiveLoadTsFileLoader { final ActiveLoadPendingQueue.ActiveLoadEntry entry, final IClientSession session) throws FileNotFoundException { final File tsFile = new File(entry.getFile()); - final LoadTsFileStatement statement = new LoadTsFileStatement(entry.getFile()); + final LoadTsFileStatement statement = + LoadTsFileStatement.createUnchecked(tsFile.getAbsolutePath()); statement.setDeleteAfterLoad(true); statement.setAutoCreateDatabase( diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatementTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatementTest.java new file mode 100644 index 00000000000..bfebf51d281 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatementTest.java @@ -0,0 +1,139 @@ +/* + * 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.iotdb.db.queryengine.plan.statement.crud; + +import org.apache.iotdb.db.conf.IoTDBConfig; +import org.apache.iotdb.db.conf.IoTDBDescriptor; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +public class LoadTsFileStatementTest { + + @Test + public void testSubStatementsKeepDatabase() throws Exception { + final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig(); + final int originalBatchSize = config.getLoadTsFileSubStatementBatchSize(); + final String[] originalAllowedDirs = config.getLoadTsFileAllowedDirs().clone(); + final Path tempDir = Files.createTempDirectory("load-tsfile-sub-statements"); + + try { + config.setLoadTsFileSubStatementBatchSize(1); + config.setLoadTsFileAllowedDirs(new String[] {tempDir.toString()}); + Files.createFile(tempDir.resolve("a.tsfile")); + Files.createFile(tempDir.resolve("b.tsfile")); + + final LoadTsFileStatement statement = new LoadTsFileStatement(tempDir.toString()); + statement.setDatabase("test_db"); + + final List<LoadTsFileStatement> subStatements = statement.getSubStatements(); + Assert.assertEquals(2, subStatements.size()); + subStatements.forEach( + subStatement -> Assert.assertEquals("test_db", subStatement.getDatabase())); + } finally { + config.setLoadTsFileSubStatementBatchSize(originalBatchSize); + config.setLoadTsFileAllowedDirs(originalAllowedDirs); + deleteRecursively(tempDir); + } + } + + @Test + public void testLoadSourcePathMustBeInAllowedDirs() throws Exception { + final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig(); + final String[] originalAllowedDirs = config.getLoadTsFileAllowedDirs().clone(); + final boolean originalCheckEnabled = config.isLoadTsFileSourcePathCheckEnabled(); + final Path allowedDir = Files.createTempDirectory("load-tsfile-allowed"); + final Path deniedDir = Files.createTempDirectory("load-tsfile-denied"); + + try { + config.setLoadTsFileSourcePathCheckEnabled(true); + config.setLoadTsFileAllowedDirs(new String[] {allowedDir.toString()}); + final Path deniedTsFile = Files.createFile(deniedDir.resolve("denied.tsfile")); + final Path traversalTsFile = + allowedDir.resolve("..").resolve(deniedDir.getFileName()).resolve("denied.tsfile"); + + assertLoadSourcePathRejected(deniedTsFile); + assertLoadSourcePathRejected(traversalTsFile); + } finally { + config.setLoadTsFileAllowedDirs(originalAllowedDirs); + config.setLoadTsFileSourcePathCheckEnabled(originalCheckEnabled); + deleteRecursively(allowedDir); + deleteRecursively(deniedDir); + } + } + + @Test + public void testLoadSourcePathCheckCanBeDisabled() throws Exception { + final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig(); + final String[] originalAllowedDirs = config.getLoadTsFileAllowedDirs().clone(); + final boolean originalCheckEnabled = config.isLoadTsFileSourcePathCheckEnabled(); + final Path allowedDir = Files.createTempDirectory("load-tsfile-allowed"); + final Path deniedDir = Files.createTempDirectory("load-tsfile-denied"); + + try { + config.setLoadTsFileSourcePathCheckEnabled(false); + config.setLoadTsFileAllowedDirs(new String[] {allowedDir.toString()}); + final Path deniedTsFile = Files.createFile(deniedDir.resolve("denied.tsfile")); + + new LoadTsFileStatement(deniedTsFile.toString()); + } finally { + config.setLoadTsFileAllowedDirs(originalAllowedDirs); + config.setLoadTsFileSourcePathCheckEnabled(originalCheckEnabled); + deleteRecursively(allowedDir); + deleteRecursively(deniedDir); + } + } + + private static void assertLoadSourcePathRejected(final Path sourcePath) { + try { + new LoadTsFileStatement(sourcePath.toString()); + Assert.fail("Expected disallowed LOAD TSFILE source path to be rejected."); + } catch (final FileNotFoundException e) { + Assert.assertTrue(e.getMessage().contains("outside allowed directories")); + } + } + + private static void deleteRecursively(final Path path) throws IOException { + if (path == null || !Files.exists(path)) { + return; + } + + try (final Stream<Path> pathStream = Files.walk(path)) { + pathStream + .sorted(Comparator.reverseOrder()) + .forEach( + currentPath -> { + try { + Files.deleteIfExists(currentPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } +} diff --git a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template index 5447bcf5fd4..741a4c846cd 100644 --- a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template +++ b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template @@ -2033,6 +2033,17 @@ load_clean_up_task_execution_delay_time_seconds=1800 # Datatype: int load_write_throughput_bytes_per_second=-1 +# Whether the load_tsfile supports path allowed dirs check. +# effectiveMode: hot_reload +# Datatype: String +load_tsfile_source_path_check_enable=false + +# Comma-separated list of directories from which user-issued LOAD TSFILE statements can read. +# If empty, IoTDB only permits LOAD sources under the internal load TsFile directories. +# effectiveMode: hot_reload +# Datatype: String +load_tsfile_allowed_dirs= + # Whether to enable the active listening mode for tsfile loading. # effectiveMode: hot_reload # Datatype: Boolean
