diff options
author | Stephen Frost <sfrost@snowman.net> | 2015-01-12 17:04:11 -0500 |
---|---|---|
committer | Stephen Frost <sfrost@snowman.net> | 2015-01-28 12:31:30 -0500 |
commit | 804b6b6db4dcfc590a468e7be390738f9f7755fb (patch) | |
tree | 334d58ccb1a76d04c9bb61309879d6c8e6a8f7e4 /src/backend/executor/execMain.c | |
parent | acc2b1e843ae04e97025880f33a82f326ab12d59 (diff) | |
download | postgresql-804b6b6db4dcfc590a468e7be390738f9f7755fb.tar.gz postgresql-804b6b6db4dcfc590a468e7be390738f9f7755fb.zip |
Fix column-privilege leak in error-message paths
While building error messages to return to the user,
BuildIndexValueDescription, ExecBuildSlotValueDescription and
ri_ReportViolation would happily include the entire key or entire row in
the result returned to the user, even if the user didn't have access to
view all of the columns being included.
Instead, include only those columns which the user is providing or which
the user has select rights on. If the user does not have any rights
to view the table or any of the columns involved then no detail is
provided and a NULL value is returned from BuildIndexValueDescription
and ExecBuildSlotValueDescription. Note that, for key cases, the user
must have access to all of the columns for the key to be shown; a
partial key will not be returned.
Further, in master only, do not return any data for cases where row
security is enabled on the relation and row security should be applied
for the user. This required a bit of refactoring and moving of things
around related to RLS- note the addition of utils/misc/rls.c.
Back-patch all the way, as column-level privileges are now in all
supported versions.
This has been assigned CVE-2014-8161, but since the issue and the patch
have already been publicized on pgsql-hackers, there's no point in trying
to hide this commit.
Diffstat (limited to 'src/backend/executor/execMain.c')
-rw-r--r-- | src/backend/executor/execMain.c | 193 |
1 files changed, 156 insertions, 37 deletions
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index b9f21c560f9..20b3188dfdc 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -56,6 +56,7 @@ #include "utils/acl.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/tqual.h" @@ -82,12 +83,23 @@ static void ExecutePlan(EState *estate, PlanState *planstate, DestReceiver *dest); static bool ExecCheckRTEPerms(RangeTblEntry *rte); static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt); -static char *ExecBuildSlotValueDescription(TupleTableSlot *slot, +static char *ExecBuildSlotValueDescription(Oid reloid, + TupleTableSlot *slot, TupleDesc tupdesc, + Bitmapset *modifiedCols, int maxfieldlen); static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree); +/* + * Note that this macro also exists in commands/trigger.c. There does not + * appear to be any good header to put it into, given the structures that + * it uses, so we let them be duplicated. Be sure to update both if one needs + * to be changed, however. + */ +#define GetModifiedColumns(relinfo, estate) \ + (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols) + /* end of local decls */ @@ -1607,15 +1619,24 @@ ExecConstraints(ResultRelInfo *resultRelInfo, { if (tupdesc->attrs[attrChk - 1]->attnotnull && slot_attisnull(slot, attrChk)) + { + char *val_desc; + Bitmapset *modifiedCols; + + modifiedCols = GetModifiedColumns(resultRelInfo, estate); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupdesc, + modifiedCols, + 64); + ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("null value in column \"%s\" violates not-null constraint", NameStr(tupdesc->attrs[attrChk - 1]->attname)), - errdetail("Failing row contains %s.", - ExecBuildSlotValueDescription(slot, - tupdesc, - 64)), + val_desc ? errdetail("Failing row contains %s.", val_desc) : 0, errtablecol(rel, attrChk))); + } } } @@ -1624,15 +1645,23 @@ ExecConstraints(ResultRelInfo *resultRelInfo, const char *failed; if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL) + { + char *val_desc; + Bitmapset *modifiedCols; + + modifiedCols = GetModifiedColumns(resultRelInfo, estate); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupdesc, + modifiedCols, + 64); ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("new row for relation \"%s\" violates check constraint \"%s\"", RelationGetRelationName(rel), failed), - errdetail("Failing row contains %s.", - ExecBuildSlotValueDescription(slot, - tupdesc, - 64)), + val_desc ? errdetail("Failing row contains %s.", val_desc) : 0, errtableconstraint(rel, failed))); + } } } @@ -1643,6 +1672,8 @@ void ExecWithCheckOptions(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) { + Relation rel = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(rel); ExprContext *econtext; ListCell *l1, *l2; @@ -1673,14 +1704,24 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo, * case (the opposite of what we do above for CHECK constraints). */ if (!ExecQual((List *) wcoExpr, econtext, false)) + { + char *val_desc; + Bitmapset *modifiedCols; + + modifiedCols = GetModifiedColumns(resultRelInfo, estate); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupdesc, + modifiedCols, + 64); + ereport(ERROR, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), errmsg("new row violates WITH CHECK OPTION for \"%s\"", wco->viewname), - errdetail("Failing row contains %s.", - ExecBuildSlotValueDescription(slot, - RelationGetDescr(resultRelInfo->ri_RelationDesc), - 64)))); + val_desc ? errdetail("Failing row contains %s.", val_desc) : + 0)); + } } } @@ -1696,25 +1737,64 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo, * dropped columns. We used to use the slot's tuple descriptor to decode the * data, but the slot's descriptor doesn't identify dropped columns, so we * now need to be passed the relation's descriptor. + * + * Note that, like BuildIndexValueDescription, if the user does not have + * permission to view any of the columns involved, a NULL is returned. Unlike + * BuildIndexValueDescription, if the user has access to view a subset of the + * column involved, that subset will be returned with a key identifying which + * columns they are. */ static char * -ExecBuildSlotValueDescription(TupleTableSlot *slot, +ExecBuildSlotValueDescription(Oid reloid, + TupleTableSlot *slot, TupleDesc tupdesc, + Bitmapset *modifiedCols, int maxfieldlen) { StringInfoData buf; + StringInfoData collist; bool write_comma = false; + bool write_comma_collist = false; int i; + AclResult aclresult; + bool table_perm = false; + bool any_perm = false; - /* Make sure the tuple is fully deconstructed */ - slot_getallattrs(slot); + /* + * Check if RLS is enabled and should be active for the relation; if so, + * then don't return anything. Otherwise, go through normal permission + * checks. + */ + if (check_enable_rls(reloid, GetUserId(), true) == RLS_ENABLED) + return NULL; initStringInfo(&buf); appendStringInfoChar(&buf, '('); + /* + * Check if the user has permissions to see the row. Table-level SELECT + * allows access to all columns. If the user does not have table-level + * SELECT then we check each column and include those the user has SELECT + * rights on. Additionally, we always include columns the user provided + * data for. + */ + aclresult = pg_class_aclcheck(reloid, GetUserId(), ACL_SELECT); + if (aclresult != ACLCHECK_OK) + { + /* Set up the buffer for the column list */ + initStringInfo(&collist); + appendStringInfoChar(&collist, '('); + } + else + table_perm = any_perm = true; + + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(slot); + for (i = 0; i < tupdesc->natts; i++) { + bool column_perm = false; char *val; int vallen; @@ -1722,37 +1802,76 @@ ExecBuildSlotValueDescription(TupleTableSlot *slot, if (tupdesc->attrs[i]->attisdropped) continue; - if (slot->tts_isnull[i]) - val = "null"; - else + if (!table_perm) { - Oid foutoid; - bool typisvarlena; + /* + * No table-level SELECT, so need to make sure they either have + * SELECT rights on the column or that they have provided the + * data for the column. If not, omit this column from the error + * message. + */ + aclresult = pg_attribute_aclcheck(reloid, tupdesc->attrs[i]->attnum, + GetUserId(), ACL_SELECT); + if (bms_is_member(tupdesc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber, + modifiedCols) || aclresult == ACLCHECK_OK) + { + column_perm = any_perm = true; - getTypeOutputInfo(tupdesc->attrs[i]->atttypid, - &foutoid, &typisvarlena); - val = OidOutputFunctionCall(foutoid, slot->tts_values[i]); - } + if (write_comma_collist) + appendStringInfoString(&collist, ", "); + else + write_comma_collist = true; - if (write_comma) - appendStringInfoString(&buf, ", "); - else - write_comma = true; + appendStringInfoString(&collist, NameStr(tupdesc->attrs[i]->attname)); + } + } - /* truncate if needed */ - vallen = strlen(val); - if (vallen <= maxfieldlen) - appendStringInfoString(&buf, val); - else + if (table_perm || column_perm) { - vallen = pg_mbcliplen(val, vallen, maxfieldlen); - appendBinaryStringInfo(&buf, val, vallen); - appendStringInfoString(&buf, "..."); + if (slot->tts_isnull[i]) + val = "null"; + else + { + Oid foutoid; + bool typisvarlena; + + getTypeOutputInfo(tupdesc->attrs[i]->atttypid, + &foutoid, &typisvarlena); + val = OidOutputFunctionCall(foutoid, slot->tts_values[i]); + } + + if (write_comma) + appendStringInfoString(&buf, ", "); + else + write_comma = true; + + /* truncate if needed */ + vallen = strlen(val); + if (vallen <= maxfieldlen) + appendStringInfoString(&buf, val); + else + { + vallen = pg_mbcliplen(val, vallen, maxfieldlen); + appendBinaryStringInfo(&buf, val, vallen); + appendStringInfoString(&buf, "..."); + } } } + /* If we end up with zero columns being returned, then return NULL. */ + if (!any_perm) + return NULL; + appendStringInfoChar(&buf, ')'); + if (!table_perm) + { + appendStringInfoString(&collist, ") = "); + appendStringInfoString(&collist, buf.data); + + return collist.data; + } + return buf.data; } |