/* * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * * openGauss is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: * * http://license.coscl.org.cn/MulanPSL2 * * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. * --------------------------------------------------------------------------------------- * * masking.cpp * * IDENTIFICATION * src/contrib/security_plugin/masking.cpp * * --------------------------------------------------------------------------------------- */ #include #include "postgres.h" #include "nodes/nodes.h" #include "nodes/nodeFuncs.h" #include "nodes/params.h" #include "nodes/primnodes.h" #include "parser/parse_clause.h" #include "parser/parse_target.h" #include "parser/parse_expr.h" #include "parser/parse_oper.h" #include "parser/parse_func.h" #include "parser/parse_utilcmd.h" #include "parser/parse_relation.h" #include "utils/numeric.h" #include "catalog/pg_proc.h" #include "utils/acl.h" #include "utils/syscache.h" #include "utils/lsyscache.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "parser/parse_type.h" #include "nodes/makefuncs.h" #include "parser/parse_coerce.h" #include "commands/tablespace.h" #include "masking.h" #include "access_audit.h" #include "gs_threadlocal.h" #include "gs_policy_plugin.h" #include "gs_policy/gs_vector.h" #include "gs_policy/gs_policy_masking.h" #define BUFFSIZE 256 #ifdef ENABLE_UT #define static #endif using StrMap = gs_stl::gs_map; static THR_LOCAL StrMap* masked_prepared_stmts = NULL; static THR_LOCAL StrMap* masked_cursor_stmts = NULL; void reset_node_location() { t_thrd.security_policy_cxt.node_location = 0; } const int DATA_TYPE_LENGTH = 2; /* data type length */ static Node* create_udf_function(ParseState *pstate, Var* var, Oid funcid, masking_result *result, long long polid, const char* full_column, const func_params *f_params); static void parse_func(Node *expr); static void get_var_value(const List *rtable, Var *var, PolicyLabelItem *full_column, PolicyLabelItem *view_name); static void get_fqdn_by_joinalias(const List *rtable, const RangeTblEntry *rte, const Var *var, PolicyLabelItem *full_column, PolicyLabelItem *view_name) { ListCell *l = NULL; int joinpos = 1; /* Scan all joinaliasvars of JOIN RTE. * varattno marks its orders in joinaliasvars, * and we can use it to get the real var of join statement */ foreach (l, rte->joinaliasvars) { if (joinpos != (int)var->varattno) { ++joinpos; continue; } /* Get real join var information */ Var *joinvar = (Var *)lfirst(l); get_var_value(rtable, joinvar, full_column, view_name); break; } } static void parse_value(const List *rtable, RangeTblEntry *rte, Var *var, PolicyLabelItem *full_column, PolicyLabelItem *view_name) { switch (rte->rtekind) { case RTE_RELATION: /* relation ID of current table */ if (OidIsValid(rte->relid)) { get_fqdn_by_relid(rte, full_column, var, view_name); } break; case RTE_JOIN: /* RTE is JOIN kind */ get_fqdn_by_joinalias(rtable, rte, var, full_column, view_name); break; case RTE_FUNCTION: /* relation is function */ parse_func(rte->funcexpr); break; default: /* Here is no need to parse subquery or CTE, because subquery or CTE will mask itself. */ break; } } static void get_var_value(const List *rtable, Var *var, PolicyLabelItem *full_column, PolicyLabelItem *view_name) { ListCell *l = NULL; int pos = 1; /* In TargetList, each TargetEntry's varno points to rtable. * So, the RangeTableEntry corresponding to each varno needs to be got first. * Additionally, parse full column name for every (var-rte) pair. */ foreach (l, rtable) { if (pos != (int)var->varno) { ++pos; continue; } RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); parse_value(rtable, rte, var, full_column, view_name); break; } } static bool mask_expr_node(ParseState *pstate, Expr*& expr, const policy_set *policy_ids, masking_result *result, List* rtable, bool can_mask = true); bool handle_masking_node(ParseState *pstate, Expr*& src_expr, const policy_set *policy_ids, masking_result *result, List *rtable, bool can_mask = true); static bool mask_func(ParseState *pstate, Expr*& expr, const policy_set *policy_ids, masking_result *result, List* rtable, bool can_mask = true) { if (expr == NULL) { return false; } bool is_masking = false; if (nodeTag(expr) == T_FuncExpr) { FuncExpr *fe = (FuncExpr *)(expr); PolicyLabelItem func_value; if (get_function_name(fe->funcid, &func_value)) { set_result_set_function(func_value); } if (fe->args != NULL) { ListCell* temp = NULL; foreach(temp, fe->args) { Node *&item = (Node*&)lfirst(temp); bool expr_masked = mask_expr_node(pstate, (Expr*&)item, policy_ids, result, rtable, can_mask); is_masking = is_masking || expr_masked; } } } return is_masking; } static void parse_func(Node* expr) { switch (nodeTag(expr)) { case T_FuncExpr: { FuncExpr *fe = (FuncExpr *)expr; { PolicyLabelItem func_value; if (get_function_name(fe->funcid, &func_value)) { set_result_set_function(func_value); } } if (fe->args != NIL) { ListCell* temp = NULL; foreach(temp, fe->args) { Node *item = (Node*)lfirst(temp); if (IsA(item, FuncExpr)) { parse_func(item); } } } } break; default: break; } } static bool get_function_id(int vartype, const char* funcname, Oid *funcid, Oid *rettype, Oid schemaid = SchemaNameGetSchemaOid("pg_catalog", true)) { CatCList *catlist = NULL; #ifndef ENABLE_MULTIPLE_NODES if (t_thrd.proc->workingVersionNum < 92470) { catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); } else { catlist = SearchSysCacheList1(PROCALLARGS, CStringGetDatum(funcname)); } #else catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); #endif if (catlist != NULL) { for (int i = 0; i < catlist->n_members; ++i) { HeapTuple proctup = t_thrd.lsc_cxt.FetchTupleFromCatCList(catlist, i); Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); if (procform && (int)procform->prorettype == vartype && procform->pronamespace == schemaid) { (*funcid) = HeapTupleGetOid(proctup); (*rettype) = procform->prorettype; ReleaseSysCacheList(catlist); return true; } } ReleaseSysCacheList(catlist); return false; } return false; } static Node* create_predefined_function(const char* funcname, int funcid, int rettype, Node* arg, int funccollid, CoercionForm funcformat = COERCE_EXPLICIT_CALL) { FuncExpr *funcexpr = makeNode(FuncExpr); funcexpr->funcid = funcid; funcexpr->funcresulttype = rettype; funcexpr->funcretset = false; funcexpr->funcvariadic = false; funcexpr->funcformat = funcformat; if (IsA(arg, Var)) { Var* var = (Var*)arg; var->location += t_thrd.security_policy_cxt.node_location; funcexpr->location = var->location; var->location += strlen(funcname) + 1; t_thrd.security_policy_cxt.node_location += (strlen(funcname) + 2); } else if(IsA(arg, Const)) { Const* const_arg = (Const*)arg; const_arg->location += t_thrd.security_policy_cxt.node_location; funcexpr->location = const_arg->location; const_arg->location += strlen(funcname) + 1; t_thrd.security_policy_cxt.node_location += (strlen(funcname) + 2); } else if(IsA(arg, FuncExpr)) { FuncExpr* fe = (FuncExpr*)arg; fe->location += t_thrd.security_policy_cxt.node_location; funcexpr->location = fe->location; fe->location += strlen(funcname) + 1; t_thrd.security_policy_cxt.node_location += (strlen(funcname) + 2); } funcexpr->args = lappend(funcexpr->args, arg); funcexpr->funccollid = funccollid; funcexpr->inputcollid = 100; /* OID of collation that function should use */ return (Node*)funcexpr; } static Node* create_relabel_type(Node* func, int resulttype, int location, CoercionForm relabelformat = COERCE_EXPLICIT_CAST) { if (func == NULL) { return NULL; } RelabelType *typeexpr = makeNode(RelabelType); if (!typeexpr)return NULL; typeexpr->arg = (Expr*)func; typeexpr->resulttype = resulttype; typeexpr->resulttypmod = -1; typeexpr->resultcollid = 100; /* OID of collation */ typeexpr->relabelformat = relabelformat; typeexpr->location = location; return (Node*)typeexpr; } static inline Node* create_string_node(ParseState *pstate, const char* letter, int location, int col_type = TEXTOID) { Node* const_node = (Node*)make_const(pstate, makeString((char*)letter), location); const_node = coerce_type(pstate, const_node, ((Const*)const_node)->consttype, col_type, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); return const_node; } Node* create_integer_node(ParseState *pstate, int value, int location, int col_type, bool make_cast) { Node* const_node = (Node*)make_const(pstate, makeInteger(value), location); if (make_cast) { const_node = coerce_type(pstate, const_node, ((Const*)const_node)->consttype, col_type, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); } return const_node; } static inline Node* create_float_node(ParseState *pstate, const char* value, int location, int col_type = FLOAT4OID) { Node* const_node = (Node*)make_const(pstate, makeFloat((char*)value), location); const_node = coerce_type(pstate, const_node, ((Const*)const_node)->consttype, col_type, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); return const_node; } static inline Node* create_empty_node(ParseState *pstate, int location, int col_type = INT4OID, bool make_cast = true) { Const* const_node = (Const*)create_integer_node(pstate, 0, location, col_type, make_cast); if (const_node) { const_node->constisnull = false; const_node->constbyval = true; } return (Node*)const_node; } static Node *create_repeat_function(ParseState *pstate, const char *letter, Node *arg) { Var *var = (Var*)arg; Oid funcid = 0; Oid rettype = 0; if (get_function_id(TEXTOID, "repeat", &funcid, &rettype)) { Node *const_node = create_string_node(pstate, letter, var->location); Node *repeat_func = create_predefined_function("repeat", funcid, rettype, const_node, 100); FuncExpr *funcexpr = (FuncExpr*)repeat_func; if (get_function_id(INT4OID, "length", &funcid, &rettype)) { Node *length_func = create_predefined_function("length", funcid, rettype, arg, 0); funcexpr->args = lappend(funcexpr->args, (Node*)length_func); } return repeat_func; } return arg; } static Node *regexp_function(ParseState *pstate, Var *var, masking_result *result, long long polid, const char *full_column, const func_params *f_params) { bool cast = false; Node *regexp_func_node = NULL; switch (var->vartype) { case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: cast = true; case TEXTOID: { Oid funcid = 0; Oid rettype = TEXTOID; get_function_id(TEXTOID, "regexpmasking", &funcid, &rettype); if (funcid > 0) { regexp_func_node = create_udf_function(pstate, var, funcid, result, polid, full_column, f_params); if (cast) { Expr *cast_fun = (Expr*)create_relabel_type(regexp_func_node, var->vartype, var->location); if (cast_fun != NULL) { /* success */ regexp_func_node = (Node*)cast_fun; } } (*result)[polid][M_REGEXP].insert(full_column); } break; } default: break; } return regexp_func_node; } static Node *shuffle_function(ParseState *pstate, Var *var, masking_result *result, long long polid, const char *full_column) { bool make_cast = false; Node *shuffle_func_node = NULL; switch (var->vartype) { case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: make_cast = true; case TEXTOID: { Oid funcid = 0; Oid rettype = TEXTOID; get_function_id(TEXTOID, "shufflemasking", &funcid, &rettype); if (funcid > 0) { shuffle_func_node = create_predefined_function("shufflemasking", funcid, TEXTOID, (Node*)var, 100); if (make_cast) { Expr *cast_fun = (Expr*)create_relabel_type(shuffle_func_node, var->vartype, var->location); if (cast_fun != NULL) { /* success */ shuffle_func_node = (Node*)cast_fun; } } (*result)[polid][M_SHUFFLE].insert(full_column); } break; } default: break; } return shuffle_func_node; } static Node *random_function(ParseState *pstate, Var *var, masking_result *result, long long polid, const char *full_column) { bool cast = false; Node *random_func_node = NULL; switch (var->vartype) { case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: cast = true; case TEXTOID: { Oid funcid = 0; Oid rettype = TEXTOID; get_function_id(TEXTOID, "randommasking", &funcid, &rettype); if (funcid > 0) { random_func_node = create_predefined_function("randommasking", funcid, TEXTOID, (Node*)var, 100); if (cast) { Expr *cast_fun = (Expr*)create_relabel_type(random_func_node, var->vartype, var->location); if (cast_fun != NULL) { /* success */ random_func_node = (Node*)cast_fun; } } (*result)[polid][M_RANDOM].insert(full_column); } break; } default: break; } return random_func_node; } static Node *all_digits_function(ParseState *pstate, Var *var, masking_result *result, long long polid, const char *full_column, const gs_stl::gs_vector *func_params) { bool make_cast_f = false; Node *digits_func_node = NULL; switch (var->vartype) { case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: make_cast_f = true; case TEXTOID: { Oid funcid = 0; Oid rettype = TEXTOID; get_function_id(TEXTOID, "alldigitsmasking", &funcid, &rettype); if (funcid > 0) { digits_func_node = create_predefined_function("alldigitsmasking", funcid, TEXTOID, (Node*)var, 100); if (func_params->size()) { Node *constnode = create_string_node(pstate, func_params->begin()->c_str() + DATA_TYPE_LENGTH, var->location); FuncExpr *funcexpr = (FuncExpr*)digits_func_node; funcexpr->args = lappend(funcexpr->args, constnode); } if (make_cast_f) { Expr *castfunc = (Expr*)create_relabel_type(digits_func_node, var->vartype, var->location); if (castfunc != NULL) { /* success */ digits_func_node = (Node*)castfunc; } } (*result)[polid][M_ALLDIGITS].insert(full_column); } break; } default: break; } return digits_func_node; } static Node *email_function(ParseState *pstate, int masking_behavious, Var *var, masking_result *result, long long polid, const char *full_column, const gs_stl::gs_vector *func_params) { bool makecast = false; Node *email_func_node = NULL; switch (var->vartype) { case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: makecast = true; case TEXTOID: { Oid funcid = 0; Oid rettype = TEXTOID; const char *funcname = (masking_behavious == M_BASICEMAIL) ? "basicemailmasking" : "fullemailmasking"; get_function_id(TEXTOID, funcname, &funcid, &rettype); if (funcid > 0) { email_func_node = create_predefined_function(funcname, funcid, TEXTOID, (Node*)var, 100); if (func_params->size()) { Node *const_node = create_string_node(pstate, func_params->begin()->c_str() + DATA_TYPE_LENGTH, var->location); FuncExpr *funcexpr = (FuncExpr*)email_func_node; funcexpr->args = lappend(funcexpr->args, const_node); } if (makecast) { Expr *cast_fun = (Expr*)create_relabel_type(email_func_node, var->vartype, var->location); if (cast_fun != NULL) { /* success */ email_func_node = (Node*)cast_fun; } } (*result)[polid][masking_behavious].insert(full_column); } break; } default: break; } return email_func_node; } static Node *maskall_function(ParseState *pstate, int masking_behavious, Var *var, masking_result *result, long long polid, const char *full_column, const gs_stl::gs_vector *func_params) { bool make_cast = false; char time_str[BUFFSIZE] = {0}; int printed_size = 0; Node *maskall_func_node = NULL; switch (var->vartype) { case BOOLOID: { Node *const_int_node = create_integer_node(pstate, 0, var->location, var->vartype); if (const_int_node != NULL) { /* success */ maskall_func_node = const_int_node; } } break; case RELTIMEOID: printed_size = snprintf_s(time_str, sizeof(time_str), sizeof(time_str) - 1, "1970"); securec_check_ss(printed_size, "\0", "\0"); case TIMEOID: case TIMETZOID: case INTERVALOID: if (!printed_size) { printed_size = snprintf_s(time_str, sizeof(time_str), sizeof(time_str) - 1, "00:00:00.0000+00"); securec_check_ss(printed_size, "\0", "\0"); } case TIMESTAMPOID: case TIMESTAMPTZOID: case SMALLDATETIMEOID: case ABSTIMEOID: { if (!printed_size) { printed_size = snprintf_s(time_str, sizeof(time_str), sizeof(time_str) - 1, "1970-01-01 00:00:00.0000"); securec_check_ss(printed_size, "\0", "\0"); } Node *const_node = create_string_node(pstate, time_str, var->location, var->vartype); if (const_node != NULL) { /* success */ maskall_func_node = const_node; } } break; case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: case NAMEOID: make_cast = true; case TEXTOID: { const char *replace_str = "x"; if (func_params && func_params->size() > 0) { replace_str = func_params->begin()->c_str(); } maskall_func_node = create_repeat_function(pstate, replace_str, (Node*)var); if (maskall_func_node != NULL && make_cast) { Expr *cast_func = (Expr*)create_relabel_type(maskall_func_node, var->vartype, var->location); if (cast_func != NULL) { /* success */ maskall_func_node = (Node*)cast_func; } } } break; case INT8OID: case INT4OID: case INT2OID: case INT1OID: case NUMERICOID: case FLOAT4OID: /* real */ case FLOAT8OID: { Node *const_int_node = create_integer_node(pstate, 0, var->location, var->vartype, (var->vartype != CASHOID)); if (const_int_node != NULL) { /* success */ maskall_func_node = const_int_node; } } break; default: /* wrong column type */ { ereport(WARNING, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("Unsupported type of column %s will not be masked by policy %lld.", full_column, polid))); } break; } (*result)[polid][M_MASKALL].insert(full_column); return maskall_func_node; } /* get funcname from pg_proc */ static const char* get_udf_function_name(long long funcid, Oid& rettype, func_types* types) { if (!funcid) { return ""; } HeapTuple tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); if (!HeapTupleIsValid(tuple)) { return ""; } Form_pg_proc func_rel = (Form_pg_proc) GETSTRUCT(tuple); const char* procname = func_rel->proname.data; rettype = func_rel->prorettype; get_function_parameters(tuple, types); ReleaseSysCache(tuple); return procname; } static Node* convert_text_to_numeric(Oid resulttype, Node* data) { int funccollid = 100; /* OID of collation of result */ switch (resulttype) { case INT1OID: return create_predefined_function("text_int1", F_TEXT_INT1, resulttype, data, funccollid); case INT2OID: return create_predefined_function("text_int2", F_TEXT_INT2, resulttype, data, funccollid); case INT4OID: return create_predefined_function("text_int4", F_TEXT_INT4, resulttype, data, funccollid); case INT8OID: return create_predefined_function("text_int8", F_TEXT_INT8, resulttype, data, funccollid); case NUMERICOID: return create_predefined_function("text_numeric", F_TEXT_INT8, resulttype, data, funccollid); case FLOAT4OID: return create_predefined_function("text_float4", F_TEXT_FLOAT4, resulttype, data, funccollid); case FLOAT8OID: return create_predefined_function("text_float8", F_TEXT_FLOAT8, resulttype, data, funccollid); case RELTIMEOID: return create_predefined_function("text_date", F_TEXT_DATE, resulttype, data, funccollid); case TIMEOID: case TIMETZOID: case INTERVALOID: case TIMESTAMPOID: case SMALLDATETIMEOID: case ABSTIMEOID: return create_predefined_function("text_timestamp", F_TEXT_TIMESTAMP, resulttype, data, funccollid); default: break; } return NULL; } static Node* convert_numeric_to_text(Oid resulttype, Node* data) { int funccollid = 100; /* OID of collation of result */ switch (resulttype) { case INT1OID: return create_predefined_function("int1_text", F_INT1_TEXT, TEXTOID, data, funccollid); case INT2OID: return create_predefined_function("int2_text", F_INT2_TEXT, TEXTOID, data, funccollid); case INT4OID: return create_predefined_function("int4_text", F_INT4_TEXT, TEXTOID, data, funccollid); case INT8OID: return create_predefined_function("int8_text", F_INT8_TEXT, TEXTOID, data, funccollid); case NUMERICOID: return create_predefined_function("numeric_text", F_NUMERIC_TEXT, TEXTOID, data, funccollid); case FLOAT4OID: return create_predefined_function("float4_text", F_FLOAT4_TEXT, TEXTOID, data, funccollid); case FLOAT8OID: return create_predefined_function("float8_text", F_FLOAT8_TEXT, TEXTOID, data, funccollid); case RELTIMEOID: return create_predefined_function("date_text", F_DATE_TEXT, TEXTOID, data, funccollid); case TIMEOID: case TIMETZOID: case INTERVALOID: case TIMESTAMPOID: case SMALLDATETIMEOID: case ABSTIMEOID: return create_predefined_function("timestamp_text", F_TIMESTAMP_TEXT, TEXTOID, data, funccollid); case TIMESTAMPTZOID: return create_predefined_function("timestampsone_text", F_TIMESTAMPZONE_TEXT, TEXTOID, data, funccollid); default: break; } return NULL; } static Node* check_and_fix_col_node(Oid prog_col_type, Var* var) { switch (prog_col_type) { /* function input type */ case TEXTOID: case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: { switch (var->vartype) { /* col type */ case TEXTOID: case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: return (Node*)var; break; default: break; } } break; case INT1OID: case INT2OID: case INT4OID: case INT8OID: case NUMERICOID: case FLOAT4OID: // real case FLOAT8OID: { if (var->vartype == prog_col_type) { return (Node*)var; } } break; default: break; } return NULL; /* function input type not support or mismatching, use maskall */ } static Node* verify_function_return_type(Oid func_rettype, Var* var, FuncExpr* funcexpr) { switch (func_rettype) { /* function return type */ case TEXTOID: case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: { switch (var->vartype) { /* col type */ case INT1OID: case INT2OID: case INT4OID: case INT8OID: case NUMERICOID: case FLOAT4OID: case FLOAT8OID: case TIMEOID: case TIMETZOID: case INTERVALOID: case TIMESTAMPOID: case SMALLDATETIMEOID: case ABSTIMEOID: case RELTIMEOID: return convert_text_to_numeric(var->vartype, (Node*)funcexpr); break; case TEXTOID: case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: { Expr *cast_fun = (Expr*)create_relabel_type((Node*)funcexpr, var->vartype, var->location); return (Node*)cast_fun; } break; default: break; } } break; case INT1OID: case INT2OID: case INT4OID: case INT8OID: case NUMERICOID: case FLOAT4OID: case FLOAT8OID: case TIMEOID: case TIMETZOID: case INTERVALOID: case TIMESTAMPOID: case SMALLDATETIMEOID: case ABSTIMEOID: case RELTIMEOID: { switch (var->vartype) { case TEXTOID: case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: return convert_numeric_to_text(var->vartype, (Node*)funcexpr); break; default: break; } } break; default: return NULL; /* function return type not support, use maskall */ } return (Node*)funcexpr; } static Node* create_udf_function(ParseState *pstate, Var* var, Oid funcid, masking_result *result, long long polid, const char* full_column, const func_params *f_params) { Node* ret_node = NULL; AclResult aclresult; Oid rescollid = 100; /* OID of collation, or InvalidOid if none */ PG_TRY(); { if (funcid == 0) { return NULL; } Oid rettype = var->vartype; aclresult = pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE); func_types proctypes; const char* funcname = get_udf_function_name(funcid, rettype, &proctypes); if (aclresult == ACLCHECK_OK && strlen(funcname) != 0) { /* verify function input parameters */ if (verify_proc_params(f_params, &proctypes)) { Oid prog_col_type = proctypes[0]; /* check if mismatching between function input and col type */ Node* newc = check_and_fix_col_node(prog_col_type, var); if (newc) { FuncExpr* funcexpr = (FuncExpr*)create_predefined_function(funcname, funcid, rettype, newc, rescollid); func_params::const_iterator it = f_params->begin(), eit = f_params->end(); for (; it != eit; ++it) { Node * const_node = NULL; if (!strncasecmp(it->c_str(), "s:", DATA_TYPE_LENGTH)) { const_node = create_string_node(pstate, it->c_str() + DATA_TYPE_LENGTH, var->location); } else if (!strncasecmp(it->c_str(), "i:", DATA_TYPE_LENGTH)) const_node = create_integer_node(pstate, atoi(it->c_str() + DATA_TYPE_LENGTH), var->location); else if (!strncasecmp(it->c_str(), "f:", DATA_TYPE_LENGTH)) const_node = create_float_node(pstate, it->c_str() + DATA_TYPE_LENGTH, var->location); if (!const_node) break; funcexpr->args = lappend(funcexpr->args, const_node); } (*result)[polid][M_MASKALL].insert(full_column); /* verify function output parameters */ ret_node = verify_function_return_type(rettype, var, funcexpr); } } } } PG_CATCH(); { PG_RE_THROW(); } PG_END_TRY(); (*result)[polid][M_MASKALL].insert(full_column); return ret_node; } static Node *credit_card_function(ParseState *pstate, int masking_behavious, Var *var, masking_result *result, long long polid, const char *full_column, const gs_stl::gs_vector *func_params) { bool make_cast = false; Node *credit_func_node = NULL; switch (var->vartype) { case BPCHAROID: case VARCHAROID: case NVARCHAR2OID: make_cast = true; case TEXTOID: { Oid funcid = 0; Oid rettype = 25; get_function_id(TEXTOID, "creditcardmasking", &funcid, &rettype); if (funcid > 0) { credit_func_node = create_predefined_function("creditcardmasking", funcid, TEXTOID, (Node*)var, 100); if (func_params->size()) { Node *const_node = create_string_node(pstate, func_params->begin()->c_str() + DATA_TYPE_LENGTH, var->location); FuncExpr *fexpr = (FuncExpr*)credit_func_node; fexpr->args = lappend(fexpr->args, const_node); } if (make_cast) { Expr* castfun = (Expr*)create_relabel_type(credit_func_node, var->vartype, var->location); if (castfun != NULL) { /* success */ credit_func_node = (Node*)castfun; } } (*result)[polid][M_CREDIT_CARD].insert(full_column); } break; } default: break; } return credit_func_node; } static Node *mask_node_by_behavious(bool *is_masking, int masking_behavious, ParseState *pstate, Var* var, masking_result *result, long long polid, const char* full_column, const gs_stl::gs_vector *func_params) { Node *masked_node = NULL; switch (masking_behavious) { case M_CREDIT_CARD: { (*is_masking) = true; if ((masked_node = credit_card_function(pstate, masking_behavious, var, result, polid, full_column, func_params)) == NULL) { masked_node = maskall_function(pstate, masking_behavious, var, result, polid, full_column, func_params); } } break; case M_BASICEMAIL: case M_FULLEMAIL: { (*is_masking) = true; if ((masked_node = email_function(pstate, masking_behavious, var, result, polid, full_column, func_params)) == NULL) { masked_node = maskall_function(pstate, masking_behavious, var, result, polid, full_column, func_params); } } break; case M_ALLDIGITS: { (*is_masking) = true; if ((masked_node = all_digits_function(pstate, var, result, polid, full_column, func_params)) == NULL) { masked_node = maskall_function(pstate, masking_behavious, var, result, polid, full_column, func_params); } } break; case M_SHUFFLE: { (*is_masking) = true; if ((masked_node = shuffle_function(pstate, var, result, polid, full_column)) == NULL) { masked_node = maskall_function(pstate, masking_behavious, var, result, polid, full_column, func_params); } } break; case M_RANDOM: { (*is_masking) = true; if ((masked_node = random_function(pstate, var, result, polid, full_column)) == NULL) { masked_node = maskall_function(pstate, masking_behavious, var, result, polid, full_column, func_params); } } break; case M_REGEXP: { (*is_masking) = true; if ((masked_node = regexp_function(pstate, var, result, polid, full_column, func_params)) == NULL) { masked_node = maskall_function(pstate, masking_behavious, var, result, polid, full_column, NULL); } } break; case M_UNKNOWN: case M_MASKALL: { (*is_masking) = true; masked_node = maskall_function(pstate, masking_behavious, var, result, polid, full_column, func_params); } break; default: { (*is_masking) = true; if ((masked_node = create_udf_function(pstate, var, masking_behavious, result, polid, full_column, func_params)) == NULL) { masked_node = maskall_function(pstate, M_UNKNOWN, var, result, polid, full_column, NULL); } } break; } if (masked_node == NULL) { (*is_masking) = false; } return masked_node; } bool handle_masking_node(ParseState *pstate, Expr*& src_expr, const policy_set *policy_ids, masking_result *result, List* rtable, bool can_mask) { if (src_expr == NULL || policy_ids->empty()) { return false; } Var* var = (Var*)(src_expr); PolicyLabelItem full_column(0, 0, O_COLUMN), view_name; get_var_value(rtable, var, &full_column, &view_name); /* fqdn column name */ /* Varattno 'zero' references the whole tuple. */ if (full_column.m_obj_type == O_COLUMN && var->varattno == 0 && OidIsValid(full_column.m_object) && is_masked_relation_enabled(full_column.m_object)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Un-support operation for whole tuple contains masked column in table \"%s\".", get_rel_name(full_column.m_object)))); } if (full_column.empty() && rtable != NIL) { /* sub query */ bool is_found = false; audit_open_relation(rtable, var, &full_column, &is_found); } bool is_masking = false; int masking_behavious = 0; long long polid = 0; gs_stl::gs_vector func_params; if (check_masking_policy_action(policy_ids, &full_column, &view_name, &masking_behavious, &polid, &func_params)) { gs_stl::gs_string log_column; full_column.get_fqdn_value(&log_column); /* When mask a column which not allowed to be masked, we will report an error */ if (!can_mask) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Un-support operation for masking column."))); } Node* masked_node = NULL; masked_node = mask_node_by_behavious(&is_masking, masking_behavious, pstate, var, result, polid, log_column.c_str(), &func_params); if (is_masking) { ereport(DEBUG2, (errmodule(MOD_PARSER), errmsg("Column %s will be masked by masking behavious %d", log_column.c_str(), masking_behavious))); } if (masked_node != NULL) { src_expr = (Expr*)masked_node; } } return is_masking; } static bool mask_sublink(ParseState *pstate, Expr*& expr, const policy_set *policy_ids, masking_result *result, List* rtable, bool can_mask) { if (expr == NULL) { return false; } SubLink *sublink = (SubLink *)expr; Query *query = (Query *) sublink->subselect; ListCell* temp = NULL; bool is_masking = false; foreach (temp, query->targetList) { TargetEntry *old_tle = (TargetEntry *)lfirst(temp); is_masking = parser_target_entry(pstate, old_tle, policy_ids, result, query->rtable, can_mask) || is_masking; } return is_masking; } static bool mask_list_parameters(List **params, ParseState *pstate, bool *is_masking, const policy_set *policy_ids, masking_result *result, List* rtable, bool can_mask = true) { List* masked_list = NIL; ListCell *lc = NULL; foreach(lc, (*params)) { Node *item = (Node *) lfirst(lc); switch (nodeTag(item)) { case T_SubLink: { bool expr_masked = mask_sublink(pstate, (Expr*&)item, policy_ids, result, rtable, can_mask); *is_masking = expr_masked || *is_masking; } break; case T_Aggref: { Aggref* agg = (Aggref *) item; if (agg && agg->args != NIL && list_length(agg->args) > 0) { mask_list_parameters(&(agg->args), pstate, is_masking, policy_ids, result, rtable, can_mask); } } break; case T_OpExpr: { OpExpr* opexpr = (OpExpr*)item; if (opexpr && opexpr->args != NIL && list_length(opexpr->args) > 0) { mask_list_parameters(&(opexpr->args), pstate, is_masking, policy_ids, result, rtable, can_mask); } } break; case T_Var: case T_RelabelType: case T_FuncExpr: case T_CaseExpr: case T_CaseWhen: { bool expr_masked = mask_expr_node(pstate, (Expr*&)item, policy_ids, result, rtable, can_mask); *is_masking = expr_masked || *is_masking; } break; case T_TargetEntry: { TargetEntry*& target_entry = (TargetEntry*&)item; bool expr_masked = parser_target_entry(pstate, target_entry, policy_ids, result, rtable, can_mask); *is_masking = expr_masked || *is_masking; } break; default: break; } masked_list = lappend(masked_list, item); } if (*is_masking) (*params) = masked_list; return *is_masking; } static bool mask_expr_node(ParseState *pstate, Expr*& expr, const policy_set *policy_ids, masking_result *result, List* rtable, bool can_mask) { bool is_masking = false; if (expr == NULL) { return false; } switch (nodeTag(expr)) { case T_SubLink: is_masking = mask_sublink(pstate, expr, policy_ids, result, rtable, can_mask); break; case T_FuncExpr: is_masking = mask_func(pstate, expr, policy_ids, result, rtable, can_mask); break; case T_Var: is_masking = handle_masking_node(pstate, expr, policy_ids, result, rtable, can_mask); break; case T_RelabelType: { RelabelType *relabel = (RelabelType *) expr; is_masking = mask_expr_node(pstate, (Expr *&)relabel->arg, policy_ids, result, rtable, can_mask); break; } case T_CoerceViaIO: { CoerceViaIO *coerce = (CoerceViaIO *) expr; is_masking = mask_expr_node(pstate, (Expr *&)coerce->arg, policy_ids, result, rtable, false); break; } case T_Aggref: { Aggref *agg = (Aggref *) expr; if (agg->args != NIL && list_length(agg->args) > 0) { mask_list_parameters(&(agg->args), pstate, &is_masking, policy_ids, result, rtable, can_mask); } break; } case T_OpExpr: { OpExpr *opexpr = (OpExpr *) expr; if (opexpr->args != NIL && list_length(opexpr->args) > 0) { mask_list_parameters(&(opexpr->args), pstate, &is_masking, policy_ids, result, rtable, can_mask); } break; } case T_CaseExpr: { CaseExpr *caseexpr = (CaseExpr *) expr; if (caseexpr->args != NIL && list_length(caseexpr->args) > 0) { mask_list_parameters(&(caseexpr->args), pstate, &is_masking, policy_ids, result, rtable, can_mask); } bool res = mask_expr_node(pstate, (Expr *&)caseexpr->defresult, policy_ids, result, rtable, can_mask); is_masking = is_masking || res; } break; case T_CaseWhen: { CaseWhen *whenexpr = (CaseWhen *) expr; if (whenexpr->expr != NULL) { is_masking = mask_expr_node(pstate, (Expr *&)whenexpr->expr, policy_ids, result, rtable, can_mask); } bool res = mask_expr_node(pstate, (Expr *&)whenexpr->result, policy_ids, result, rtable, can_mask); is_masking = is_masking || res; } break; default: break; } return is_masking; } bool parser_target_entry(ParseState *pstate, TargetEntry *&old_tle, const policy_set *policy_ids, masking_result *result, List* rtable, bool can_mask) { Node* src_expr = (Node*)old_tle->expr; bool is_masking = false; switch (nodeTag(src_expr)) { case T_SubLink: { SubLink *sublink = (SubLink *) src_expr; Query *query = (Query *) sublink->subselect; ListCell* temp = NULL; foreach (temp, query->targetList) { TargetEntry *old_tle = (TargetEntry *) lfirst(temp); parser_target_entry(pstate, old_tle, policy_ids, result, query->rtable, can_mask); } } break; case T_Var: { is_masking = handle_masking_node(pstate, (Expr *&)old_tle->expr, policy_ids, result, rtable, can_mask); if (is_masking) { old_tle->resorigtbl = 0; old_tle->resorigcol = 0; } } break; case T_Aggref: case T_OpExpr: case T_RelabelType: case T_FuncExpr: case T_CoerceViaIO: case T_CaseExpr: { if (mask_expr_node(pstate, (Expr *&)old_tle->expr, policy_ids, result, rtable, can_mask)) { old_tle->resorigtbl = 0; old_tle->resorigcol = 0; is_masking = true; } } break; default: break; } return is_masking; } void free_masked_cursor_stmts() { if (masked_cursor_stmts != NULL) { delete masked_cursor_stmts; masked_cursor_stmts = NULL; } } void free_masked_prepared_stmts() { if (masked_prepared_stmts) { delete masked_prepared_stmts; masked_prepared_stmts = NULL; } } void close_cursor_stmt_as_masked(const char* name) { if (masked_cursor_stmts == NULL) { return; } masked_cursor_stmts->erase(name); if (masked_cursor_stmts->empty() || (strcasecmp(name, "all") == 0)) { delete masked_cursor_stmts; masked_cursor_stmts = NULL; } } void unprepare_stmt_as_masked(const char* name) { unprepare_stmt(name); if (!masked_prepared_stmts) { return; } masked_prepared_stmts->erase(name); if (masked_prepared_stmts->empty() || !strcasecmp(name, "all")) { delete masked_prepared_stmts; masked_prepared_stmts = NULL; } } void set_prepare_stmt_as_masked(const char* name, const masking_result *result) { if (!masked_prepared_stmts) { masked_prepared_stmts = new StrMap; } (*masked_prepared_stmts)[name] = (*result); } void set_cursor_stmt_as_masked(const char* name, const masking_result *result) { if (!masked_cursor_stmts) { masked_cursor_stmts = new StrMap; } (*masked_cursor_stmts)[name] = (*result); } template< class T> static inline void flush_stmt_masking_result(const char* name, T* stmts) { if (stmts) { StrMap::const_iterator it = stmts->find(name); if (it != stmts->end()) { flush_masking_result(it->second); } } } void flush_cursor_stmt_masking_result(const char* name) { flush_stmt_masking_result(name, masked_cursor_stmts); } void flush_prepare_stmt_masking_result(const char* name) { flush_stmt_masking_result(name, masked_prepared_stmts); } bool process_union_masking(Node *union_node, ParseState *pstate, const Query *query, const policy_set *policy_ids, bool audit_exist) { if (union_node == NULL) { return false; } switch (nodeTag(union_node)) { /* For each union, we get its query recursively for masking until it doesn't have any union query */ case T_SetOperationStmt: { SetOperationStmt *stmt = (SetOperationStmt *)union_node; if (stmt->op != SETOP_UNION) { return false; } process_union_masking((Node *)(stmt->larg), pstate, query, policy_ids, audit_exist); process_union_masking((Node *)(stmt->rarg), pstate, query, policy_ids, audit_exist); } break; case T_RangeTblRef: { RangeTblRef *ref = (RangeTblRef *)union_node; if (ref->rtindex <= 0 || ref->rtindex > list_length(query->rtable)) { return false; } Query *mostQuery = rt_fetch(ref->rtindex, query->rtable)->subquery; process_masking(pstate, mostQuery, policy_ids, audit_exist); } break; default: break; } return true; } /* * Main entrance for masking * Identify components in query tree that need to do masking. * This function will find all parts which need masking of select query, * mainly includes CTE / setOperation / normal select columns. */ void process_masking(ParseState *pstate, Query *query, const policy_set *policy_ids, bool audit_exist) { if (query == NULL) { return; } /* set-operation tree UNION query */ if (!process_union_masking(query->setOperations, pstate, query, policy_ids, audit_exist)) { ListCell *lc = NULL; /* For each Cte, we get its query recursively for masking, and then handle this query in normal way */ if (query->cteList != NIL) { foreach(lc, query->cteList) { CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); Query *cte_query = (Query *)cte->ctequery; process_masking(pstate, cte_query, policy_ids, audit_exist); } } /* find subquery and process each subquery node */ if (query->rtable != NULL) { foreach(lc, query->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); Query *subquery = (Query *)rte->subquery; process_masking(pstate, subquery, policy_ids, audit_exist); } } select_PostParseAnalyze(pstate, query, policy_ids, audit_exist); } } void select_PostParseAnalyze(ParseState *pstate, Query *&query, const policy_set *policy_ids, bool audit_exist) { Assert(query != NULL); List *targetList = NIL; targetList = (query->targetList != NIL) ? query->targetList : pstate->p_target_list; handle_masking(targetList, pstate, policy_ids, query->rtable, query->utilityStmt); /* deal with function type label */ load_function_label(query, audit_exist); } /* * Do masking for given target list * this function will parse each RTE of the list * and then will check wether each node need to do mask. */ bool handle_masking(List *targetList, ParseState *pstate, const policy_set *policy_ids, List *rtable, Node *utilityNode) { if (targetList == NIL || policy_ids->empty()) { return false; } ListCell *temp = NULL; masking_result masking_result; foreach (temp, targetList) { TargetEntry *old_tle = (TargetEntry *)lfirst(temp); /* Shuffle masking columns can only select directly with out other operations */ parser_target_entry(pstate, old_tle, policy_ids, &masking_result, rtable, true); } if (masking_result.size() == 0) { return false; } if (strlen(t_thrd.security_policy_cxt.prepare_stmt_name) > 0) { /* prepare statement was masked */ set_prepare_stmt_as_masked(t_thrd.security_policy_cxt.prepare_stmt_name, &masking_result); /* save masking event for executing case */ } else if (utilityNode != NULL) { switch (nodeTag(utilityNode)) { case T_DeclareCursorStmt: { DeclareCursorStmt *stmt = (DeclareCursorStmt *)utilityNode; /* save masking event for fetching case */ set_cursor_stmt_as_masked(stmt->portalname, &masking_result); } break; default: flush_masking_result(&masking_result); /* invoke masking event */ } } else { flush_masking_result(&masking_result); /* invoke masking event */ } return true; }