aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/index/genam.c60
-rw-r--r--src/backend/access/nbtree/nbtinsert.c10
-rw-r--r--src/backend/commands/copy.c6
-rw-r--r--src/backend/commands/trigger.c6
-rw-r--r--src/backend/executor/execMain.c9
-rw-r--r--src/backend/executor/execUtils.c12
-rw-r--r--src/backend/utils/adt/ri_triggers.c85
-rw-r--r--src/backend/utils/sort/tuplesort.c9
-rw-r--r--src/test/regress/expected/privileges.out28
-rw-r--r--src/test/regress/sql/privileges.sql25
10 files changed, 215 insertions, 35 deletions
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 915dad78d43..b084584f6f1 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,9 +25,11 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
+#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/syscache.h"
#include "utils/tqual.h"
@@ -150,6 +152,11 @@ IndexScanEnd(IndexScanDesc scan)
* form "(key_name, ...)=(key_value, ...)". This is currently used
* for building unique-constraint and exclusion-constraint error messages.
*
+ * Note that if the user does not have permissions to view all of the
+ * columns involved then a NULL is returned. Returning a partial key seems
+ * unlikely to be useful and we have no way to know which of the columns the
+ * user provided.
+ *
* The passed-in values/nulls arrays are the "raw" input to the index AM,
* e.g. results of FormIndexDatum --- this is not necessarily what is stored
* in the index, but it's what the user perceives to be stored.
@@ -159,13 +166,62 @@ BuildIndexValueDescription(Relation indexRelation,
Datum *values, bool *isnull)
{
StringInfoData buf;
+ Form_pg_index idxrec;
+ HeapTuple ht_idx;
int natts = indexRelation->rd_rel->relnatts;
int i;
+ int keyno;
+ Oid indexrelid = RelationGetRelid(indexRelation);
+ Oid indrelid;
+ AclResult aclresult;
+
+ /*
+ * Check permissions- if the user does not have access to view all of the
+ * key columns then return NULL to avoid leaking data.
+ *
+ * First we need to check table-level SELECT access and then, if
+ * there is no access there, check column-level permissions.
+ */
+
+ /*
+ * Fetch the pg_index tuple by the Oid of the index
+ */
+ ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
+ if (!HeapTupleIsValid(ht_idx))
+ elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+
+ indrelid = idxrec->indrelid;
+ Assert(indexrelid == idxrec->indexrelid);
+
+ /* Table-level SELECT is enough, if the user has it */
+ aclresult = pg_class_aclcheck(indrelid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ {
+ /*
+ * No table-level access, so step through the columns in the
+ * index and make sure the user has SELECT rights on all of them.
+ */
+ for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ {
+ AttrNumber attnum = idxrec->indkey.values[keyno];
+
+ aclresult = pg_attribute_aclcheck(indrelid, attnum, GetUserId(),
+ ACL_SELECT);
+
+ if (aclresult != ACLCHECK_OK)
+ {
+ /* No access, so clean up and return */
+ ReleaseSysCache(ht_idx);
+ return NULL;
+ }
+ }
+ }
+ ReleaseSysCache(ht_idx);
initStringInfo(&buf);
appendStringInfo(&buf, "(%s)=(",
- pg_get_indexdef_columns(RelationGetRelid(indexRelation),
- true));
+ pg_get_indexdef_columns(indexrelid, true));
for (i = 0; i < natts; i++)
{
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 29e04350c7b..b288659d98d 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -385,16 +385,20 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
{
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ char *key_desc;
index_deform_tuple(itup, RelationGetDescr(rel),
values, isnull);
+
+ key_desc = BuildIndexValueDescription(rel, values,
+ isnull);
+
ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("duplicate key value violates unique constraint \"%s\"",
RelationGetRelationName(rel)),
- errdetail("Key %s already exists.",
- BuildIndexValueDescription(rel,
- values, isnull))));
+ key_desc ? errdetail("Key %s already exists.",
+ key_desc) : 0));
}
}
else if (all_dead)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e91cc7e02c5..c7537687640 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -148,6 +148,7 @@ typedef struct CopyStateData
Oid *typioparams; /* array of element types for in_functions */
int *defmap; /* array of default att numbers */
ExprState **defexprs; /* array of default att expressions */
+ List *range_table;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -737,6 +738,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
bool pipe = (stmt->filename == NULL);
Relation rel;
uint64 processed;
+ RangeTblEntry *rte;
/* Disallow file COPY except to superusers. */
if (!pipe && !superuser())
@@ -750,7 +752,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
{
TupleDesc tupDesc;
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
- RangeTblEntry *rte;
List *attnums;
ListCell *cur;
@@ -795,6 +796,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
cstate = BeginCopyFrom(rel, stmt->filename,
stmt->attlist, stmt->options);
+ cstate->range_table = list_make1(rte);
processed = CopyFrom(cstate); /* copy from file to database */
EndCopyFrom(cstate);
}
@@ -802,6 +804,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
{
cstate = BeginCopyTo(rel, stmt->query, queryString, stmt->filename,
stmt->attlist, stmt->options);
+ cstate->range_table = list_make1(rte);
processed = DoCopyTo(cstate); /* copy from database to file */
EndCopyTo(cstate);
}
@@ -1933,6 +1936,7 @@ CopyFrom(CopyState cstate)
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
+ estate->es_range_table = cstate->range_table;
/* Set up a tuple slot too */
myslot = ExecInitExtraTupleSlot(estate);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index bd5dee4286a..bbb83911a9a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -60,6 +60,12 @@
int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;
+/*
+ * Note that this macro also exists in executor/execMain.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)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index cd0def4aab9..8ff6ede7714 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -95,6 +95,15 @@ static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
static void intorel_shutdown(DestReceiver *self);
static void intorel_destroy(DestReceiver *self);
+/*
+ * 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 */
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 23668ef4984..22b48d72bd6 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1307,15 +1307,19 @@ retry:
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
- errdetail("Key %s conflicts with key %s.",
- error_new, error_existing)));
+ error_new && error_existing ?
+ errdetail("Key %s conflicts with key %s.",
+ error_new, error_existing) :
+ errdetail("Key conflicts exist.")));
else
ereport(ERROR,
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("conflicting key value violates exclusion constraint \"%s\"",
RelationGetRelationName(index)),
- errdetail("Key %s conflicts with existing key %s.",
- error_new, error_existing)));
+ error_new && error_existing ?
+ errdetail("Key %s conflicts with existing key %s.",
+ error_new, error_existing) :
+ errdetail("Key conflicts with existing key.")));
}
index_endscan(index_scan);
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 32621e0fc26..0c1d67e4934 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3495,6 +3495,9 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
bool onfk;
int idx,
key_idx;
+ Oid rel_oid;
+ AclResult aclresult;
+ bool has_perm = true;
if (spi_err)
ereport(ERROR,
@@ -3513,12 +3516,14 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
if (onfk)
{
key_idx = RI_KEYPAIR_FK_IDX;
+ rel_oid = fk_rel->rd_id;
if (tupdesc == NULL)
tupdesc = fk_rel->rd_att;
}
else
{
key_idx = RI_KEYPAIR_PK_IDX;
+ rel_oid = pk_rel->rd_id;
if (tupdesc == NULL)
tupdesc = pk_rel->rd_att;
}
@@ -3538,45 +3543,81 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
RelationGetRelationName(pk_rel))));
}
- /* Get printable versions of the keys involved */
- initStringInfo(&key_names);
- initStringInfo(&key_values);
- for (idx = 0; idx < qkey->nkeypairs; idx++)
+ /*
+ * Check permissions- if the user does not have access to view the data in
+ * any of the key columns then we don't include the errdetail() below.
+ *
+ * Check table-level permissions first and, failing that, column-level
+ * privileges.
+ */
+ aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
{
- int fnum = qkey->keypair[idx][key_idx];
- char *name,
- *val;
-
- name = SPI_fname(tupdesc, fnum);
- val = SPI_getvalue(violator, tupdesc, fnum);
- if (!val)
- val = "null";
+ /* Try for column-level permissions */
+ for (idx = 0; idx < qkey->nkeypairs; idx++)
+ {
+ aclresult = pg_attribute_aclcheck(rel_oid, qkey->keypair[idx][key_idx],
+ GetUserId(),
+ ACL_SELECT);
+ /* No access to the key */
+ if (aclresult != ACLCHECK_OK)
+ {
+ has_perm = false;
+ break;
+ }
+ }
+ }
- if (idx > 0)
+ if (has_perm)
+ {
+ /* Get printable versions of the keys involved */
+ initStringInfo(&key_names);
+ initStringInfo(&key_values);
+ for (idx = 0; idx < qkey->nkeypairs; idx++)
{
- appendStringInfoString(&key_names, ", ");
- appendStringInfoString(&key_values, ", ");
+ int fnum = qkey->keypair[idx][key_idx];
+ char *name,
+ *val;
+
+ name = SPI_fname(tupdesc, fnum);
+ val = SPI_getvalue(violator, tupdesc, fnum);
+ if (!val)
+ val = "null";
+
+ if (idx > 0)
+ {
+ appendStringInfoString(&key_names, ", ");
+ appendStringInfoString(&key_values, ", ");
+ }
+ appendStringInfoString(&key_names, name);
+ appendStringInfoString(&key_values, val);
}
- appendStringInfoString(&key_names, name);
- appendStringInfoString(&key_values, val);
}
if (onfk)
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
- RelationGetRelationName(fk_rel), constrname),
- errdetail("Key (%s)=(%s) is not present in table \"%s\".",
- key_names.data, key_values.data,
- RelationGetRelationName(pk_rel))));
+ RelationGetRelationName(fk_rel),
+ constrname),
+ has_perm ?
+ errdetail("Key (%s)=(%s) is not present in table \"%s\".",
+ key_names.data, key_values.data,
+ RelationGetRelationName(pk_rel)) :
+ errdetail("Key is not present in table \"%s\".",
+ RelationGetRelationName(pk_rel))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"",
RelationGetRelationName(pk_rel),
- constrname, RelationGetRelationName(fk_rel)),
+ constrname,
+ RelationGetRelationName(fk_rel)),
+ has_perm ?
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
+ RelationGetRelationName(fk_rel)) :
+ errdetail("Key is still referenced from table \"%s\".",
RelationGetRelationName(fk_rel))));
}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 90ef0abca62..b6b06d56856 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -3124,15 +3124,18 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b,
{
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ char *key_desc;
index_deform_tuple(tuple1, tupDes, values, isnull);
+
+ key_desc = BuildIndexValueDescription(state->indexRel, values, isnull);
+
ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("could not create unique index \"%s\"",
RelationGetRelationName(state->indexRel)),
- errdetail("Key %s is duplicated.",
- BuildIndexValueDescription(state->indexRel,
- values, isnull))));
+ key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+ errdetail("Duplicate keys exist.")));
}
/*
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index b3e913de354..dfeb5bde12b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -362,6 +362,34 @@ SELECT atest6 FROM atest6; -- ok
(0 rows)
COPY atest6 TO stdout; -- ok
+-- check error reporting with column privs
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
+GRANT SELECT (c1) ON t1 TO regressuser2;
+GRANT INSERT (c1, c2, c3) ON t1 TO regressuser2;
+GRANT UPDATE (c1, c2, c3) ON t1 TO regressuser2;
+-- seed data
+INSERT INTO t1 VALUES (1, 1, 1);
+INSERT INTO t1 VALUES (1, 2, 1);
+INSERT INTO t1 VALUES (2, 1, 2);
+INSERT INTO t1 VALUES (2, 2, 2);
+INSERT INTO t1 VALUES (3, 1, 3);
+SET SESSION AUTHORIZATION regressuser2;
+INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown
+ERROR: duplicate key value violates unique constraint "t1_pkey"
+UPDATE t1 SET c2 = 1; -- fail, but row not shown
+ERROR: duplicate key value violates unique constraint "t1_pkey"
+INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted
+ERROR: null value in column "c1" violates not-null constraint
+INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT
+ERROR: null value in column "c1" violates not-null constraint
+INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT
+ERROR: null value in column "c2" violates not-null constraint
+UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified
+ERROR: new row for relation "t1" violates check constraint "t1_c3_check"
+SET SESSION AUTHORIZATION regressuser1;
+DROP TABLE t1;
-- test column-level privileges when involved with DELETE
SET SESSION AUTHORIZATION regressuser1;
ALTER TABLE atest6 ADD COLUMN three integer;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index cc9d8577902..f3cc04bd1d3 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -238,6 +238,31 @@ UPDATE atest5 SET one = 1; -- fail
SELECT atest6 FROM atest6; -- ok
COPY atest6 TO stdout; -- ok
+-- check error reporting with column privs
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
+GRANT SELECT (c1) ON t1 TO regressuser2;
+GRANT INSERT (c1, c2, c3) ON t1 TO regressuser2;
+GRANT UPDATE (c1, c2, c3) ON t1 TO regressuser2;
+
+-- seed data
+INSERT INTO t1 VALUES (1, 1, 1);
+INSERT INTO t1 VALUES (1, 2, 1);
+INSERT INTO t1 VALUES (2, 1, 2);
+INSERT INTO t1 VALUES (2, 2, 2);
+INSERT INTO t1 VALUES (3, 1, 3);
+
+SET SESSION AUTHORIZATION regressuser2;
+INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown
+UPDATE t1 SET c2 = 1; -- fail, but row not shown
+INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted
+INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT
+INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT
+UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified
+
+SET SESSION AUTHORIZATION regressuser1;
+DROP TABLE t1;
+
-- test column-level privileges when involved with DELETE
SET SESSION AUTHORIZATION regressuser1;
ALTER TABLE atest6 ADD COLUMN three integer;