This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/dev/1.3 by this push:
new 5ec7ee21e3b [To dev/1.3] Load: Harden LOAD TSFILE source path
validation (#17624) (#17654)
5ec7ee21e3b is described below
commit 5ec7ee21e3be47c3cd8bb6a14572554e7d11476e
Author: Caideyipi <[email protected]>
AuthorDate: Wed May 13 14:09:51 2026 +0800
[To dev/1.3] Load: Harden LOAD TSFILE source path validation (#17624)
(#17654)
* Load: Harden LOAD TSFILE source path validation (#17624)
* Load pri
* sp
* MAINTAIN
* rollback
* Add
* change
* canonical
* line
* Pre
* Update LoadTsFileStatementTest.java
* Update LoadTsFileStatementTest.java
* Update IoTDBDescriptor.java
---
.../java/org/apache/iotdb/db/conf/IoTDBConfig.java | 83 +++++++++++++++
.../org/apache/iotdb/db/conf/IoTDBDescriptor.java | 30 ++++++
.../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 | 111 +++++++++++++++++++++
.../conf/iotdb-system.properties.template | 11 ++
10 files changed, 307 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..b380a31eb64 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,25 @@ public class IoTDBDescriptor {
ConfigurationFileUtils.getConfigurationDefaultValue(
"load_write_throughput_bytes_per_second"))));
+ conf.setLoadTsFileAllowedDirs(
+ Arrays.stream(
+ properties
+ .getProperty(
+ "load_tsfile_allowed_dirs",
+ Optional.ofNullable(
+
ConfigurationFileUtils.getConfigurationDefaultValue(
+ "load_tsfile_allowed_dirs"))
+ .orElse(""))
+ .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..acf608abf80
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/statement/crud/LoadTsFileStatementTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.stream.Stream;
+
+public class LoadTsFileStatementTest {
+
+ @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