mirror of
https://github.com/postgres/postgres.git
synced 2025-07-05 07:21:24 +03:00
Always require SELECT permission for ON CONFLICT DO UPDATE.
The update path of an INSERT ... ON CONFLICT DO UPDATE requires SELECT permission on the columns of the arbiter index, but it failed to check for that in the case of an arbiter specified by constraint name. In addition, for a table with row level security enabled, it failed to check updated rows against the table's SELECT policies when the update path was taken (regardless of how the arbiter index was specified). Backpatch to 9.5 where ON CONFLICT DO UPDATE and RLS were introduced. Security: CVE-2017-15099
This commit is contained in:
@ -805,6 +805,104 @@ get_relation_constraint_oid(Oid relid, const char *conname, bool missing_ok)
|
||||
return conOid;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_relation_constraint_attnos
|
||||
* Find a constraint on the specified relation with the specified name
|
||||
* and return the constrained columns.
|
||||
*
|
||||
* Returns a Bitmapset of the column attnos of the constrained columns, with
|
||||
* attnos being offset by FirstLowInvalidHeapAttributeNumber so that system
|
||||
* columns can be represented.
|
||||
*
|
||||
* *constraintOid is set to the OID of the constraint, or InvalidOid on
|
||||
* failure.
|
||||
*/
|
||||
Bitmapset *
|
||||
get_relation_constraint_attnos(Oid relid, const char *conname,
|
||||
bool missing_ok, Oid *constraintOid)
|
||||
{
|
||||
Bitmapset *conattnos = NULL;
|
||||
Relation pg_constraint;
|
||||
HeapTuple tuple;
|
||||
SysScanDesc scan;
|
||||
ScanKeyData skey[1];
|
||||
|
||||
/* Set *constraintOid, to avoid complaints about uninitialized vars */
|
||||
*constraintOid = InvalidOid;
|
||||
|
||||
/*
|
||||
* Fetch the constraint tuple from pg_constraint. There may be more than
|
||||
* one match, because constraints are not required to have unique names;
|
||||
* if so, error out.
|
||||
*/
|
||||
pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
|
||||
|
||||
ScanKeyInit(&skey[0],
|
||||
Anum_pg_constraint_conrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(relid));
|
||||
|
||||
scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
|
||||
NULL, 1, skey);
|
||||
|
||||
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
|
||||
{
|
||||
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
|
||||
Datum adatum;
|
||||
bool isNull;
|
||||
ArrayType *arr;
|
||||
int16 *attnums;
|
||||
int numcols;
|
||||
int i;
|
||||
|
||||
/* Check the constraint name */
|
||||
if (strcmp(NameStr(con->conname), conname) != 0)
|
||||
continue;
|
||||
if (OidIsValid(*constraintOid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
||||
errmsg("table \"%s\" has multiple constraints named \"%s\"",
|
||||
get_rel_name(relid), conname)));
|
||||
|
||||
*constraintOid = HeapTupleGetOid(tuple);
|
||||
|
||||
/* Extract the conkey array, ie, attnums of constrained columns */
|
||||
adatum = heap_getattr(tuple, Anum_pg_constraint_conkey,
|
||||
RelationGetDescr(pg_constraint), &isNull);
|
||||
if (isNull)
|
||||
continue; /* no constrained columns */
|
||||
|
||||
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
||||
numcols = ARR_DIMS(arr)[0];
|
||||
if (ARR_NDIM(arr) != 1 ||
|
||||
numcols < 0 ||
|
||||
ARR_HASNULL(arr) ||
|
||||
ARR_ELEMTYPE(arr) != INT2OID)
|
||||
elog(ERROR, "conkey is not a 1-D smallint array");
|
||||
attnums = (int16 *) ARR_DATA_PTR(arr);
|
||||
|
||||
/* Construct the result value */
|
||||
for (i = 0; i < numcols; i++)
|
||||
{
|
||||
conattnos = bms_add_member(conattnos,
|
||||
attnums[i] - FirstLowInvalidHeapAttributeNumber);
|
||||
}
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
|
||||
/* If no such constraint exists, complain */
|
||||
if (!OidIsValid(*constraintOid) && !missing_ok)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("constraint \"%s\" for table \"%s\" does not exist",
|
||||
conname, get_rel_name(relid))));
|
||||
|
||||
heap_close(pg_constraint, AccessShareLock);
|
||||
|
||||
return conattnos;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_domain_constraint_oid
|
||||
* Find a constraint on the specified domain with the specified name.
|
||||
|
@ -3164,9 +3164,26 @@ transformOnConflictArbiter(ParseState *pstate,
|
||||
|
||||
pstate->p_namespace = save_namespace;
|
||||
|
||||
/*
|
||||
* If the arbiter is specified by constraint name, get the constraint
|
||||
* OID and mark the constrained columns as requiring SELECT privilege,
|
||||
* in the same way as would have happened if the arbiter had been
|
||||
* specified by explicit reference to the constraint's index columns.
|
||||
*/
|
||||
if (infer->conname)
|
||||
*constraint = get_relation_constraint_oid(RelationGetRelid(pstate->p_target_relation),
|
||||
infer->conname, false);
|
||||
{
|
||||
Oid relid = RelationGetRelid(pstate->p_target_relation);
|
||||
RangeTblEntry *rte = pstate->p_target_rangetblentry;
|
||||
Bitmapset *conattnos;
|
||||
|
||||
conattnos = get_relation_constraint_attnos(relid, infer->conname,
|
||||
false, constraint);
|
||||
|
||||
/* Make sure the rel as a whole is marked for SELECT access */
|
||||
rte->requiredPerms |= ACL_SELECT;
|
||||
/* Mark the constrained columns as requiring SELECT access */
|
||||
rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -310,6 +310,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
|
||||
{
|
||||
List *conflict_permissive_policies;
|
||||
List *conflict_restrictive_policies;
|
||||
List *conflict_select_permissive_policies = NIL;
|
||||
List *conflict_select_restrictive_policies = NIL;
|
||||
|
||||
/* Get the policies that apply to the auxiliary UPDATE */
|
||||
get_policies_for_relation(rel, CMD_UPDATE, user_id,
|
||||
@ -339,9 +341,6 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
|
||||
*/
|
||||
if (rte->requiredPerms & ACL_SELECT)
|
||||
{
|
||||
List *conflict_select_permissive_policies = NIL;
|
||||
List *conflict_select_restrictive_policies = NIL;
|
||||
|
||||
get_policies_for_relation(rel, CMD_SELECT, user_id,
|
||||
&conflict_select_permissive_policies,
|
||||
&conflict_select_restrictive_policies);
|
||||
@ -362,6 +361,21 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
|
||||
withCheckOptions,
|
||||
hasSubLinks,
|
||||
false);
|
||||
|
||||
/*
|
||||
* Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure
|
||||
* that the final updated row is visible when taking the UPDATE
|
||||
* path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
|
||||
* are required for this relation.
|
||||
*/
|
||||
if (rte->requiredPerms & ACL_SELECT)
|
||||
add_with_check_options(rel, rt_index,
|
||||
WCO_RLS_UPDATE_CHECK,
|
||||
conflict_select_permissive_policies,
|
||||
conflict_select_restrictive_policies,
|
||||
withCheckOptions,
|
||||
hasSubLinks,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user