From 1eef280d6186782b4adfbd6a9cb97413f7c46b8b Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 30 Jul 2010 17:57:25 +0000 Subject: [PATCH] Improved version of patch to protect pg_get_expr() against misuse: look through join alias Vars to avoid breaking join queries, and move the test to someplace where it will catch more possible ways of calling a function. We still ought to throw away the whole thing in favor of a data-type-based solution, but that's not feasible in the back branches. Completion of back-port of my patch of yesterday. --- src/backend/parser/parse_expr.c | 82 +------------------------ src/backend/parser/parse_func.c | 105 +++++++++++++++++++++++++++++++- src/backend/parser/parse_oper.c | 8 ++- src/include/parser/parse_func.h | 4 +- 4 files changed, 115 insertions(+), 84 deletions(-) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index ff4d43caf0..488235fbb5 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,16 +8,13 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.179.4.4 2010/06/30 18:11:31 heikki Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.179.4.5 2010/07/30 17:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "catalog/catname.h" -#include "catalog/pg_attrdef.h" -#include "catalog/pg_constraint.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "commands/dbcommands.h" @@ -35,7 +32,6 @@ #include "parser/parse_relation.h" #include "parser/parse_type.h" #include "utils/builtins.h" -#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" @@ -439,82 +435,6 @@ transformExpr(ParseState *pstate, Node *expr) fn->agg_star, fn->agg_distinct, false); - - /* - * pg_get_expr() is a system function that exposes the - * expression deparsing functionality in ruleutils.c to users. - * Very handy, but it was later realized that the functions in - * ruleutils.c don't check the input rigorously, assuming it to - * come from system catalogs and to therefore be valid. That - * makes it easy for a user to crash the backend by passing a - * maliciously crafted string representation of an expression - * to pg_get_expr(). - * - * There's a lot of code in ruleutils.c, so it's not feasible - * to add water-proof input checking after the fact. Even if - * we did it once, it would need to be taken into account in - * any future patches too. - * - * Instead, we restrict pg_rule_expr() to only allow input from - * system catalogs instead. This is a hack, but it's the most - * robust and easiest to backpatch way of plugging the - * vulnerability. - * - * This is transparent to the typical usage pattern of - * "pg_get_expr(systemcolumn, ...)", but will break - * "pg_get_expr('foo', ...)", even if 'foo' is a valid - * expression fetched earlier from a system catalog. Hopefully - * there's isn't many clients doing that out there. - */ - if (result && IsA(result, FuncExpr) && !superuser()) - { - FuncExpr *fe = (FuncExpr *) result; - if (fe->funcid == F_PG_GET_EXPR || - fe->funcid == F_PG_GET_EXPR_EXT) - { - Expr *arg = linitial(fe->args); - bool allowed = false; - - /* - * Check that the argument came directly from one of the - * allowed system catalog columns - */ - if (IsA(arg, Var)) - { - Var *var = (Var *) arg; - RangeTblEntry *rte; - - rte = GetRTEByRangeTablePosn(pstate, - var->varno, var->varlevelsup); - - if (rte->relid == get_system_catalog_relid(IndexRelationName)) - { - if (var->varattno == Anum_pg_index_indexprs || - var->varattno == Anum_pg_index_indpred) - allowed = true; - } - else if (rte->relid == get_system_catalog_relid(AttrDefaultRelationName)) - { - if (var->varattno == Anum_pg_attrdef_adbin) - allowed = true; - } - else if (rte->relid == get_system_catalog_relid(ConstraintRelationName)) - { - if (var->varattno == Anum_pg_constraint_conbin) - allowed = true; - } - else if (rte->relid == get_system_catalog_relid(TypeRelationName)) - { - if (var->varattno == Anum_pg_type_typdefaultbin) - allowed = true; - } - } - if (!allowed) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("argument to pg_get_expr() must come from system catalogs"))); - } - } break; } case T_SubLink: diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 2a72a660fa..b463506786 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.175 2004/12/31 22:00:27 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.175.4.1 2010/07/30 17:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,9 +16,12 @@ #include "access/heapam.h" #include "catalog/catname.h" +#include "catalog/pg_attrdef.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "lib/stringinfo.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_agg.h" #include "parser/parse_coerce.h" @@ -255,6 +258,9 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("aggregates may not return sets"))); } + /* Hack to protect pg_get_expr() against misuse */ + check_pg_get_expr_args(pstate, funcid, fargs); + return retval; } @@ -1406,3 +1412,100 @@ LookupFuncNameTypeNames(List *funcname, List *argtypes, bool noError) return LookupFuncName(funcname, argcount, argoids, noError); } + + +/* + * pg_get_expr() is a system function that exposes the expression + * deparsing functionality in ruleutils.c to users. Very handy, but it was + * later realized that the functions in ruleutils.c don't check the input + * rigorously, assuming it to come from system catalogs and to therefore + * be valid. That makes it easy for a user to crash the backend by passing + * a maliciously crafted string representation of an expression to + * pg_get_expr(). + * + * There's a lot of code in ruleutils.c, so it's not feasible to add + * water-proof input checking after the fact. Even if we did it once, it + * would need to be taken into account in any future patches too. + * + * Instead, we restrict pg_rule_expr() to only allow input from system + * catalogs. This is a hack, but it's the most robust and easiest + * to backpatch way of plugging the vulnerability. + * + * This is transparent to the typical usage pattern of + * "pg_get_expr(systemcolumn, ...)", but will break "pg_get_expr('foo', + * ...)", even if 'foo' is a valid expression fetched earlier from a + * system catalog. Hopefully there aren't many clients doing that out there. + */ +void +check_pg_get_expr_args(ParseState *pstate, Oid fnoid, List *args) +{ + bool allowed = false; + Node *arg; + int netlevelsup; + + /* if not being called for pg_get_expr, do nothing */ + if (fnoid != F_PG_GET_EXPR && fnoid != F_PG_GET_EXPR_EXT) + return; + + /* superusers are allowed to call it anyway (dubious) */ + if (superuser()) + return; + + /* + * The first argument must be a Var referencing one of the allowed + * system-catalog columns. It could be a join alias Var, though. + */ + Assert(list_length(args) > 1); + arg = (Node *) linitial(args); + netlevelsup = 0; + +restart: + if (IsA(arg, Var)) + { + Var *var = (Var *) arg; + RangeTblEntry *rte; + + netlevelsup += var->varlevelsup; + rte = GetRTEByRangeTablePosn(pstate, var->varno, netlevelsup); + + if (rte->rtekind == RTE_JOIN) + { + /* Expand join alias reference */ + if (var->varattno > 0 && + var->varattno <= list_length(rte->joinaliasvars)) + { + arg = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1); + goto restart; + } + } + else if (rte->rtekind == RTE_RELATION) + { + if (rte->relid == get_system_catalog_relid(IndexRelationName)) + { + if (var->varattno == Anum_pg_index_indexprs || + var->varattno == Anum_pg_index_indpred) + allowed = true; + } + else if (rte->relid == get_system_catalog_relid(AttrDefaultRelationName)) + { + if (var->varattno == Anum_pg_attrdef_adbin) + allowed = true; + } + else if (rte->relid == get_system_catalog_relid(ConstraintRelationName)) + { + if (var->varattno == Anum_pg_constraint_conbin) + allowed = true; + } + else if (rte->relid == get_system_catalog_relid(TypeRelationName)) + { + if (var->varattno == Anum_pg_type_typdefaultbin) + allowed = true; + } + } + } + + if (!allowed) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("argument to pg_get_expr() must come from system catalogs"))); +} diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c index 4b615542c8..08c25b9f33 100644 --- a/src/backend/parser/parse_oper.c +++ b/src/backend/parser/parse_oper.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.81 2004/12/31 22:00:27 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.81.4.1 2010/07/30 17:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -932,6 +932,9 @@ make_scalar_array_op(ParseState *pstate, List *opname, result->useOr = useOr; result->args = args; + /* Hack to protect pg_get_expr() against misuse */ + check_pg_get_expr_args(pstate, opform->oprcode, args); + ReleaseSysCache(tup); return (Expr *) result; @@ -1005,5 +1008,8 @@ make_op_expr(ParseState *pstate, Operator op, result->opretset = get_func_retset(opform->oprcode); result->args = args; + /* Hack to protect pg_get_expr() against misuse */ + check_pg_get_expr_args(pstate, opform->oprcode, args); + return (Expr *) result; } diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index 5d203e60ca..f45e096e6d 100644 --- a/src/include/parser/parse_func.h +++ b/src/include/parser/parse_func.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.53 2004/12/31 22:03:38 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.53.4.1 2010/07/30 17:57:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -78,4 +78,6 @@ extern Oid LookupFuncName(List *funcname, int nargs, const Oid *argtypes, extern Oid LookupFuncNameTypeNames(List *funcname, List *argtypes, bool noError); +extern void check_pg_get_expr_args(ParseState *pstate, Oid fnoid, List *args); + #endif /* PARSE_FUNC_H */