diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index cdd5006..e4c9a4f 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -664,6 +664,39 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid) } /* + * RangeVarCheckNamespaceAccessNoError + * Returns true if given relation's namespace can be accessable by the + * current user. If no namespace is given in the relation, just returns + * true. + */ +bool +RangeVarCheckNamespaceAccessNoError(RangeVar *relation) +{ + Oid namespaceId; + AclResult aclresult; + + if (relation->catalogname) + { + if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0) + /* Cross-database references is not allowed */ + return false; + } + if (relation->schemaname) + { + namespaceId = get_namespace_oid(relation->schemaname, true); + if (!OidIsValid(namespaceId)) + /* Namespace is invalid */ + return false; + aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + /* Namespace ACL is not ok */ + return false; + } + + return true; +} + +/* * RelnameGetRelid * Try to resolve an unqualified relation name. * Returns OID if relation found in search path, else InvalidOid. diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index 09cf0e1..6828633 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -128,6 +128,8 @@ to_regproc(PG_FUNCTION_ARGS) * entries in the current search path. */ names = stringToQualifiedNameList(pro_name); + if (!RangeVarCheckNamespaceAccessNoError(makeRangeVarFromNameList(names))) + PG_RETURN_NULL(); clist = FuncnameGetCandidates(names, -1, NIL, false, false, true); if (clist == NULL || clist->next != NULL) @@ -301,6 +303,8 @@ to_regprocedure(PG_FUNCTION_ARGS) * given argument types. (There will not be more than one match.) */ parseNameAndArgTypes(pro_name, false, &names, &nargs, argtypes); + if (!RangeVarCheckNamespaceAccessNoError(makeRangeVarFromNameList(names))) + PG_RETURN_NULL(); clist = FuncnameGetCandidates(names, nargs, NIL, false, false, true); @@ -546,6 +550,8 @@ to_regoper(PG_FUNCTION_ARGS) * entries in the current search path. */ names = stringToQualifiedNameList(opr_name); + if (!RangeVarCheckNamespaceAccessNoError(makeRangeVarFromNameList(names))) + PG_RETURN_NULL(); clist = OpernameGetCandidates(names, '\0', true); if (clist == NULL || clist->next != NULL) @@ -736,6 +742,8 @@ to_regoperator(PG_FUNCTION_ARGS) (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg("too many arguments"), errhint("Provide two argument types for operator."))); + if (!RangeVarCheckNamespaceAccessNoError(makeRangeVarFromNameList(names))) + PG_RETURN_NULL(); result = OpernameGetOprid(names, argtypes[0], argtypes[1]); @@ -948,16 +956,18 @@ to_regclass(PG_FUNCTION_ARGS) { char *class_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); Oid result; - List *names; + RangeVar *names; /* * Parse the name into components and see if it matches any pg_class * entries in the current search path. */ - names = stringToQualifiedNameList(class_name); + names = makeRangeVarFromNameList(stringToQualifiedNameList(class_name)); + if (!RangeVarCheckNamespaceAccessNoError(names)) + PG_RETURN_NULL(); /* We might not even have permissions on this relation; don't lock it. */ - result = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, true); + result = RangeVarGetRelid(names, NoLock, true); if (OidIsValid(result)) PG_RETURN_OID(result); @@ -1104,10 +1114,14 @@ to_regtype(PG_FUNCTION_ARGS) char *typ_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); Oid result; int32 typmod; + List *names; /* * Invoke the full parser to deal with special cases such as array syntax. */ + names = stringToQualifiedNameList(typ_name); + if (!RangeVarCheckNamespaceAccessNoError(makeRangeVarFromNameList(names))) + PG_RETURN_NULL(); parseTypeString(typ_name, &result, &typmod, true); if (OidIsValid(result)) diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 6741834..ff3b065 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -73,6 +73,7 @@ extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *newRelation, LOCKMODE lockmode, Oid *existing_relation_id); extern void RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid); +extern bool RangeVarCheckNamespaceAccessNoError(RangeVar *relation); extern Oid RelnameGetRelid(const char *relname); extern bool RelationIsVisible(Oid relid); diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out index ee4fcda..ccadae4 100644 --- a/src/test/regress/expected/regproc.out +++ b/src/test/regress/expected/regproc.out @@ -395,3 +395,65 @@ SELECT to_regnamespace('"Nonexistent"'); SELECT to_regnamespace('foo.bar'); ERROR: invalid name syntax +/* If objects exist, and user don't have USAGE privilege, return NULL with no error. */ +SELECT current_user AS username +\gset +CREATE USER test_usr; +CREATE SCHEMA test_schema; +REVOKE ALL ON SCHEMA test_schema FROM test_usr; +CREATE OPERATOR test_schema.+ ( + leftarg = int8, + rightarg = int8, + procedure = int8pl +); +CREATE OR REPLACE FUNCTION test_schema.test_func(int) +RETURNS INTEGER AS + 'SELECT 1;' +LANGUAGE sql; +CREATE TABLE test_schema.test_tbl(id int); +CREATE TYPE test_schema.test_type AS (a1 int,a2 int); +\connect - test_usr +SELECT to_regoper('test_schema.+'); + to_regoper +------------ + +(1 row) + +SELECT to_regoperator('test_schema.+(int8,int8)'); + to_regoperator +---------------- + +(1 row) + +SELECT to_regproc('test_schema.test_func'); + to_regproc +------------ + +(1 row) + +SELECT to_regprocedure('test_schema.test_func(int)'); + to_regprocedure +----------------- + +(1 row) + +SELECT to_regclass('test_schema.test_tbl'); + to_regclass +------------- + +(1 row) + +SELECT to_regtype('test_schema.test_type'); + to_regtype +------------ + +(1 row) + +\connect - :username +DROP SCHEMA test_schema CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to operator test_schema.+(bigint,bigint) +drop cascades to function test_schema.test_func(integer) +drop cascades to table test_schema.test_tbl +drop cascades to type test_schema.test_type +DROP ROLE test_usr; diff --git a/src/test/regress/sql/regproc.sql b/src/test/regress/sql/regproc.sql index a60bc28..897f181 100644 --- a/src/test/regress/sql/regproc.sql +++ b/src/test/regress/sql/regproc.sql @@ -113,3 +113,37 @@ SELECT to_regrole('foo.bar'); SELECT to_regnamespace('Nonexistent'); SELECT to_regnamespace('"Nonexistent"'); SELECT to_regnamespace('foo.bar'); + +/* If objects exist, and user don't have USAGE privilege, return NULL with no error. */ + +SELECT current_user AS username +\gset + +CREATE USER test_usr; +CREATE SCHEMA test_schema; +REVOKE ALL ON SCHEMA test_schema FROM test_usr; + +CREATE OPERATOR test_schema.+ ( + leftarg = int8, + rightarg = int8, + procedure = int8pl +); +CREATE OR REPLACE FUNCTION test_schema.test_func(int) +RETURNS INTEGER AS + 'SELECT 1;' +LANGUAGE sql; +CREATE TABLE test_schema.test_tbl(id int); +CREATE TYPE test_schema.test_type AS (a1 int,a2 int); + +\connect - test_usr + +SELECT to_regoper('test_schema.+'); +SELECT to_regoperator('test_schema.+(int8,int8)'); +SELECT to_regproc('test_schema.test_func'); +SELECT to_regprocedure('test_schema.test_func(int)'); +SELECT to_regclass('test_schema.test_tbl'); +SELECT to_regtype('test_schema.test_type'); + +\connect - :username +DROP SCHEMA test_schema CASCADE; +DROP ROLE test_usr;