[CP] [to #2024071600103723443] fix: add param type defence at execution stage & fix several related bugs

This commit is contained in:
haohao022 2024-09-02 21:23:25 +00:00 committed by ob-robot
parent 696c9127a0
commit 2cdeaddd8c
7 changed files with 201 additions and 96 deletions

View File

@ -2748,7 +2748,7 @@ int ObPL::insert_error_msg(int errcode)
return ret;
}
int ObPL::check_trigger_arg(const ParamStore &params, const ObPLFunction &func)
int ObPL::check_trigger_arg(ParamStore &params, const ObPLFunction &func)
{
int ret = OB_SUCCESS;
if (TriggerHandle::is_trigger_body_routine(func.get_package_id(), func.get_routine_id(), func.get_proc_type())) {
@ -2774,6 +2774,7 @@ int ObPL::check_trigger_arg(const ParamStore &params, const ObPLFunction &func)
ObPLRecord *record = reinterpret_cast<ObPLRecord *>(params.at(i).get_ext());
CK (OB_NOT_NULL(record));
CK (record->get_count() == (static_cast<const ObRecordType *>(udt))->get_member_count());
OX (params.at(i).set_udt_id(udt_id));
}
}
}
@ -3205,6 +3206,7 @@ int ObPLExecState::init_complex_obj(ObIAllocator &allocator,
if (OB_FAIL(ret)) {
} else if (real_pl_type->is_ref_cursor_type() || real_pl_type->is_sys_refcursor_type()) {
OX (obj.set_is_ref_cursor_type(true));
OX (obj.set_extend(0, PL_REF_CURSOR_TYPE));
} else if (real_pl_type->is_udt_type()) {
ObPLUDTNS ns(*schema_guard);
OZ (ns.init_complex_obj(allocator, *real_pl_type, obj, false, set_null));
@ -3226,6 +3228,92 @@ int ObPLExecState::init_complex_obj(ObIAllocator &allocator,
return ret;
}
// This function is designed to defend against the modification of stored procedures during execution, which could lead
// to unexpected parameters being passed to the stored procedure, resulting in unknown errors.
// This function can be removed after the feature of defending the compilation cache of the executing stored procedure
// from being eliminated on the PL cache side.
int ObPLExecState::defend_stored_routine_change(const ObObjParam &actual_param, const ObPLDataType &formal_param_type)
{
int ret = OB_SUCCESS;
if (actual_param.is_null() || actual_param.is_pl_mock_default_param()) {
// no actual param type info(eg: out params), skip check
} else if (!actual_param.is_ext()) {
if (!formal_param_type.is_obj_type()) {
ret = OB_INVALID_ARGUMENT;
LOG_WARN("incorrect argument type, expected complex, but get basic type",
K(ret), K(formal_param_type), K(actual_param));
}
} else if ((actual_param.is_ref_cursor_type() /* refcursor */
|| PL_REF_CURSOR_TYPE == actual_param.get_meta().get_extend_type() /* also refcursor */
|| PL_CURSOR_TYPE == actual_param.get_meta().get_extend_type() /* cursor */)
&& formal_param_type.is_cursor_type()) {
// skip check
} else { // user defined type
uint64_t formal_udt_id = OB_INVALID_ID;
uint64_t actual_udt_id = OB_INVALID_ID;
OV (formal_param_type.is_composite_type(), OB_INVALID_ARGUMENT, formal_param_type);
OX (formal_udt_id = formal_param_type.get_user_type_id());
OV (OB_INVALID_ID != formal_udt_id, OB_ERR_UNEXPECTED, formal_param_type);
OX (actual_udt_id = actual_param.get_udt_id());
if (OB_SUCC(ret) && OB_INVALID_ID == actual_udt_id) {
if (PL_RECORD_TYPE == actual_param.get_meta().get_extend_type()
|| PL_NESTED_TABLE_TYPE == actual_param.get_meta().get_extend_type()
|| PL_ASSOCIATIVE_ARRAY_TYPE == actual_param.get_meta().get_extend_type()
|| PL_VARRAY_TYPE == actual_param.get_meta().get_extend_type()) {
const pl::ObPLComposite *actual_composite = nullptr;
CK (OB_NOT_NULL(actual_composite = reinterpret_cast<const ObPLComposite *>(actual_param.get_ext())));
OX (actual_udt_id = actual_composite->get_id());
OV (OB_INVALID_ID != actual_udt_id || actual_composite->is_collection(), // anonymous array has invalid udt id
OB_ERR_UNEXPECTED, KPC(actual_composite), actual_param);
} else {
ret = OB_ERR_UNEXPECTED;
LOG_WARN("unexpected extend type without udt id", K(ret), K(actual_param));
}
}
if (OB_FAIL(ret)) {
} else if (OB_INVALID_ID == actual_udt_id || formal_udt_id == actual_udt_id) {
// skip anonymous array & same udt id
} else {
bool is_compatible = true;
OZ (ObPLResolver::check_composite_compatible(ctx_, actual_udt_id, formal_udt_id, is_compatible));
if (OB_INVALID_ARGUMENT == ret) {
LOG_INFO("the type of actual param is not found in the current procedure's lexical scope, "
"should search caller's scope",
K(ret), K(actual_udt_id), K(formal_udt_id));
ret = OB_SUCCESS;
const ObUserDefinedType *actual_type = nullptr;
const ObUserDefinedType *formal_type = nullptr;
ObPLContext *pl_ctx = nullptr;
ObPLExecState *caller = nullptr;
CK (OB_NOT_NULL(ctx_.exec_ctx_));
CK (OB_NOT_NULL(ctx_.exec_ctx_->get_my_session()));
CK (OB_NOT_NULL(pl_ctx = ctx_.exec_ctx_->get_my_session()->get_pl_context()));
CK (pl_ctx->get_exec_stack().count() >= 2);
CK (OB_NOT_NULL(caller = pl_ctx->get_exec_stack().at(pl_ctx->get_exec_stack().count() - 2)));
// fetch actual param type from caller
OZ (caller->get_exec_ctx().get_user_type(actual_udt_id, actual_type));
// fetch formal param type from callee
OZ (get_exec_ctx().get_user_type(formal_udt_id, formal_type));
OV (OB_NOT_NULL(actual_type) && OB_NOT_NULL(formal_type), OB_ERR_UNEXPECTED, actual_udt_id, formal_udt_id);
OZ (ObPLResolver::check_composite_compatible(actual_type, formal_type, is_compatible));
}
OV (is_compatible, OB_INVALID_ARGUMENT, formal_udt_id, actual_udt_id, formal_param_type, actual_param);
}
}
if (OB_FAIL(ret)) {
LOG_WARN("param type not match, procedure/function could have been replaced",
K(ret), K(actual_param), K(formal_param_type));
ret = OB_ERR_UNEXPECTED; // replace errno, indicating that it's an internal error
LOG_USER_ERROR(OB_ERR_UNEXPECTED, "param type not match, procedure/function could have been replaced");
}
return ret;
}
int ObPLExecState::check_anonymous_collection_compatible(ObPLComposite &composite, const ObPLDataType &dest_type, bool &need_cast)
{
int ret = OB_SUCCESS;
@ -3415,6 +3503,7 @@ int ObPLExecState::init_params(const ParamStore *params, bool is_anonymous)
}
} else if (func_.get_variables().at(i).is_ref_cursor_type()) {
OX (param.set_is_ref_cursor_type(true));
OX (param.set_extend(0, PL_REF_CURSOR_TYPE));
// CURSOR初始化为NULL
} else if (func_.get_variables().at(i).is_cursor_type()) {
// leave obj as null type, spi_init wil init it.
@ -3482,8 +3571,11 @@ do { \
* In Args of Anonymous, If need_to_check_type_ is true then check to convert.
* else It will directly used by static sql, do not need to check.
*/
if (func_.get_in_args().has_member(i)) {
const ObPLDataType &pl_type = func_.get_variables().at(i);
const ObPLDataType &pl_type = func_.get_variables().at(i); // formal param type
if (FAILEDx(defend_stored_routine_change(params->at(i), pl_type))) {
LOG_WARN("param type not match, procedure/function could have been replaced",
K(ret), K(i), K(params->at(i)), K(pl_type));
} else if (func_.get_in_args().has_member(i)) {
if (is_anonymous && !func_.get_params_info().at(i).flag_.need_to_check_type_) {
OX (get_params().at(i) = params->at(i));
} else if (params->at(i).is_pl_mock_default_param()) { // 使用参数默认值
@ -3604,7 +3696,9 @@ do { \
// same type, we already check this on resolve stage, here directly assign value to symbol.
if (get_params().at(i).is_ref_cursor_type()) {
get_params().at(i) = params->at(i);
get_params().at(i).set_is_ref_cursor_type(true);
get_params().at(i).set_is_ref_cursor_type(true); // last assignment statement could clear this flag
get_params().at(i).set_extend(
get_params().at(i).get_ext(), PL_REF_CURSOR_TYPE, get_params().at(i).get_val_len());
} else if (pl_type.is_collection_type() && OB_INVALID_ID == params->at(i).get_udt_id()) {
ObPLComposite *composite = NULL;
get_params().at(i) = params->at(i);
@ -3630,7 +3724,7 @@ do { \
} else {
CHECK_NOT_NULL_VIOLATED(i, params->at(i));
if (OB_FAIL(ret)) {
} else if (func_.get_variables().at(i).is_obj_type()) {
} else if (pl_type.is_obj_type()) {
// 纯OUT参数, 对于基础类型直接传入NULL的ObObj
ObObj obj; // 基础类型apply一个空的OBJECT
ObObjMeta null_meta = get_params().at(i).get_meta();
@ -3643,8 +3737,7 @@ do { \
}
OX (get_params().at(i).set_param_meta(null_meta));
} else if (is_anonymous
&& (func_.get_variables().at(i).is_nested_table_type()
|| func_.get_variables().at(i).is_varray_type())
&& (pl_type.is_nested_table_type() || pl_type.is_varray_type())
&& params->at(i).is_ext()) {
#ifndef OB_BUILD_ORACLE_PL
ret = OB_NOT_SUPPORTED;
@ -3663,7 +3756,7 @@ do { \
// 这里先copy入参的值, 由init_complex_obj函数判断是否重新分配内存
OX (get_params().at(i) = params->at(i));
OZ (init_complex_obj(*(get_allocator()),
func_.get_variables().at(i),
pl_type,
get_params().at(i),
!(top_call_ && ctx_.exec_ctx_->get_sql_ctx()->is_execute_call_stmt_)));
}

View File

@ -729,10 +729,10 @@ public:
virtual ~ObPLExecState();
int init(const ParamStore *params = NULL, bool is_anonymous = false);
int defend_stored_routine_change(const ObObjParam &actual_param, const ObPLDataType &formal_param_type);
int check_routine_param_legal(ParamStore *params = NULL);
int check_anonymous_collection_compatible(ObPLComposite &composite, const ObPLDataType &dest_type, bool &need_cast);
int convert_composite(ObObjParam &param, const ObPLDataType &dest_type);
int init_params(const ParamStore *params = NULL, bool is_anonymous = false);
int execute();
int final(int ret);
@ -1208,7 +1208,7 @@ public:
static int simple_execute(ObPLExecCtx *ctx, int64_t argc, int64_t *argv);
static int check_trigger_arg(const ParamStore &params, const ObPLFunction &func);
static int check_trigger_arg(ParamStore &params, const ObPLFunction &func);
std::pair<common::ObBucketLock, common::ObBucketLock>& get_jit_lock() { return jit_lock_; }

View File

@ -7330,22 +7330,11 @@ int ObPLResolver::check_in_param_type_legal(const ObIRoutineParam *param_info,
is_legal));
}
} else if (actually_type.get_user_type_id() != expected_type.get_user_type_id()) {
#ifdef OB_BUILD_ORACLE_PL
if (ObPlJsonUtil::is_pl_jsontype(actually_type.get_user_type_id())) {
OZ (check_composite_compatible(current_block_->get_namespace(),
expected_type.get_user_type_id(),
actually_type.get_user_type_id(),
is_legal));
} else {
#endif
OZ (check_composite_compatible(current_block_->get_namespace(),
actually_type.get_user_type_id(),
expected_type.get_user_type_id(),
is_legal));
}
#ifdef OB_BUILD_ORACLE_PL
OZ (check_composite_compatible(current_block_->get_namespace(),
actually_type.get_user_type_id(),
expected_type.get_user_type_id(),
is_legal));
}
#endif
} else if (actually_type.is_composite_type() || expected_type.is_composite_type()) {
if (actually_type.is_obj_type()
&& ObExtendType == actually_type.get_data_type()->get_obj_type()) {
@ -9798,67 +9787,97 @@ int ObPLResolver::build_raw_expr(const ParseNode *node,
return ret;
}
bool ObPLResolver::is_json_type_compatible(const ObUserDefinedType *left, const ObUserDefinedType *right)
{
bool ObPLResolver::is_json_type_compatible(const ObUserDefinedType *actual_param_type,
const ObUserDefinedType *formal_param_type) {
#ifdef OB_BUILD_ORACLE_PL
return (ObPlJsonUtil::is_pl_json_element_type(left->get_user_type_id())
&& (ObPlJsonUtil::is_pl_json_object_type(right->get_user_type_id())
|| ObPlJsonUtil::is_pl_json_array_type(right->get_user_type_id()))) ;
// TYPE JSON_OBJECT_T UNDER JSON_ELEMENT_T
// TYPE JSON_ARRAY_T UNDER JSON_ELEMENT_T
return (ObPlJsonUtil::is_pl_json_object_type(actual_param_type->get_user_type_id())
|| ObPlJsonUtil::is_pl_json_array_type(actual_param_type->get_user_type_id()))
&& ObPlJsonUtil::is_pl_json_element_type(formal_param_type->get_user_type_id());
#else
return false;
#endif
}
int ObPLResolver::check_composite_compatible(const ObPLINS &ns,
uint64_t left_type_id,
uint64_t right_type_id,
uint64_t actual_param_type_id,
uint64_t formal_param_type_id,
bool &is_compatible)
{
int ret = OB_SUCCESS;
const ObUserDefinedType *left_type = NULL;
const ObUserDefinedType *right_type = NULL;
const ObUserDefinedType *actual_param_type = nullptr;
const ObUserDefinedType *formal_param_type = nullptr;
ObArenaAllocator allocator;
is_compatible = false;
//NOTICE: do not call this function when left_type_id equal to right_type_id
CK (left_type_id != right_type_id);
OZ (ns.get_user_type(left_type_id, left_type, &allocator));
OZ (ns.get_user_type(right_type_id, right_type, &allocator));
CK (OB_NOT_NULL(left_type) && OB_NOT_NULL(right_type));
// NOTICE: do not call this function when left_type_id equal to right_type_id
CK (actual_param_type_id != formal_param_type_id);
// The type of the actual argument may not exist in the current function's lexical scope, an error needs to be
// reported to the upper level to search in the caller's lexical scope. Consider case below:
// ```
// declare
// cursor cur is select * from some_table;
// begin
// for v in cur loop
// some_proc(v);
// end loop;
// end;
// ```
// The type of actual param V passed to SOME_PROC can not be found in SOME_PROC's scope, it is recorded in the caller
// anonymous block's scope.
OZ (ns.get_user_type(actual_param_type_id, actual_param_type, &allocator));
if (OB_FAIL(ret) || OB_ISNULL(actual_param_type)) {
LOG_WARN("failed to get user type of actual param", K(ret), K(actual_param_type_id));
ret = OB_INVALID_ARGUMENT; // indicates that the type of the actual argument was not found
}
OV (OB_NOT_NULL(actual_param_type), OB_INVALID_ARGUMENT, actual_param_type_id, formal_param_type_id);
// The type of the formal argument must exist in the current function's lexical scope.
OZ (ns.get_user_type(formal_param_type_id, formal_param_type, &allocator));
CK (OB_NOT_NULL(formal_param_type));
OZ (check_composite_compatible(actual_param_type, formal_param_type, is_compatible));
return ret;
}
int ObPLResolver::check_composite_compatible(const ObUserDefinedType *actual_param_type,
const ObUserDefinedType *formal_param_type,
bool &is_compatible)
{
int ret = OB_SUCCESS;
// Assigning One Record Variable to Another
// You can assign the value of one record variable to another record variable only in these cases:
// The two variables have the same RECORD type.
// The target variable is declared with a RECORD type, the source variable is declared with %ROWTYPE,
// their fields match in number and order, and corresponding fields have the same data type.
// For record components of composite variables, the types of the composite variables need not match.
if (OB_FAIL(ret)) {
} else if (is_json_type_compatible(left_type, right_type)) {
// 1. The two variables have the same RECORD type.
// 2. The target variable is declared with a RECORD type, the source variable is declared with %ROWTYPE,
// their fields match in number and order, and corresponding fields have the same data type.
// 3. For record components of composite variables, the types of the composite variables need not match.
if (is_json_type_compatible(actual_param_type, formal_param_type)) {
is_compatible = true;
} else if (left_type->is_cursor_type() && right_type->is_cursor_type()) {
} else if (actual_param_type->is_cursor_type() && formal_param_type->is_cursor_type()) {
is_compatible = true;
} else if (right_type->is_generic_type()) {
if ((right_type->is_generic_adt_type()
|| right_type->is_generic_record_type())
&& left_type->is_record_type()) {
} else if (formal_param_type->is_generic_type()) {
if ((formal_param_type->is_generic_adt_type() || formal_param_type->is_generic_record_type())
&& actual_param_type->is_record_type()) {
is_compatible = true;
} else if ((right_type->is_generic_varray_type()
|| right_type->is_generic_v2_table_type()
|| right_type->is_generic_table_type()
|| right_type->is_generic_collection_type())
&& left_type->is_collection_type()) {
} else if ((formal_param_type->is_generic_varray_type()
|| formal_param_type->is_generic_v2_table_type()
|| formal_param_type->is_generic_table_type()
|| formal_param_type->is_generic_collection_type())
&& actual_param_type->is_collection_type()) {
is_compatible = true;
} else if (right_type->is_generic_ref_cursor_type()
&& left_type->is_cursor_type()) {
} else if (formal_param_type->is_generic_ref_cursor_type() && actual_param_type->is_cursor_type()) {
is_compatible = true;
}
} else if (left_type->is_record_type()
&& right_type->is_record_type()
&& !left_type->is_udt_type()
&& !right_type->is_udt_type()
&& (left_type->is_rowtype_type() || right_type->is_rowtype_type())) {
const ObRecordType *left_r_type = static_cast<const ObRecordType *>(left_type);
const ObRecordType *right_r_type = static_cast<const ObRecordType *>(right_type);
CK (OB_NOT_NULL(left_r_type) && OB_NOT_NULL(right_r_type));
OZ (left_r_type->is_compatble(*right_r_type, is_compatible));
} else if (actual_param_type->is_record_type()
&& formal_param_type->is_record_type()
&& !actual_param_type->is_udt_type()
&& !formal_param_type->is_udt_type()
&& (actual_param_type->is_rowtype_type() || formal_param_type->is_rowtype_type())) {
const ObRecordType *actual_r_type = static_cast<const ObRecordType *>(actual_param_type);
const ObRecordType *formal_r_type = static_cast<const ObRecordType *>(formal_param_type);
CK (OB_NOT_NULL(actual_r_type) && OB_NOT_NULL(formal_r_type));
OZ (actual_r_type->is_compatble(*formal_r_type, is_compatible));
}
return ret;
}
@ -10089,17 +10108,17 @@ int ObPLResolver::resolve_expr(const ParseNode *node,
&& expr->get_expr_type() != T_FUN_SYS_PDB_GET_RUNTIME_INFO) {
bool is_compatible = false;
OZ (check_composite_compatible(current_block_->get_namespace(),
expected_type->get_user_type_id(),
expr->get_result_type().get_udt_id(),
expected_type->get_user_type_id(),
is_compatible));
if (OB_FAIL(ret)) {
} else if (!is_compatible) {
ret = OB_ERR_INVALID_TYPE_FOR_OP;
#ifdef OB_BUILD_ORACLE_PL
// error code compiltable with oracle
if (ObPlJsonUtil::is_pl_json_object_type(expected_type->get_user_type_id())
&& ObPlJsonUtil::is_pl_json_element_type(expr->get_result_type().get_udt_id())
&& ObPlJsonUtil::is_pl_json_array_type(expr->get_result_type().get_udt_id())) {
if ((ObPlJsonUtil::is_pl_json_array_type(expected_type->get_user_type_id())
|| ObPlJsonUtil::is_pl_json_object_type(expected_type->get_user_type_id()))
&& ObPlJsonUtil::is_pl_json_element_type(expr->get_result_type().get_udt_id())) {
ret = OB_ERR_EXPRESSION_WRONG_TYPE;
}
#endif

View File

@ -512,10 +512,15 @@ public:
ObIArray<ObRawExpr*>& params,
const ObUserDefinedType *user_type,
ObPLDataType &pl_type);
static bool is_json_type_compatible(
const ObUserDefinedType *left, const ObUserDefinedType *right);
static bool is_json_type_compatible(const ObUserDefinedType *actual_param_type,
const ObUserDefinedType *formal_param_type);
static int check_composite_compatible(const ObPLINS &ns,
uint64_t left_type_id, uint64_t right_type_id, bool &is_compatible);
uint64_t actual_param_type_id,
uint64_t formal_param_type_id,
bool &is_compatible);
static int check_composite_compatible(const ObUserDefinedType *actual_param_type,
const ObUserDefinedType *formal_param_type,
bool &is_compatible);
static
int resolve_nocopy_params(const share::schema::ObIRoutineInfo *routine_info,

View File

@ -67,7 +67,8 @@ public:
pl::ObProcType type)
{
bool is_trg_routine = false;
if (schema::ObTriggerInfo::is_trigger_body_package_id(package_id) && pl::ObProcType::PACKAGE_PROCEDURE == type) {
if (schema::ObTriggerInfo::is_trigger_body_package_id(package_id)
&& (pl::ObProcType::PACKAGE_PROCEDURE == type || pl::ObProcType::PACKAGE_FUNCTION == type)) {
if (lib::is_oracle_mode()) {
is_trg_routine = (routine_id >= ROUTINE_IDX_CALC_WHEN && routine_id <= ROUTINE_IDX_AFTER_STMT);
} else {

View File

@ -231,7 +231,7 @@ int ObPlAggUdfFunction::build_in_params_store(ObObjParam &pl_obj,
LOG_WARN("failed to push back param", K(ret));
} else if (obj_params != NULL &&
OB_FAIL(ObExprUDF::process_in_params(obj_params, param_num, params_desc,
params_type_, *udf_params, *allocator_))) {
params_type, *udf_params, *allocator_))) {
LOG_WARN("failed to process in params", K(ret));
} else {
LOG_TRACE("succeed to build in params store", K(pl_obj), K(obj_params), K(params_desc),

View File

@ -1127,27 +1127,14 @@ int ObResolverUtils::check_type_match(const pl::ObPLResolveCtx &resolve_ctx,
} else {
// 复杂类型的TypeClass相同, 需要检查兼容性
bool is_compatible = false;
#ifdef OB_BUILD_ORACLE_PL
if (ObPlJsonUtil::is_pl_jsontype(src_type_id)) {
OZ (ObPLResolver::check_composite_compatible(
NULL == resolve_ctx.params_.secondary_namespace_
? static_cast<const ObPLINS&>(resolve_ctx)
: static_cast<const ObPLINS&>(*resolve_ctx.params_.secondary_namespace_),
dst_pl_type.get_user_type_id(),
src_type_id,
is_compatible), K(src_type_id), K(dst_pl_type), K(resolve_ctx.params_.is_execute_call_stmt_));
} else {
#endif
OZ (ObPLResolver::check_composite_compatible(
NULL == resolve_ctx.params_.secondary_namespace_
? static_cast<const ObPLINS&>(resolve_ctx)
: static_cast<const ObPLINS&>(*resolve_ctx.params_.secondary_namespace_),
src_type_id,
dst_pl_type.get_user_type_id(),
is_compatible), K(src_type_id), K(dst_pl_type), K(resolve_ctx.params_.is_execute_call_stmt_));
#ifdef OB_BUILD_ORACLE_PL
}
#endif
OZ (ObPLResolver::check_composite_compatible(
NULL == resolve_ctx.params_.secondary_namespace_
? static_cast<const ObPLINS &>(resolve_ctx)
: static_cast<const ObPLINS &>(*resolve_ctx.params_.secondary_namespace_),
src_type_id,
dst_pl_type.get_user_type_id(),
is_compatible),
K(src_type_id), K(dst_pl_type), K(resolve_ctx.params_.is_execute_call_stmt_));
if (OB_FAIL(ret)) {
} else if (is_compatible) {
OX (match_info = ObRoutineMatchInfo::MatchInfo(true, src_type, dst_type));