mirror of
https://github.com/taosdata/TDengine
synced 2026-05-24 10:09:01 +00:00
fix(scalar): prevent crash when IN expression has invalid type from unhandled node
Root cause: sclGetNodeType() silently set *type = -1 and returned TSDB_CODE_SUCCESS for unhandled node types. This propagated -1 as ctx->type.selfType, which was then cast to (uint32_t)-1 = 4294967295 and used as an index into tDataTypes[], causing an out-of-bounds crash in scalarGenerateSetFromList(). The bug was triggered by semantically odd SQL such as: ifnull(b not between vb and a, n in (...)) in (today(), today(), now()) where a boolean result is compared against a timestamp list. Fixes: 1. sclGetNodeType(): return TSDB_CODE_QRY_INVALID_INPUT for unhandled node types instead of silently setting type = -1. 2. sclInitParam(): validate selfType before using it as list element type to prevent OOB access in vectorGetConvertType(-1, ...). 3. scalarGenerateSetFromList(): add IS_INVALID_TYPE() guard at entry as a defensive check against any future invalid-type paths. Add regression test: test_scalar_invalid_type.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
7a26619006
commit
ed9de01c0c
3 changed files with 105 additions and 3 deletions
|
|
@ -235,6 +235,10 @@ _return:
|
|||
|
||||
// processType = 0 means all type. 1 means number, 2 means var, 3 means float, 4 means var&integer
|
||||
int32_t scalarGenerateSetFromList(void **data, void *pNode, uint32_t type, STypeMod typeMod, int8_t processType) {
|
||||
if (IS_INVALID_TYPE(type)) {
|
||||
sclError("invalid type:%u in set generation", type);
|
||||
SCL_ERR_RET(TSDB_CODE_QRY_INVALID_INPUT);
|
||||
}
|
||||
SHashObj *pObj = taosHashInit(256, taosGetDefaultHashFunction(type), true, false);
|
||||
if (NULL == pObj) {
|
||||
sclError("taosHashInit failed, size:%d", 256);
|
||||
|
|
@ -556,6 +560,10 @@ int32_t sclInitParam(SNode *node, SScalarParam *param, SScalarCtx *ctx, int32_t
|
|||
param->hashParam.hasValue = true;
|
||||
|
||||
int32_t type = ctx->type.selfType;
|
||||
if (IS_INVALID_TYPE(type)) {
|
||||
sclError("invalid selfType:%d for nodeList IN expression", type);
|
||||
SCL_ERR_RET(TSDB_CODE_QRY_INVALID_INPUT);
|
||||
}
|
||||
STypeMod typeMod = 0;
|
||||
SNode *nodeItem = NULL;
|
||||
FOREACH(nodeItem, nodeList->pNodeList) {
|
||||
|
|
@ -1111,9 +1119,9 @@ int32_t sclGetNodeType(SNode *pNode, SScalarCtx *ctx, int32_t *type, STypeMod *p
|
|||
}
|
||||
}
|
||||
|
||||
*type = -1;
|
||||
*pTypeMod = 0;
|
||||
return TSDB_CODE_SUCCESS;
|
||||
sclError("unsupported node type for type resolution, nodeType:%d, node:%p", nodeType(pNode), pNode);
|
||||
terrno = TSDB_CODE_QRY_INVALID_INPUT;
|
||||
return TSDB_CODE_QRY_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int32_t sclSetOperatorValueType(SOperatorNode *node, SScalarCtx *ctx) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
from new_test_framework.utils import tdLog, tdSql
|
||||
|
||||
|
||||
class TestScalarInvalidType:
|
||||
"""Regression test for scalar engine crash when IN expression has
|
||||
invalid type propagated from unhandled node type in sclGetNodeType.
|
||||
|
||||
Related: defect #6925396284
|
||||
Crash SQL: ifnull(b not between vb and a, n in ('s_199', ...)) in (today(), ...)
|
||||
Root cause: sclGetNodeType fallthrough set type=-1 silently, then
|
||||
scalarGenerateSetFromList used (uint32_t)-1 = 4294967295 as
|
||||
index into tDataTypes[] → OOB crash.
|
||||
"""
|
||||
|
||||
def setup_class(cls):
|
||||
tdLog.debug(f"start to execute {__file__}")
|
||||
|
||||
def test_scalar_invalid_type_no_crash(self):
|
||||
"""Verify that semantically invalid IN expressions do not crash taosd.
|
||||
|
||||
The expressions compare boolean/non-timestamp results against
|
||||
timestamp lists, which should return an error, not a crash.
|
||||
|
||||
Catalog:
|
||||
- Query:Filter
|
||||
|
||||
Since: v3.4.1.0
|
||||
|
||||
Labels: common,ci
|
||||
|
||||
Jira: TD-6925396284
|
||||
|
||||
History:
|
||||
- 2026-3-30 fix: prevent taosd crash on invalid type in scalar IN expression
|
||||
"""
|
||||
|
||||
tdSql.execute("drop database if exists tdsqlsmith_shared")
|
||||
tdSql.execute("create database tdsqlsmith_shared")
|
||||
tdSql.execute("use tdsqlsmith_shared")
|
||||
tdSql.execute(
|
||||
"create table t1(ts timestamp, id int, v int, c1 int, c2 int, "
|
||||
"u1 int unsigned, bi bigint, ubi bigint unsigned, f float, d double, "
|
||||
"si smallint, usi smallint unsigned, ti tinyint, uti tinyint unsigned, "
|
||||
"ok bool, a binary(32), b varchar(64), n nchar(32), "
|
||||
"vb varbinary(64), geo geometry(100), de decimal(18,6))"
|
||||
)
|
||||
tdSql.execute(
|
||||
"create table t2(ts timestamp, id int, v int, c1 int, c2 int, "
|
||||
"u1 int unsigned, bi bigint, ubi bigint unsigned, f float, d double, "
|
||||
"si smallint, usi smallint unsigned, ti tinyint, uti tinyint unsigned, "
|
||||
"ok bool, a binary(32), b varchar(64), n nchar(32), "
|
||||
"vb varbinary(64), geo geometry(100), de decimal(18,6))"
|
||||
)
|
||||
tdSql.execute(
|
||||
"create table t3(ts timestamp, id int, v int, c1 int, c2 int, "
|
||||
"u1 int unsigned, bi bigint, ubi bigint unsigned, f float, d double, "
|
||||
"si smallint, usi smallint unsigned, ti tinyint, uti tinyint unsigned, "
|
||||
"ok bool, a binary(32), b varchar(64), n nchar(32), "
|
||||
"vb varbinary(64), geo geometry(100), de decimal(18,6))"
|
||||
)
|
||||
tdSql.execute(
|
||||
"insert into t1 values(now,1,10,1,2,11,111111111,222222222,1.25,2.5,"
|
||||
"12,34,7,9,true,'alpha','beta','gamma','\\x010203','POINT(1 2)',123.456789)"
|
||||
)
|
||||
tdSql.execute(
|
||||
"insert into t2 values(now,2,20,3,4,21,211111111,322222222,3.5,4.75,"
|
||||
"-12,44,-7,19,false,'left','right','delta','\\x0A0B0C','POINT(2 3)',223.000001)"
|
||||
)
|
||||
tdSql.execute(
|
||||
"insert into t3 values(now,3,30,5,6,31,311111111,422222222,5.75,6.125,"
|
||||
"22,54,17,29,true,'foo','bar','omega','\\x112233','POINT(3 4)',323.5)"
|
||||
)
|
||||
|
||||
# Previously caused taosd crash (OOB access on tDataTypes[4294967295])
|
||||
# After fix: should return error or empty result, not crash
|
||||
crash_sqls = [
|
||||
"select 's_770' from t3 as y where ifnull(b not between vb and a, n in ('s_199', 's_445', 's_683')) in (today(), today(), now()) order by abs(uti) & c2 desc nulls last",
|
||||
"select a, sum(c1), sum(usi) from t2 where (c2 between (+v) and ceil(ubi)) and (nullif(bi, si) not in ('s_436', 's_131', 's_542')) group by a",
|
||||
"select vb, f, max(u1) from t2 as x where not (ifnull(floor(d) & c2, ok is null) not in (false, true, false)) group by vb, f limit 21",
|
||||
]
|
||||
|
||||
for sql in crash_sqls:
|
||||
tdLog.debug(f"testing: {sql}")
|
||||
try:
|
||||
# The query may succeed (return 0 rows) or fail with error
|
||||
# Either is acceptable — what's NOT acceptable is a crash
|
||||
tdSql.query(sql, queryTimes=1)
|
||||
tdLog.debug(f"query returned {tdSql.queryRows} rows (no crash)")
|
||||
except Exception as e:
|
||||
tdLog.debug(f"query returned error (expected): {e}")
|
||||
|
||||
tdSql.execute("drop database tdsqlsmith_shared")
|
||||
tdLog.success(f"{__file__} successfully executed")
|
||||
|
|
@ -301,6 +301,7 @@
|
|||
,,y,.,./ci/pytest.sh pytest cases/09-DataQuerying/02-Filter/test_filter_sma.py
|
||||
,,y,.,./ci/pytest.sh pytest cases/09-DataQuerying/02-Filter/test_filter_tag.py
|
||||
,,y,.,./ci/pytest.sh pytest cases/09-DataQuerying/02-Filter/test_filter_timestamp.py
|
||||
,,y,.,./ci/pytest.sh pytest cases/09-DataQuerying/02-Filter/test_scalar_invalid_type.py
|
||||
## 03-GroupBy
|
||||
,,y,.,./ci/pytest.sh pytest cases/09-DataQuerying/03-GroupBy/test_query_groupby_alwaysreturn.py
|
||||
,,y,.,./ci/pytest.sh pytest cases/09-DataQuerying/03-GroupBy/test_query_groupby_basic.py
|
||||
|
|
|
|||
Loading…
Reference in a new issue