This is an automated email from the ASF dual-hosted git repository. jackietien pushed a commit to branch force_ci/object_type in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 65c96dfa445d65e81d9bd2c0883689f4f75d03ae Author: Caideyipi <[email protected]> AuthorDate: Mon Dec 8 11:43:33 2025 +0800 Added name check to avoid directory attack (#16720) * partial * fix * part * createOrUpdate * partial * fix * partial * part * may_main_final * bug-fix * fix * Update StatementAnalyzer.java * fix * removal * object * fix-add-test * bug-fix * spotless * change-back * fix * change-name * fix * fix * partial_apply * fix * fix * fix * fix * fix * fix * remove-check * check_write * defense * unwebbed * clean * c * remove-useless-check * may-comp * qp --- .../iotdb/relational/it/schema/IoTDBTableIT.java | 133 +++++++++++++++++++++ .../manager/schema/ClusterSchemaManager.java | 13 +- .../execution/config/TableConfigTaskVisitor.java | 10 ++ .../fetcher/TableDeviceSchemaValidator.java | 23 ++-- .../plan/relational/sql/ast/InsertRow.java | 11 +- .../plan/relational/sql/ast/InsertRows.java | 14 ++- .../plan/relational/sql/ast/InsertTablet.java | 9 ++ .../schemaregion/impl/SchemaRegionPBTreeImpl.java | 2 +- .../apache/iotdb/commons/schema/table/TsTable.java | 31 +++++ 9 files changed, 233 insertions(+), 13 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java index 753ff5fa42b..1e95b05b05f 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java @@ -41,6 +41,9 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; @@ -777,6 +780,136 @@ public class IoTDBTableIT { } } + @Test + public void testTableObjectCheck() throws Exception { + final Set<String> illegal = new HashSet<>(Arrays.asList("./", ".", "..", ".\\", "../hack")); + for (final String single : illegal) { + testObject4SingleIllegalPath(single); + } + } + + private void testObject4SingleIllegalPath(final String illegal) throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement(); + final ITableSession session = EnvFactory.getEnv().getTableSessionConnection()) { + statement.execute("create database if not exists db2"); + statement.execute("use db2"); + statement.execute(String.format("create table \"%s\" ()", illegal)); + + try { + statement.execute(String.format("alter table \"%s\" add column a object", illegal)); + fail(); + } catch (final SQLException e) { + Assert.assertEquals( + String.format( + "701: When there are object fields, the tableName %s shall not be '.', '..' or contain './', '.\\'", + illegal), + e.getMessage()); + } + + // Test auto-create + String testObject = + System.getProperty("user.dir") + + File.separator + + "target" + + File.separator + + "test-classes" + + File.separator + + "ainode-example" + + File.separator + + "model.pt"; + + List<IMeasurementSchema> schemaList = new ArrayList<>(); + schemaList.add(new MeasurementSchema("a", TSDataType.STRING)); + schemaList.add(new MeasurementSchema("b", TSDataType.STRING)); + schemaList.add(new MeasurementSchema("c", TSDataType.INT32)); + schemaList.add(new MeasurementSchema(illegal, TSDataType.OBJECT)); + final List<ColumnCategory> columnTypes = + Arrays.asList( + ColumnCategory.TAG, + ColumnCategory.ATTRIBUTE, + ColumnCategory.FIELD, + ColumnCategory.FIELD); + final Tablet tablet = + new Tablet( + illegal, + IMeasurementSchema.getMeasurementNameList(schemaList), + IMeasurementSchema.getDataTypeList(schemaList), + columnTypes, + 1); + tablet.addTimestamp(0, System.currentTimeMillis()); + tablet.addValue(schemaList.get(0).getMeasurementName(), 0, "d1"); + tablet.addValue(schemaList.get(1).getMeasurementName(), 0, "a1"); + tablet.addValue(schemaList.get(2).getMeasurementName(), 0, 0); + tablet.addValue(0, 3, true, 0, Files.readAllBytes(Paths.get(testObject))); + + try { + session.executeNonQueryStatement("use db2"); + session.insert(tablet); + } catch (final Exception e) { + Assert.assertEquals( + String.format( + "701: When there are object fields, the tableName %s shall not be '.', '..' or contain './', '.\\'", + illegal), + e.getMessage()); + } + + try { + statement.execute(String.format("create table test (\"%s\" object)", illegal)); + fail(); + } catch (final SQLException e) { + Assert.assertEquals( + String.format( + "701: When there are object fields, the objectName %s shall not be '.', '..' or contain './', '.\\'", + illegal), + e.getMessage()); + } + + statement.execute("create table test (a tag, b attribute, c int32, d object)"); + + // Cannot auto-extend illegal column + tablet.setTableName("test"); + try { + session.executeNonQueryStatement("use db2"); + session.insert(tablet); + } catch (final Exception e) { + Assert.assertEquals( + String.format( + "701: When there are object fields, the objectName %s shall not be '.', '..' or contain './', '.\\'", + illegal), + e.getMessage()); + } + + // It's OK if you don't write object + statement.execute(String.format("insert into test (a, b, c) values ('%s', 1, 1)", illegal)); + try { + statement.execute( + String.format("insert into test (a, b, c, d) values ('%s', 1, 1, 's')", illegal)); + fail(); + } catch (final SQLException e) { + Assert.assertEquals( + String.format( + "507: When there are object fields, the deviceId [test, %s] shall not be '.', '..' or contain './', '.\\'", + illegal), + e.getMessage()); + } + + try { + statement.execute(String.format("alter table test add column \"%s\" object", illegal)); + fail(); + } catch (final SQLException e) { + Assert.assertEquals( + String.format( + "701: When there are object fields, the objectName %s shall not be '.', '..' or contain './', '.\\'", + illegal), + e.getMessage()); + } + + statement.execute("drop database db2"); + } + } + @Test public void testTreeViewTable() throws Exception { try (final Connection connection = EnvFactory.getEnv().getConnection(); diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java index 9a04d9e9e59..a192511ffff 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java @@ -111,6 +111,7 @@ import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.rpc.TSStatusCode; import org.apache.tsfile.annotations.TableModel; +import org.apache.tsfile.enums.TSDataType; import org.apache.tsfile.file.metadata.IDeviceID; import org.apache.tsfile.utils.Pair; import org.slf4j.Logger; @@ -124,6 +125,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -1355,9 +1357,14 @@ public class ClusterSchemaManager { columnSchemaList.stream() .map(TsTableColumnSchema::getColumnName) .collect(Collectors.joining(", "))); + + final AtomicBoolean hasObject = new AtomicBoolean(false); columnSchemaList.removeIf( columnSchema -> { if (Objects.isNull(originalTable.getColumnSchema(columnSchema.getColumnName()))) { + if (columnSchema.getDataType().equals(TSDataType.OBJECT)) { + hasObject.set(true); + } expandedTable.addColumnSchema(columnSchema); return false; } @@ -1367,7 +1374,11 @@ public class ClusterSchemaManager { if (columnSchemaList.isEmpty()) { return new Pair<>(RpcUtils.getStatus(TSStatusCode.COLUMN_ALREADY_EXISTS, errorMsg), null); } - return new Pair<>(RpcUtils.SUCCESS_STATUS, expandedTable); + + if (hasObject.get()) { + expandedTable.checkTableNameAndObjectNames4Object(); + } + return new Pair<>(StatusUtils.OK, expandedTable); } public synchronized Pair<TSStatus, TsTable> tableColumnCheckForColumnRenaming( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java index bc52a3a7e8c..d659f30fc8f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java @@ -27,6 +27,7 @@ import org.apache.iotdb.commons.audit.UserEntity; import org.apache.iotdb.commons.auth.entity.PrivilegeType; import org.apache.iotdb.commons.auth.entity.User; import org.apache.iotdb.commons.exception.IllegalPathException; +import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.exception.auth.AccessDeniedException; import org.apache.iotdb.commons.executable.ExecutableManager; import org.apache.iotdb.commons.path.PartialPath; @@ -571,10 +572,12 @@ public class TableConfigTaskVisitor extends AstVisitor<IConfigTask, MPPQueryCont // TODO: Place the check at statement analyzer boolean hasTimeColumn = false; final Set<String> sourceNameSet = new HashSet<>(); + boolean hasObject = false; for (final ColumnDefinition columnDefinition : node.getElements()) { final TsTableColumnCategory category = columnDefinition.getColumnCategory(); final String columnName = columnDefinition.getName().getValue(); final TSDataType dataType = getDataType(columnDefinition.getType()); + hasObject |= dataType.equals(TSDataType.OBJECT); final String comment = columnDefinition.getComment(); if (checkTimeColumnIdempotent(category, columnName, dataType, comment, table) && !hasTimeColumn) { @@ -603,6 +606,13 @@ public class TableConfigTaskVisitor extends AstVisitor<IConfigTask, MPPQueryCont } table.addColumnSchema(schema); } + if (hasObject) { + try { + table.checkTableNameAndObjectNames4Object(); + } catch (final MetadataException e) { + throw new SemanticException(e.getMessage(), e.getErrorCode()); + } + } return new Pair<>(database, table); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableDeviceSchemaValidator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableDeviceSchemaValidator.java index b50a875d7b0..3be5d931657 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableDeviceSchemaValidator.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableDeviceSchemaValidator.java @@ -19,9 +19,9 @@ package org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher; -import org.apache.iotdb.commons.exception.IoTDBException; -import org.apache.iotdb.db.conf.IoTDBConfig; -import org.apache.iotdb.db.conf.IoTDBDescriptor; +import org.apache.iotdb.commons.exception.IoTDBRuntimeException; +import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.db.exception.sql.SemanticException; import org.apache.iotdb.db.protocol.session.SessionManager; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; import org.apache.iotdb.db.queryengine.plan.Coordinator; @@ -53,8 +53,6 @@ public class TableDeviceSchemaValidator { private static final Logger LOGGER = LoggerFactory.getLogger(TableDeviceSchemaValidator.class); - private final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig(); - private final Coordinator coordinator = Coordinator.getInstance(); private final TableDeviceSchemaFetcher fetcher = TableDeviceSchemaFetcher.getInstance(); @@ -247,9 +245,18 @@ public class TableDeviceSchemaValidator { Long.MAX_VALUE, false); if (executionResult.status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - throw new RuntimeException( - new IoTDBException( - executionResult.status.getMessage(), executionResult.status.getCode())); + throw new IoTDBRuntimeException( + executionResult.status.getMessage(), executionResult.status.getCode()); + } + } + + public static void checkObject4DeviceId(final Object[] deviceId) { + for (final Object part : deviceId) { + final String value = (String) part; + if (Objects.nonNull(value) && TsTable.isInvalid4ObjectType(value)) { + throw new SemanticException( + TsTable.getObjectStringError("deviceId", Arrays.toString(deviceId))); + } } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRow.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRow.java index cbce78e6528..70b2e062365 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRow.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRow.java @@ -21,11 +21,15 @@ package org.apache.iotdb.db.queryengine.plan.relational.sql.ast; import org.apache.iotdb.db.exception.query.QueryProcessException; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableDeviceSchemaValidator; import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement; +import org.apache.tsfile.enums.TSDataType; + import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; public class InsertRow extends WrappedInsertStatement { @@ -56,7 +60,12 @@ public class InsertRow extends WrappedInsertStatement { @Override public List<Object[]> getDeviceIdList() { final InsertRowStatement insertRowStatement = getInnerTreeStatement(); - Object[] segments = insertRowStatement.getTableDeviceID().getSegments(); + final Object[] segments = insertRowStatement.getTableDeviceID().getSegments(); + if (Objects.nonNull(getInnerTreeStatement().getMeasurementSchemas()) + && Arrays.stream(getInnerTreeStatement().getMeasurementSchemas()) + .anyMatch(schema -> Objects.nonNull(schema) && schema.getType() == TSDataType.OBJECT)) { + TableDeviceSchemaValidator.checkObject4DeviceId(segments); + } return Collections.singletonList(Arrays.copyOfRange(segments, 1, segments.length)); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRows.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRows.java index d2a0da3c1dd..d35e68efcf5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRows.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertRows.java @@ -25,13 +25,17 @@ import org.apache.iotdb.db.queryengine.common.MPPQueryContext; import org.apache.iotdb.db.queryengine.plan.analyze.AnalyzeUtils; import org.apache.iotdb.db.queryengine.plan.relational.metadata.ITableDeviceSchemaValidation; import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableDeviceSchemaValidator; import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement; import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowsStatement; +import org.apache.tsfile.enums.TSDataType; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; public class InsertRows extends WrappedInsertStatement { @@ -117,8 +121,14 @@ public class InsertRows extends WrappedInsertStatement { @Override public List<Object[]> getDeviceIdList() { - Object[] idSegments = insertRowStatement.getTableDeviceID().getSegments(); - return Collections.singletonList(Arrays.copyOfRange(idSegments, 1, idSegments.length)); + final Object[] tagSegments = insertRowStatement.getTableDeviceID().getSegments(); + if (Objects.nonNull(insertRowStatement.getMeasurementSchemas()) + && Arrays.stream(insertRowStatement.getMeasurementSchemas()) + .anyMatch( + schema -> Objects.nonNull(schema) && schema.getType() == TSDataType.OBJECT)) { + TableDeviceSchemaValidator.checkObject4DeviceId(tagSegments); + } + return Collections.singletonList(Arrays.copyOfRange(tagSegments, 1, tagSegments.length)); } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java index 9894adb4d01..8c1bccf28ab 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java @@ -21,8 +21,10 @@ package org.apache.iotdb.db.queryengine.plan.relational.sql.ast; import org.apache.iotdb.db.exception.query.QueryProcessException; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableDeviceSchemaValidator; import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement; +import org.apache.tsfile.enums.TSDataType; import org.apache.tsfile.file.metadata.IDeviceID; import java.util.ArrayList; @@ -30,6 +32,7 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; public class InsertTablet extends WrappedInsertStatement { @@ -65,6 +68,12 @@ public class InsertTablet extends WrappedInsertStatement { List<Object[]> deviceIdList = new ArrayList<>(); for (IDeviceID deviceID : deviceID2LastIdxMap.keySet()) { Object[] segments = deviceID.getSegments(); + if (Objects.nonNull(super.getInnerTreeStatement().getMeasurementSchemas()) + && Arrays.stream(super.getInnerTreeStatement().getMeasurementSchemas()) + .anyMatch( + schema -> Objects.nonNull(schema) && schema.getType() == TSDataType.OBJECT)) { + TableDeviceSchemaValidator.checkObject4DeviceId(segments); + } deviceIdList.add(Arrays.copyOfRange(segments, 1, segments.length)); } return deviceIdList; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java index a39b881fe1f..5a105cf7dd7 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java @@ -1548,7 +1548,7 @@ public class SchemaRegionPBTreeImpl implements ISchemaRegion { @Override public ISchemaReader<IDeviceSchemaInfo> getTableDeviceReader( - String table, List<Object[]> devicePathList) throws MetadataException { + String table, List<Object[]> devicePathList) { throw new UnsupportedOperationException(); } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java index 6c3d85c12aa..8336f0f4aa0 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java @@ -20,6 +20,7 @@ package org.apache.iotdb.commons.schema.table; import org.apache.iotdb.commons.conf.CommonDescriptor; +import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.exception.runtime.SchemaExecutionException; import org.apache.iotdb.commons.schema.table.column.AttributeColumnSchema; import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema; @@ -29,6 +30,7 @@ import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory; import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchemaUtil; import org.apache.iotdb.commons.utils.CommonDateTimeUtils; +import org.apache.iotdb.rpc.TSStatusCode; import com.google.common.collect.ImmutableList; import org.apache.tsfile.enums.TSDataType; @@ -69,6 +71,8 @@ public class TsTable { public static final String TTL_PROPERTY = "ttl"; public static final Set<String> TABLE_ALLOWED_PROPERTIES = Collections.singleton(TTL_PROPERTY); + private static final String OBJECT_STRING_ERROR = + "When there are object fields, the %s %s shall not be '.', '..' or contain './', '.\\'"; protected String tableName; private final Map<String, TsTableColumnSchema> columnSchemaMap = new LinkedHashMap<>(); @@ -410,6 +414,33 @@ public class TsTable { executeWrite(() -> this.props = props); } + public void checkTableNameAndObjectNames4Object() throws MetadataException { + if (isInvalid4ObjectType(tableName)) { + throw new MetadataException( + getObjectStringError("tableName", tableName), + TSStatusCode.SEMANTIC_ERROR.getStatusCode()); + } + for (final TsTableColumnSchema schema : columnSchemaMap.values()) { + if (schema.getDataType().equals(TSDataType.OBJECT) + && isInvalid4ObjectType(schema.getColumnName())) { + throw new MetadataException( + getObjectStringError("objectName", schema.getColumnName()), + TSStatusCode.SEMANTIC_ERROR.getStatusCode()); + } + } + } + + public static boolean isInvalid4ObjectType(final String column) { + return column.equals(".") + || column.equals("..") + || column.contains("./") + || column.contains(".\\"); + } + + public static String getObjectStringError(final String columnType, final String columnName) { + return String.format(OBJECT_STRING_ERROR, columnType, columnName); + } + @Override public boolean equals(Object o) { return super.equals(o);
