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.
This commit is contained in:
Tom Lane 2010-07-30 17:57:25 +00:00
parent 8e21bf5250
commit 1eef280d61
4 changed files with 115 additions and 84 deletions

View File

@ -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:

View File

@ -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")));
}

View File

@ -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;
}

View File

@ -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 */