Identifying user-created objects
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.
I've attached PoC patch to introduce a SQL function
pg_is_user_object() that returns true if the given oid is user object
oid, that is greater than or equal to FirstNormalObjectId. Feedback is
very welcome.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
identify_user_object.patchapplication/octet-stream; name=identify_user_object.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ceda48e0fc..809ccb8047 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18740,6 +18740,10 @@ SELECT collation for ('foo' COLLATE "de_DE");
<primary>pg_get_object_address</primary>
</indexterm>
+ <indexterm>
+ <primary>pg_is_user_object</primary>
+ </indexterm>
+
<para>
<xref linkend="functions-info-object-table"/> lists functions related to
database object identification and addressing.
@@ -18768,6 +18772,11 @@ SELECT collation for ('foo' COLLATE "de_DE");
<entry><parameter>type</parameter> <type>text</type>, <parameter>object_names</parameter> <type>text[]</type>, <parameter>object_args</parameter> <type>text[]</type></entry>
<entry>get external representation of a database object's address</entry>
</row>
+ <row>
+ <entry><literal><function>pg_is_user_object(<parameter>oid</parameter>)</function></literal></entry>
+ <entry><type>bool</type></entry>
+ <entry>true if <parameter>oid</parameter> is user defined object</entry>
+ </row>
<row>
<entry><literal><function>pg_get_object_address(<parameter>type</parameter> <type>text</type>, <parameter>object_names</parameter> <type>text[]</type>, <parameter>object_args</parameter> <type>text[]</type>)</function></literal></entry>
<entry><parameter>classid</parameter> <type>oid</type>, <parameter>objid</parameter> <type>oid</type>, <parameter>objsubid</parameter> <type>integer</type></entry>
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 158784474d..335b13d52f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3202,6 +3202,16 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+/*
+ * Return true if the given oid is normal user object
+ */
+Datum
+pg_is_user_object(PG_FUNCTION_ARGS)
+{
+ Oid oid = PG_GETARG_OID(0);
+
+ PG_RETURN_BOOL(oid >= FirstNormalObjectId);
+}
/*
* deparse_expression - General utility for deparsing expressions
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256907..2dbee10c1c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3637,6 +3637,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '4192', descr => 'identify normal user object',
+ proname => 'pg_is_user_object', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid',
+ prosrc => 'pg_is_user_object' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '400',
On Wed, Feb 5, 2020 at 8:27 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.I've attached PoC patch to introduce a SQL function
pg_is_user_object() that returns true if the given oid is user object
oid, that is greater than or equal to FirstNormalObjectId. Feedback is
very welcome.
+1.
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.
Thanks,
Amit
On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.
FWIW, if we bother adding SQL functions for that, my first impression
was to have three functions, each one of them returning:
- FirstNormalObjectId
- FirstGenbkiObjectId
- FirstNormalObjectId
Perhaps you should add a minimal set of regression tests in the
patch.
--
Michael
On Thu, Feb 6, 2020 at 4:31 PM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.FWIW, if we bother adding SQL functions for that, my first impression
was to have three functions, each one of them returning:
- FirstNormalObjectId
- FirstGenbkiObjectId
- FirstNormalObjectId
Did you miss FirstBootstrapObjectId by any chance?
I see the following ranges as defined in transam.h.
1-(FirstGenbkiObjectId - 1): manually assigned OIDs
FirstGenbkiObjectId-(FirstBootstrapObjectId - 1): genbki.pl assigned OIDs
FirstBootstrapObjectId-(FirstNormalObjectId - 1): initdb requested
FirstNormalObjectId or greater: user-defined objects
Sawada-san's proposal covers #4. Do we need an SQL function for the
first three? IOW, would the distinction between OIDs belonging to the
first three ranges be of interest to anyone except core PG hackers?
Thanks,
Amit
On Thu, Feb 6, 2020 at 8:53 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Feb 6, 2020 at 4:31 PM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.FWIW, if we bother adding SQL functions for that, my first impression
was to have three functions, each one of them returning:
- FirstNormalObjectId
- FirstGenbkiObjectId
- FirstNormalObjectIdDid you miss FirstBootstrapObjectId by any chance?
I see the following ranges as defined in transam.h.
1-(FirstGenbkiObjectId - 1): manually assigned OIDs
FirstGenbkiObjectId-(FirstBootstrapObjectId - 1): genbki.pl assigned OIDs
FirstBootstrapObjectId-(FirstNormalObjectId - 1): initdb requested
FirstNormalObjectId or greater: user-defined objectsSawada-san's proposal covers #4. Do we need an SQL function for the
first three? IOW, would the distinction between OIDs belonging to the
first three ranges be of interest to anyone except core PG hackers?
+1 for #4, but I'm not sure that the other 3 are really interesting to
have at SQL level.
On Thu, Feb 06, 2020 at 04:52:48PM +0900, Amit Langote wrote:
On Thu, Feb 6, 2020 at 4:31 PM Michael Paquier <michael@paquier.xyz> wrote:
FWIW, if we bother adding SQL functions for that, my first impression
was to have three functions, each one of them returning:
- FirstNormalObjectId
- FirstGenbkiObjectId
- FirstNormalObjectIdDid you miss FirstBootstrapObjectId by any chance?
Yep, incorrect copy-pasto.
--
Michael
On Thu, 6 Feb 2020 at 16:53, Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Feb 6, 2020 at 4:31 PM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.FWIW, if we bother adding SQL functions for that, my first impression
was to have three functions, each one of them returning:
- FirstNormalObjectId
- FirstGenbkiObjectId
- FirstNormalObjectIdDid you miss FirstBootstrapObjectId by any chance?
I see the following ranges as defined in transam.h.
1-(FirstGenbkiObjectId - 1): manually assigned OIDs
FirstGenbkiObjectId-(FirstBootstrapObjectId - 1): genbki.pl assigned OIDs
FirstBootstrapObjectId-(FirstNormalObjectId - 1): initdb requested
FirstNormalObjectId or greater: user-defined objectsSawada-san's proposal covers #4. Do we need an SQL function for the
first three? IOW, would the distinction between OIDs belonging to the
first three ranges be of interest to anyone except core PG hackers?
Yeah I thought of these three values but I'm also not sure it's worth for users.
If we have these functions returning the values respectively, when we
want to check if an oid is assigned during initdb we will end up with
doing something like 'WHERE oid >= pg_first_bootstrap_oid() and oid <
pg_first_normal_oid()', which is not intuitive, I think. Users have to
remember the order of these values.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Thu, 6 Feb 2020 at 17:18, Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
On Thu, 6 Feb 2020 at 16:53, Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Feb 6, 2020 at 4:31 PM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.FWIW, if we bother adding SQL functions for that, my first impression
was to have three functions, each one of them returning:
- FirstNormalObjectId
- FirstGenbkiObjectId
- FirstNormalObjectIdDid you miss FirstBootstrapObjectId by any chance?
I see the following ranges as defined in transam.h.
1-(FirstGenbkiObjectId - 1): manually assigned OIDs
FirstGenbkiObjectId-(FirstBootstrapObjectId - 1): genbki.pl assigned OIDs
FirstBootstrapObjectId-(FirstNormalObjectId - 1): initdb requested
FirstNormalObjectId or greater: user-defined objectsSawada-san's proposal covers #4. Do we need an SQL function for the
first three? IOW, would the distinction between OIDs belonging to the
first three ranges be of interest to anyone except core PG hackers?Yeah I thought of these three values but I'm also not sure it's worth for users.
If we have these functions returning the values respectively, when we
want to check if an oid is assigned during initdb we will end up with
doing something like 'WHERE oid >= pg_first_bootstrap_oid() and oid <
pg_first_normal_oid()', which is not intuitive, I think. Users have to
remember the order of these values.
Attached the updated version patch that includes regression tests. And
I have registered this to the next commit fest.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
identify_user_object_v2.patchapplication/octet-stream; name=identify_user_object_v2.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ceda48e0fc..809ccb8047 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18740,6 +18740,10 @@ SELECT collation for ('foo' COLLATE "de_DE");
<primary>pg_get_object_address</primary>
</indexterm>
+ <indexterm>
+ <primary>pg_is_user_object</primary>
+ </indexterm>
+
<para>
<xref linkend="functions-info-object-table"/> lists functions related to
database object identification and addressing.
@@ -18768,6 +18772,11 @@ SELECT collation for ('foo' COLLATE "de_DE");
<entry><parameter>type</parameter> <type>text</type>, <parameter>object_names</parameter> <type>text[]</type>, <parameter>object_args</parameter> <type>text[]</type></entry>
<entry>get external representation of a database object's address</entry>
</row>
+ <row>
+ <entry><literal><function>pg_is_user_object(<parameter>oid</parameter>)</function></literal></entry>
+ <entry><type>bool</type></entry>
+ <entry>true if <parameter>oid</parameter> is user defined object</entry>
+ </row>
<row>
<entry><literal><function>pg_get_object_address(<parameter>type</parameter> <type>text</type>, <parameter>object_names</parameter> <type>text[]</type>, <parameter>object_args</parameter> <type>text[]</type>)</function></literal></entry>
<entry><parameter>classid</parameter> <type>oid</type>, <parameter>objid</parameter> <type>oid</type>, <parameter>objsubid</parameter> <type>integer</type></entry>
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 158784474d..335b13d52f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3202,6 +3202,16 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+/*
+ * Return true if the given oid is normal user object
+ */
+Datum
+pg_is_user_object(PG_FUNCTION_ARGS)
+{
+ Oid oid = PG_GETARG_OID(0);
+
+ PG_RETURN_BOOL(oid >= FirstNormalObjectId);
+}
/*
* deparse_expression - General utility for deparsing expressions
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 226c904c04..196a59e031 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3637,6 +3637,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '4192', descr => 'identify normal user object',
+ proname => 'pg_is_user_object', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid',
+ prosrc => 'pg_is_user_object' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '400',
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index d6d1470156..caec91d1c4 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,6 +492,19 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
publication relation | | | addr_nsp.gentable in publication addr_pub | t
(49 rows)
+--check if the object is built-in or user-defined
+SELECT pg_is_user_object('pg_class'::regclass);
+ pg_is_user_object
+-------------------
+ f
+(1 row)
+
+SELECT pg_is_user_object('trig'::regproc);
+ pg_is_user_object
+-------------------
+ t
+(1 row)
+
---
--- Cleanup resources
---
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 8e06248eb5..faac767ff3 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -210,6 +210,10 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
pg_get_object_address(typ, nms, ioa.args) as addr2
ORDER BY addr1.classid, addr1.objid, addr1.objsubid;
+--check if the object is built-in or user-defined
+SELECT pg_is_user_object('pg_class'::regclass);
+SELECT pg_is_user_object('trig'::regproc);
+
---
--- Cleanup resources
---
Sawada-san,
On Mon, Feb 10, 2020 at 12:25 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.Attached the updated version patch that includes regression tests. And
I have registered this to the next commit fest.
Thank you.
Any thoughts on the above suggestion?
Regards,
Amit
On Mon, 10 Feb 2020 at 12:54, Amit Langote <amitlangote09@gmail.com> wrote:
Sawada-san,
On Mon, Feb 10, 2020 at 12:25 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.Attached the updated version patch that includes regression tests. And
I have registered this to the next commit fest.Thank you.
Any thoughts on the above suggestion?
Oops, I had overlooked it. I agree with you.
How about having it as a macro like:
#define ObjectIdIsUserObject(oid) ((Oid)(oid) >= FirstNormalObjectId)
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Mon, Feb 10, 2020 at 1:06 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
On Mon, 10 Feb 2020 at 12:54, Amit Langote <amitlangote09@gmail.com> wrote:
Sawada-san,
On Mon, Feb 10, 2020 at 12:25 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:On Thu, Feb 06, 2020 at 04:25:47PM +0900, Amit Langote wrote:
About the implementation, how about defining a static inline function,
say is_user_object(), next to FirstNormalObjectId's definition and
make pg_is_user_object() call it? There are a few placed in the
backend code that perform the same computation as pg_is_user_object(),
which could be changed to use is_user_object() instead.Attached the updated version patch that includes regression tests. And
I have registered this to the next commit fest.Thank you.
Any thoughts on the above suggestion?
Oops, I had overlooked it. I agree with you.
How about having it as a macro like:
#define ObjectIdIsUserObject(oid) ((Oid)(oid) >= FirstNormalObjectId)
I'm fine with a macro.
Thanks,
Amit
On Mon, Feb 10, 2020 at 01:16:30PM +0900, Amit Langote wrote:
On Mon, Feb 10, 2020 at 1:06 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:How about having it as a macro like:
#define ObjectIdIsUserObject(oid) ((Oid)(oid) >= FirstNormalObjectId)
I'm fine with a macro.
I am not sure that it is worth having one extra abstraction layer for
that.
--
Michael
On Mon, 10 Feb 2020 at 14:09, Michael Paquier <michael@paquier.xyz> wrote:
On Mon, Feb 10, 2020 at 01:16:30PM +0900, Amit Langote wrote:
On Mon, Feb 10, 2020 at 1:06 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:How about having it as a macro like:
#define ObjectIdIsUserObject(oid) ((Oid)(oid) >= FirstNormalObjectId)
I'm fine with a macro.
I am not sure that it is worth having one extra abstraction layer for
that.
Hmm I'm not going to insist on that but I thought that it could
somewhat improve readability at places where they already compares an
oid to FirstNormalObjectId as Amit mentioned:
src/backend/catalog/pg_publication.c: relid >= FirstNormalObjectId;
src/backend/utils/adt/json.c: if (typoid >= FirstNormalObjectId)
src/backend/utils/adt/jsonb.c: if (typoid >= FirstNormalObjectId)
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Mon, Feb 10, 2020 at 2:23 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
On Mon, 10 Feb 2020 at 14:09, Michael Paquier <michael@paquier.xyz> wrote:
On Mon, Feb 10, 2020 at 01:16:30PM +0900, Amit Langote wrote:
On Mon, Feb 10, 2020 at 1:06 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:How about having it as a macro like:
#define ObjectIdIsUserObject(oid) ((Oid)(oid) >= FirstNormalObjectId)
I'm fine with a macro.
I am not sure that it is worth having one extra abstraction layer for
that.Hmm I'm not going to insist on that but I thought that it could
somewhat improve readability at places where they already compares an
oid to FirstNormalObjectId as Amit mentioned:src/backend/catalog/pg_publication.c: relid >= FirstNormalObjectId;
src/backend/utils/adt/json.c: if (typoid >= FirstNormalObjectId)
src/backend/utils/adt/jsonb.c: if (typoid >= FirstNormalObjectId)
Agree that ObjectIsUserObject(oid) is easier to read than oid >=
FirstNormalObject. I would have not bothered, for example, if it was
something like oid >= FirstUserObjectId to begin with.
Thanks,
Amit
At Mon, 10 Feb 2020 14:32:44 +0900, Amit Langote <amitlangote09@gmail.com> wrote in
On Mon, Feb 10, 2020 at 2:23 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:On Mon, 10 Feb 2020 at 14:09, Michael Paquier <michael@paquier.xyz> wrote:
On Mon, Feb 10, 2020 at 01:16:30PM +0900, Amit Langote wrote:
On Mon, Feb 10, 2020 at 1:06 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:How about having it as a macro like:
#define ObjectIdIsUserObject(oid) ((Oid)(oid) >= FirstNormalObjectId)
I'm fine with a macro.
I am not sure that it is worth having one extra abstraction layer for
that.Hmm I'm not going to insist on that but I thought that it could
somewhat improve readability at places where they already compares an
oid to FirstNormalObjectId as Amit mentioned:src/backend/catalog/pg_publication.c: relid >= FirstNormalObjectId;
src/backend/utils/adt/json.c: if (typoid >= FirstNormalObjectId)
src/backend/utils/adt/jsonb.c: if (typoid >= FirstNormalObjectId)Agree that ObjectIsUserObject(oid) is easier to read than oid >=
FirstNormalObject. I would have not bothered, for example, if it was
something like oid >= FirstUserObjectId to begin with.
Aside from the naming, I'm not sure it's sensible to use
FirstNormalObjectId since I don't see a clear definition or required
characteristics for "user created objects" is. If we did CREATE
TABLE, FUNCTION or maybe any objects during single-user mode before
the first object is created during normal multiuser operation, the
"user-created(or not?)" object has an OID less than
FirstNormalObjectId. If such objects are the "user created object", we
need FirstUserObjectId defferent from FirstNormalObjectId.
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
On Thu, Feb 13, 2020 at 10:30 AM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:
At Mon, 10 Feb 2020 14:32:44 +0900, Amit Langote <amitlangote09@gmail.com> wrote in
Agree that ObjectIsUserObject(oid) is easier to read than oid >=
FirstNormalObject. I would have not bothered, for example, if it was
something like oid >= FirstUserObjectId to begin with.Aside from the naming, I'm not sure it's sensible to use
FirstNormalObjectId since I don't see a clear definition or required
characteristics for "user created objects" is. If we did CREATE
TABLE, FUNCTION or maybe any objects during single-user mode before
the first object is created during normal multiuser operation, the
"user-created(or not?)" object has an OID less than
FirstNormalObjectId. If such objects are the "user created object", we
need FirstUserObjectId defferent from FirstNormalObjectId.
Interesting observation. Connecting to database in --single mode,
whether done using initdb or directly, is always considered
"bootstrapping", so the OIDs from the bootstrapping range are
consumed.
$ postgres --single -D pgdata postgres
PostgreSQL stand-alone backend 13devel
backend> create table a (a int);
backend> select 'a'::regclass::oid;
1: oid (typeid = 26, len = 4, typmod = -1, byval = t)
----
1: oid = "14168" (typeid = 26, len = 4, typmod = -1, byval = t)
Here, FirstBootstrapObjectId < 14168 < FirstNormalObjectId
Maybe we could document that pg_is_user_object() and its internal
counterpart returns true only for objects that are created during
"normal" multi-user database operation.
Thanks,
Amit
On Thu, Feb 13, 2020 at 8:32 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Feb 13, 2020 at 10:30 AM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:At Mon, 10 Feb 2020 14:32:44 +0900, Amit Langote <amitlangote09@gmail.com> wrote in
Agree that ObjectIsUserObject(oid) is easier to read than oid >=
FirstNormalObject. I would have not bothered, for example, if it was
something like oid >= FirstUserObjectId to begin with.Aside from the naming, I'm not sure it's sensible to use
FirstNormalObjectId since I don't see a clear definition or required
characteristics for "user created objects" is. If we did CREATE
TABLE, FUNCTION or maybe any objects during single-user mode before
the first object is created during normal multiuser operation, the
"user-created(or not?)" object has an OID less than
FirstNormalObjectId. If such objects are the "user created object", we
need FirstUserObjectId defferent from FirstNormalObjectId.Interesting observation. Connecting to database in --single mode,
whether done using initdb or directly, is always considered
"bootstrapping", so the OIDs from the bootstrapping range are
consumed.$ postgres --single -D pgdata postgres
PostgreSQL stand-alone backend 13devel
backend> create table a (a int);
backend> select 'a'::regclass::oid;
1: oid (typeid = 26, len = 4, typmod = -1, byval = t)
----
1: oid = "14168" (typeid = 26, len = 4, typmod = -1, byval = t)Here, FirstBootstrapObjectId < 14168 < FirstNormalObjectId
FTR it's also possible to get the same result using binary mode and
binary_upgrade_set_next_XXX functions.
Maybe we could document that pg_is_user_object() and its internal
counterpart returns true only for objects that are created during
"normal" multi-user database operation.
+1
On Thu, 13 Feb 2020 at 17:13, Julien Rouhaud <rjuju123@gmail.com> wrote:
On Thu, Feb 13, 2020 at 8:32 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Feb 13, 2020 at 10:30 AM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:At Mon, 10 Feb 2020 14:32:44 +0900, Amit Langote <amitlangote09@gmail.com> wrote in
Agree that ObjectIsUserObject(oid) is easier to read than oid >=
FirstNormalObject. I would have not bothered, for example, if it was
something like oid >= FirstUserObjectId to begin with.Aside from the naming, I'm not sure it's sensible to use
FirstNormalObjectId since I don't see a clear definition or required
characteristics for "user created objects" is. If we did CREATE
TABLE, FUNCTION or maybe any objects during single-user mode before
the first object is created during normal multiuser operation, the
"user-created(or not?)" object has an OID less than
FirstNormalObjectId. If such objects are the "user created object", we
need FirstUserObjectId defferent from FirstNormalObjectId.Interesting observation. Connecting to database in --single mode,
whether done using initdb or directly, is always considered
"bootstrapping", so the OIDs from the bootstrapping range are
consumed.$ postgres --single -D pgdata postgres
PostgreSQL stand-alone backend 13devel
backend> create table a (a int);
backend> select 'a'::regclass::oid;
1: oid (typeid = 26, len = 4, typmod = -1, byval = t)
----
1: oid = "14168" (typeid = 26, len = 4, typmod = -1, byval = t)Here, FirstBootstrapObjectId < 14168 < FirstNormalObjectId
FTR it's also possible to get the same result using binary mode and
binary_upgrade_set_next_XXX functions.Maybe we could document that pg_is_user_object() and its internal
counterpart returns true only for objects that are created during
"normal" multi-user database operation.+1
Agreed.
Attached updated version patch.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
identify_user_object_v3.patchapplication/octet-stream; name=identify_user_object_v3.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ceda48e0fc..321d9c1fb1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18740,6 +18740,10 @@ SELECT collation for ('foo' COLLATE "de_DE");
<primary>pg_get_object_address</primary>
</indexterm>
+ <indexterm>
+ <primary>pg_is_user_object</primary>
+ </indexterm>
+
<para>
<xref linkend="functions-info-object-table"/> lists functions related to
database object identification and addressing.
@@ -18768,6 +18772,14 @@ SELECT collation for ('foo' COLLATE "de_DE");
<entry><parameter>type</parameter> <type>text</type>, <parameter>object_names</parameter> <type>text[]</type>, <parameter>object_args</parameter> <type>text[]</type></entry>
<entry>get external representation of a database object's address</entry>
</row>
+ <row>
+ <entry><literal><function>pg_is_user_object(<parameter>oid</parameter>)</function></literal></entry>
+ <entry><type>bool</type></entry>
+ <entry>
+ true if <parameter>oid</parameter> is the object which is created during
+ normal multi-user database operation.
+ </entry>
+ </row>
<row>
<entry><literal><function>pg_get_object_address(<parameter>type</parameter> <type>text</type>, <parameter>object_names</parameter> <type>text[]</type>, <parameter>object_args</parameter> <type>text[]</type>)</function></literal></entry>
<entry><parameter>classid</parameter> <type>oid</type>, <parameter>objid</parameter> <type>oid</type>, <parameter>objsubid</parameter> <type>integer</type></entry>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index c5eea7af3f..7888e00d03 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -106,7 +106,7 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
return reltuple->relkind == RELKIND_RELATION &&
!IsCatalogRelationOid(relid) &&
reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
- relid >= FirstNormalObjectId;
+ ObjectIsUserObject(relid);
}
/*
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 567eab1e01..e4f00fc309 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -204,7 +204,7 @@ json_categorize_type(Oid typoid,
/* It's probably the general case ... */
*tcategory = JSONTYPE_OTHER;
/* but let's look for a cast to json, if it's not built-in */
- if (typoid >= FirstNormalObjectId)
+ if (ObjectIsUserObject(typoid))
{
Oid castfunc;
CoercionPathType ctype;
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index fea4335951..6869ea9954 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -690,7 +690,7 @@ jsonb_categorize_type(Oid typoid,
* but first let's look for a cast to json (note: not to
* jsonb) if it's not built-in.
*/
- if (typoid >= FirstNormalObjectId)
+ if (ObjectIsUserObject(typoid))
{
Oid castfunc;
CoercionPathType ctype;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 158784474d..15b1ad13eb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3202,6 +3202,16 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+/*
+ * Return true if the given oid is normal user object
+ */
+Datum
+pg_is_user_object(PG_FUNCTION_ARGS)
+{
+ Oid oid = PG_GETARG_OID(0);
+
+ PG_RETURN_BOOL(ObjectIsUserObject(oid));
+}
/*
* deparse_expression - General utility for deparsing expressions
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 6a947b958b..afc3c24978 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -140,6 +140,9 @@ FullTransactionIdAdvance(FullTransactionId *dest)
#define FirstBootstrapObjectId 12000
#define FirstNormalObjectId 16384
+/* Return true if the oid is assigned during normal multiuser operation */
+#define ObjectIsUserObject(oid) ((Oid) oid >= FirstNormalObjectId)
+
/*
* VariableCache is a data structure in shared memory that is used to track
* OID and XID assignment state. For largely historical reasons, there is
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eb3c1a88d1..5c83f5f80f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3637,6 +3637,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '4192', descr => 'identify normal user object',
+ proname => 'pg_is_user_object', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid',
+ prosrc => 'pg_is_user_object' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '400',
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index d6d1470156..c9f117ac38 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,6 +492,19 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
publication relation | | | addr_nsp.gentable in publication addr_pub | t
(49 rows)
+-- identity if the object is built-in or user-defined
+SELECT pg_is_user_object('pg_class'::regclass);
+ pg_is_user_object
+-------------------
+ f
+(1 row)
+
+SELECT pg_is_user_object('trig'::regproc);
+ pg_is_user_object
+-------------------
+ t
+(1 row)
+
---
--- Cleanup resources
---
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 8e06248eb5..3b8f0316fe 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -210,6 +210,10 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
pg_get_object_address(typ, nms, ioa.args) as addr2
ORDER BY addr1.classid, addr1.objid, addr1.objsubid;
+-- identity if the object is built-in or user-defined
+SELECT pg_is_user_object('pg_class'::regclass);
+SELECT pg_is_user_object('trig'::regproc);
+
---
--- Cleanup resources
---
On Wed, Feb 26, 2020 at 4:48 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
On Thu, 13 Feb 2020 at 17:13, Julien Rouhaud <rjuju123@gmail.com> wrote:
On Thu, Feb 13, 2020 at 8:32 AM Amit Langote <amitlangote09@gmail.com> wrote:
Maybe we could document that pg_is_user_object() and its internal
counterpart returns true only for objects that are created during
"normal" multi-user database operation.+1
Agreed.
Attached updated version patch.
Thanks for updating the patch. Some comments:
+ <row>
+ <entry><literal><function>pg_is_user_object(<parameter>oid</parameter>)</function></literal></entry>
+ <entry><type>bool</type></entry>
+ <entry>
+ true if <parameter>oid</parameter> is the object which is
created during
+ normal multi-user database operation.
+ </entry>
+ </row>
How about clarifying the description further as follows:
"true for objects created while database is operating in normal
multi-user mode, as opposed to single-user mode (see <xref
linkend="app-postgres"/>)."
Term "multi-user operation" is not mentioned elsewhere in the
documentation, so better to clarify what it means.
Also, maybe a minor nitpick, but how about adding the new function's
row at the end of the table (Table 9.72) instead of in the middle?
Other than that, patch looks to be in pretty good shape.
Thanks,
Amit
On Wed, Feb 26, 2020 at 1:18 PM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
On Thu, 13 Feb 2020 at 17:13, Julien Rouhaud <rjuju123@gmail.com> wrote:
On Thu, Feb 13, 2020 at 8:32 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Feb 13, 2020 at 10:30 AM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:At Mon, 10 Feb 2020 14:32:44 +0900, Amit Langote <amitlangote09@gmail.com> wrote in
Agree that ObjectIsUserObject(oid) is easier to read than oid >=
FirstNormalObject. I would have not bothered, for example, if it was
something like oid >= FirstUserObjectId to begin with.Aside from the naming, I'm not sure it's sensible to use
FirstNormalObjectId since I don't see a clear definition or required
characteristics for "user created objects" is. If we did CREATE
TABLE, FUNCTION or maybe any objects during single-user mode before
the first object is created during normal multiuser operation, the
"user-created(or not?)" object has an OID less than
FirstNormalObjectId. If such objects are the "user created object", we
need FirstUserObjectId defferent from FirstNormalObjectId.Interesting observation. Connecting to database in --single mode,
whether done using initdb or directly, is always considered
"bootstrapping", so the OIDs from the bootstrapping range are
consumed.$ postgres --single -D pgdata postgres
PostgreSQL stand-alone backend 13devel
backend> create table a (a int);
backend> select 'a'::regclass::oid;
1: oid (typeid = 26, len = 4, typmod = -1, byval = t)
----
1: oid = "14168" (typeid = 26, len = 4, typmod = -1, byval = t)Here, FirstBootstrapObjectId < 14168 < FirstNormalObjectId
FTR it's also possible to get the same result using binary mode and
binary_upgrade_set_next_XXX functions.Maybe we could document that pg_is_user_object() and its internal
counterpart returns true only for objects that are created during
"normal" multi-user database operation.+1
Agreed.
Attached updated version patch.
Should we add some check if object exists or not here:
+Datum
+pg_is_user_object(PG_FUNCTION_ARGS)
+{
+ Oid oid = PG_GETARG_OID(0);
+
+ PG_RETURN_BOOL(ObjectIsUserObject(oid));
+}
I was trying some scenarios where we pass an object which does not exist:
postgres=# SELECT pg_is_user_object(0);
pg_is_user_object
-------------------
f
(1 row)
postgres=# SELECT pg_is_user_object(222222);
pg_is_user_object
-------------------
t
(1 row)
SELECT pg_is_user_object('pg_class1'::regclass);
ERROR: relation "pg_class1" does not exist
LINE 1: SELECT pg_is_user_object('pg_class1'::regclass);
^
I felt these behavior seems to be slightly inconsistent.
Thoughts?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Tue, 3 Mar 2020 at 23:33, vignesh C <vignesh21@gmail.com> wrote:
Should we add some check if object exists or not here: +Datum +pg_is_user_object(PG_FUNCTION_ARGS) +{ + Oid oid = PG_GETARG_OID(0); + + PG_RETURN_BOOL(ObjectIsUserObject(oid)); +}I was trying some scenarios where we pass an object which does not exist:
postgres=# SELECT pg_is_user_object(0);
pg_is_user_object
-------------------
f
(1 row)
postgres=# SELECT pg_is_user_object(222222);
pg_is_user_object
-------------------
t
(1 row)
SELECT pg_is_user_object('pg_class1'::regclass);
ERROR: relation "pg_class1" does not exist
LINE 1: SELECT pg_is_user_object('pg_class1'::regclass);
^
I felt these behavior seems to be slightly inconsistent.
Thoughts?
Hmm I'm not sure we should existing check in that function. Main use
case would be passing an oid of a tuple of a system catalog to that
function to check if the given object was created while multi-user
mode. So I think this function can assume that the given object id
exists. And if we want to do that check, we will end up with checking
if the object having that oid in all system catalogs, which is very
high cost I think.
I suspect perhaps the function name pg_is_user_object led that
confusion. That name looks like it checks if the given 'object' is
created while multi-user mode. So maybe we can improve it either by
renaming to pg_is_user_object_id (or pg_is_user_oid?) or leaving the
name but describing in the doc (based on Amit's suggestion in previous
mail):
"true for oids of objects assigned while database is operating in
normal multi-user mode, as opposed to single-user mode (see
<xreflinkend="app-postgres"/>)."
What do you think?
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Mar 4, 2020 at 9:02 AM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:
On Tue, 3 Mar 2020 at 23:33, vignesh C <vignesh21@gmail.com> wrote:
Should we add some check if object exists or not here: +Datum +pg_is_user_object(PG_FUNCTION_ARGS) +{ + Oid oid = PG_GETARG_OID(0); + + PG_RETURN_BOOL(ObjectIsUserObject(oid)); +}I was trying some scenarios where we pass an object which does not exist:
postgres=# SELECT pg_is_user_object(0);
pg_is_user_object
-------------------
f
(1 row)
postgres=# SELECT pg_is_user_object(222222);
pg_is_user_object
-------------------
t
(1 row)
SELECT pg_is_user_object('pg_class1'::regclass);
ERROR: relation "pg_class1" does not exist
LINE 1: SELECT pg_is_user_object('pg_class1'::regclass);
^
I felt these behavior seems to be slightly inconsistent.
Thoughts?Hmm I'm not sure we should existing check in that function. Main use
case would be passing an oid of a tuple of a system catalog to that
function to check if the given object was created while multi-user
mode. So I think this function can assume that the given object id
exists. And if we want to do that check, we will end up with checking
if the object having that oid in all system catalogs, which is very
high cost I think.I suspect perhaps the function name pg_is_user_object led that
confusion. That name looks like it checks if the given 'object' is
created while multi-user mode. So maybe we can improve it either by
renaming to pg_is_user_object_id (or pg_is_user_oid?) or leaving the
name but describing in the doc (based on Amit's suggestion in previous
mail):
I liked pg_is_user_oid over pg_is_user_object_id but this liking may
vary from person to person, so I'm still ok if you don't change the
name. I'm fine about adding the information in the document unless
someone else feels that this check is required in this function.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On 2020/02/05 20:26, Masahiko Sawada wrote:
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.
The function that you are proposing is really enough for this use case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?
Regards,
--
Fujii Masao
NTT DATA CORPORATION
Advanced Platform Technology Group
Research and Development Headquarters
On Wed, 4 Mar 2020 at 16:43, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/02/05 20:26, Masahiko Sawada wrote:
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.The function that you are proposing is really enough for this use case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?
That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:
TRAP: BadArgument("OidIsValid(relid)", File: "autovacuum.c", Line: 2990)
As you pointed out, it's not enough as long as users can manually
update oid to < FirstNormalObjectId. But I wonder if we should rather
forbid that.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 2020/03/04 17:05, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 16:43, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/02/05 20:26, Masahiko Sawada wrote:
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.The function that you are proposing is really enough for this use case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:
Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as superusers
and not give the permission on system catalogs to such users.
Regards,
--
Fujii Masao
NTT DATA CORPORATION
Advanced Platform Technology Group
Research and Development Headquarters
On Wed, 4 Mar 2020 at 18:02, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/03/04 17:05, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 16:43, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/02/05 20:26, Masahiko Sawada wrote:
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.The function that you are proposing is really enough for this use case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as superusers
and not give the permission on system catalogs to such users.
I think there is still insider threats. As long as we depend on
superuser privilege to do some DBA work, a malicious DBA might be able
to log in as superuser and modify oid.
This behavior is introduced in PG12 where we made oid column
non-system column. A table having oid = 0 is shown in pg_class but we
cannot drop it.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 2020/03/04 18:36, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 18:02, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/03/04 17:05, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 16:43, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/02/05 20:26, Masahiko Sawada wrote:
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.The function that you are proposing is really enough for this use case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as superusers
and not give the permission on system catalogs to such users.I think there is still insider threats. As long as we depend on
superuser privilege to do some DBA work, a malicious DBA might be able
to log in as superuser and modify oid.
Yes. But I'm sure that DBA has already considered the measures
againt such threads. Otherwise malicious users can do anything
more malicious rather than changing oid.
Regards,
--
Fujii Masao
NTT DATA CORPORATION
Advanced Platform Technology Group
Research and Development Headquarters
On Wed, 4 Mar 2020 at 18:57, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/03/04 18:36, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 18:02, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/03/04 17:05, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 16:43, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/02/05 20:26, Masahiko Sawada wrote:
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.The function that you are proposing is really enough for this use case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as superusers
and not give the permission on system catalogs to such users.I think there is still insider threats. As long as we depend on
superuser privilege to do some DBA work, a malicious DBA might be able
to log in as superuser and modify oid.Yes. But I'm sure that DBA has already considered the measures
againt such threads. Otherwise malicious users can do anything
more malicious rather than changing oid.
Agreed. So that's not a serious problem in practice but we cannot say
the checking by pg_is_user_object() is totally enough for checking
whether malicious object exists or not. Is that right?
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, 4 Mar 2020 at 15:28, vignesh C <vignesh21@gmail.com> wrote:
On Wed, Mar 4, 2020 at 9:02 AM Masahiko Sawada
<masahiko.sawada@2ndquadrant.com> wrote:On Tue, 3 Mar 2020 at 23:33, vignesh C <vignesh21@gmail.com> wrote:
Should we add some check if object exists or not here: +Datum +pg_is_user_object(PG_FUNCTION_ARGS) +{ + Oid oid = PG_GETARG_OID(0); + + PG_RETURN_BOOL(ObjectIsUserObject(oid)); +}I was trying some scenarios where we pass an object which does not exist:
postgres=# SELECT pg_is_user_object(0);
pg_is_user_object
-------------------
f
(1 row)
postgres=# SELECT pg_is_user_object(222222);
pg_is_user_object
-------------------
t
(1 row)
SELECT pg_is_user_object('pg_class1'::regclass);
ERROR: relation "pg_class1" does not exist
LINE 1: SELECT pg_is_user_object('pg_class1'::regclass);
^
I felt these behavior seems to be slightly inconsistent.
Thoughts?Hmm I'm not sure we should existing check in that function. Main use
case would be passing an oid of a tuple of a system catalog to that
function to check if the given object was created while multi-user
mode. So I think this function can assume that the given object id
exists. And if we want to do that check, we will end up with checking
if the object having that oid in all system catalogs, which is very
high cost I think.I suspect perhaps the function name pg_is_user_object led that
confusion. That name looks like it checks if the given 'object' is
created while multi-user mode. So maybe we can improve it either by
renaming to pg_is_user_object_id (or pg_is_user_oid?) or leaving the
name but describing in the doc (based on Amit's suggestion in previous
mail):I liked pg_is_user_oid over pg_is_user_object_id but this liking may
vary from person to person, so I'm still ok if you don't change the
name. I'm fine about adding the information in the document unless
someone else feels that this check is required in this function.
Attached updated patch that incorporated comments from Amit and Vignesh.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
identify_user_object_v4.patchapplication/x-patch; name=identify_user_object_v4.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..3a2c7fa6a6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18752,6 +18752,10 @@ SELECT collation for ('foo' COLLATE "de_DE");
<primary>pg_get_object_address</primary>
</indexterm>
+ <indexterm>
+ <primary>pg_is_user_object</primary>
+ </indexterm>
+
<para>
<xref linkend="functions-info-object-table"/> lists functions related to
database object identification and addressing.
@@ -18785,6 +18789,14 @@ SELECT collation for ('foo' COLLATE "de_DE");
<entry><parameter>classid</parameter> <type>oid</type>, <parameter>objid</parameter> <type>oid</type>, <parameter>objsubid</parameter> <type>integer</type></entry>
<entry>get address of a database object from its external representation</entry>
</row>
+ <row>
+ <entry><literal><function>pg_is_user_object(<parameter>oid</parameter>)</function></literal></entry>
+ <entry><type>bool</type></entry>
+ <entry>
+ true for OIDs assigned while database is operating in normal multi-user
+ mode, as opposed to single-user mode (see <xref linkend="app-postgres"/>).
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index c5eea7af3f..7888e00d03 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -106,7 +106,7 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
return reltuple->relkind == RELKIND_RELATION &&
!IsCatalogRelationOid(relid) &&
reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
- relid >= FirstNormalObjectId;
+ ObjectIsUserObject(relid);
}
/*
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 567eab1e01..e4f00fc309 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -204,7 +204,7 @@ json_categorize_type(Oid typoid,
/* It's probably the general case ... */
*tcategory = JSONTYPE_OTHER;
/* but let's look for a cast to json, if it's not built-in */
- if (typoid >= FirstNormalObjectId)
+ if (ObjectIsUserObject(typoid))
{
Oid castfunc;
CoercionPathType ctype;
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index fea4335951..6869ea9954 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -690,7 +690,7 @@ jsonb_categorize_type(Oid typoid,
* but first let's look for a cast to json (note: not to
* jsonb) if it's not built-in.
*/
- if (typoid >= FirstNormalObjectId)
+ if (ObjectIsUserObject(typoid))
{
Oid castfunc;
CoercionPathType ctype;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 158784474d..15b1ad13eb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3202,6 +3202,16 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+/*
+ * Return true if the given oid is normal user object
+ */
+Datum
+pg_is_user_object(PG_FUNCTION_ARGS)
+{
+ Oid oid = PG_GETARG_OID(0);
+
+ PG_RETURN_BOOL(ObjectIsUserObject(oid));
+}
/*
* deparse_expression - General utility for deparsing expressions
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 6a947b958b..ba79098ed6 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -140,6 +140,9 @@ FullTransactionIdAdvance(FullTransactionId *dest)
#define FirstBootstrapObjectId 12000
#define FirstNormalObjectId 16384
+/* Return true if the oid is assigned during normal multiuser operation */
+#define ObjectIsUserObject(oid) (((Oid) oid) >= FirstNormalObjectId)
+
/*
* VariableCache is a data structure in shared memory that is used to track
* OID and XID assignment state. For largely historical reasons, there is
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7b7b..d2f4480c96 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3640,6 +3640,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '4192', descr => 'identify normal user object',
+ proname => 'pg_is_user_object', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid',
+ prosrc => 'pg_is_user_object' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '400',
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index d6d1470156..c9f117ac38 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,6 +492,19 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
publication relation | | | addr_nsp.gentable in publication addr_pub | t
(49 rows)
+-- identity if the object is built-in or user-defined
+SELECT pg_is_user_object('pg_class'::regclass);
+ pg_is_user_object
+-------------------
+ f
+(1 row)
+
+SELECT pg_is_user_object('trig'::regproc);
+ pg_is_user_object
+-------------------
+ t
+(1 row)
+
---
--- Cleanup resources
---
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 8e06248eb5..3b8f0316fe 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -210,6 +210,10 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
pg_get_object_address(typ, nms, ioa.args) as addr2
ORDER BY addr1.classid, addr1.objid, addr1.objsubid;
+-- identity if the object is built-in or user-defined
+SELECT pg_is_user_object('pg_class'::regclass);
+SELECT pg_is_user_object('trig'::regproc);
+
---
--- Cleanup resources
---
On 2020/03/04 19:14, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 18:57, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/03/04 18:36, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 18:02, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/03/04 17:05, Masahiko Sawada wrote:
On Wed, 4 Mar 2020 at 16:43, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/02/05 20:26, Masahiko Sawada wrote:
Hi,
User can create database objects such as functions into pg_catalog.
But if I'm not missing something, currently there is no
straightforward way to identify if the object is a user created object
or a system object which is created during initdb. If we can do that
user will be able to check if malicious functions are not created in
the database, which is important from the security perspective.The function that you are proposing is really enough for this use case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as superusers
and not give the permission on system catalogs to such users.I think there is still insider threats. As long as we depend on
superuser privilege to do some DBA work, a malicious DBA might be able
to log in as superuser and modify oid.Yes. But I'm sure that DBA has already considered the measures
againt such threads. Otherwise malicious users can do anything
more malicious rather than changing oid.Agreed. So that's not a serious problem in practice but we cannot say
the checking by pg_is_user_object() is totally enough for checking
whether malicious object exists or not. Is that right?
Yes.
My opinion is that, if malious users are not allowed to log in
as superusers and the admin give no permission on the system
schema/catalog to them, checking whether the object is defined
under pg_catalog schema or not is enough for your purpose.
Because they are also not allowed to create the object under
pg_catalog. pg_is_user_object() seems not necessary.
OTOH, if you address the case where malicious users can create
the object under pg_catalog, of course, checking whether
the object is defined under pg_catalog schema or not is enough
for the purpose. But pg_is_user_object() is also not enough
because such users can change oid.
Regards,
--
Fujii Masao
NTT DATA CORPORATION
Advanced Platform Technology Group
Research and Development Headquarters
At Wed, 4 Mar 2020 21:07:05 +0900, Fujii Masao <masao.fujii@oss.nttdata.com> wrote in
The function that you are proposing is really enough for this use
case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as
superusers
and not give the permission on system catalogs to such users.I think there is still insider threats. As long as we depend on
superuser privilege to do some DBA work, a malicious DBA might be able
to log in as superuser and modify oid.Yes. But I'm sure that DBA has already considered the measures
againt such threads. Otherwise malicious users can do anything
more malicious rather than changing oid.Agreed. So that's not a serious problem in practice but we cannot say
the checking by pg_is_user_object() is totally enough for checking
whether malicious object exists or not. Is that right?Yes.
My opinion is that, if malious users are not allowed to log in
as superusers and the admin give no permission on the system
schema/catalog to them, checking whether the object is defined
under pg_catalog schema or not is enough for your purpose.
Because they are also not allowed to create the object under
pg_catalog. pg_is_user_object() seems not necessary.OTOH, if you address the case where malicious users can create
the object under pg_catalog, of course, checking whether
the object is defined under pg_catalog schema or not is enough
for the purpose. But pg_is_user_object() is also not enough
because such users can change oid.
The discussion seems assuming the feature is related to some security
measure. But I think I haven't seen the objective or use case for the
feature. I don't see how we should treat them according the result
from the "user-defined objects detection" feature.
For example, we could decide a function whether to be pushed-out or
not to remote server on postgres_fdw. In this case, we need to ask "is
the behavior of this function known to us?", in short, "is this
function is predefined?". In this use case, we have no concern if DBA
have added some functions as "not user-defined", since it's their own
risk.
I don't come up with another use cases but, anyway, I think we need to
clarify the scope of the feature.
regads.
--
Kyotaro Horiguchi
NTT Open Source Software Center
On 2020/03/05 12:32, Kyotaro Horiguchi wrote:
At Wed, 4 Mar 2020 21:07:05 +0900, Fujii Masao <masao.fujii@oss.nttdata.com> wrote in
The function that you are proposing is really enough for this use
case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as
superusers
and not give the permission on system catalogs to such users.I think there is still insider threats. As long as we depend on
superuser privilege to do some DBA work, a malicious DBA might be able
to log in as superuser and modify oid.Yes. But I'm sure that DBA has already considered the measures
againt such threads. Otherwise malicious users can do anything
more malicious rather than changing oid.Agreed. So that's not a serious problem in practice but we cannot say
the checking by pg_is_user_object() is totally enough for checking
whether malicious object exists or not. Is that right?Yes.
My opinion is that, if malious users are not allowed to log in
as superusers and the admin give no permission on the system
schema/catalog to them, checking whether the object is defined
under pg_catalog schema or not is enough for your purpose.
Because they are also not allowed to create the object under
pg_catalog. pg_is_user_object() seems not necessary.OTOH, if you address the case where malicious users can create
the object under pg_catalog, of course, checking whether
the object is defined under pg_catalog schema or not is enough
for the purpose. But pg_is_user_object() is also not enough
because such users can change oid.The discussion seems assuming the feature is related to some security
measure. But I think I haven't seen the objective or use case for the
feature. I don't see how we should treat them according the result
from the "user-defined objects detection" feature.For example, we could decide a function whether to be pushed-out or
not to remote server on postgres_fdw. In this case, we need to ask "is
the behavior of this function known to us?", in short, "is this
function is predefined?". In this use case, we have no concern if DBA
have added some functions as "not user-defined", since it's their own
risk.I don't come up with another use cases but, anyway, I think we need to
clarify the scope of the feature.
Agreed. Also we would need to consider that the existing approach
(e.g., checking whether the object is defined under pg_catalog or not,
or seeing pg_stat_user_functions, _indexes, and _tables) is enough
for the use cases. If enough, new function might not be necessary.
If not enough, we might also need to reconsider the definitions of
pg_stat_user_xxx after considering the function.
Regards,
--
Fujii Masao
NTT DATA CORPORATION
Advanced Platform Technology Group
Research and Development Headquarters
On Wed, Mar 04, 2020 at 06:57:00PM +0900, Fujii Masao wrote:
Yes. But I'm sure that DBA has already considered the measures
againt such threads. Otherwise malicious users can do anything
more malicious rather than changing oid.
A superuser is by definition able to do anything on the system using
the rights of the OS user running the Postgres backend. One thing for
example is to take a base backup of the full instance, but you can do
much more interesting things once you have such rights. So I don't
quite get the line of arguments used on this thread regarding the
relation with somebody being malicious with superuser rights, and the
arguments about a superuser able to manipulate freely the catalog's
contents.
--
Michael
On Thu, 5 Mar 2020 at 13:23, Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/03/05 12:32, Kyotaro Horiguchi wrote:
At Wed, 4 Mar 2020 21:07:05 +0900, Fujii Masao <masao.fujii@oss.nttdata.com> wrote in
The function that you are proposing is really enough for this use
case?
What if malicious users directly change the oid of function
to < FirstNormalObjectId? Or you're assuming that malicious users will
never log in as superuser and not be able to change the oid?That's a good point! I'm surprised that user is allowed to update an
oid of database object. In addition, surprisingly we can update it to
0, which in turn leads the assertion failure:Since non-superusers are not allowed to do that by default,
that's not so bad? That is, to avoid such unexpected change of oid,
admin just should prevent malicious users from logging in as
superusers
and not give the permission on system catalogs to such users.I think there is still insider threats. As long as we depend on
superuser privilege to do some DBA work, a malicious DBA might be able
to log in as superuser and modify oid.Yes. But I'm sure that DBA has already considered the measures
againt such threads. Otherwise malicious users can do anything
more malicious rather than changing oid.Agreed. So that's not a serious problem in practice but we cannot say
the checking by pg_is_user_object() is totally enough for checking
whether malicious object exists or not. Is that right?Yes.
My opinion is that, if malious users are not allowed to log in
as superusers and the admin give no permission on the system
schema/catalog to them, checking whether the object is defined
under pg_catalog schema or not is enough for your purpose.
Because they are also not allowed to create the object under
pg_catalog. pg_is_user_object() seems not necessary.OTOH, if you address the case where malicious users can create
the object under pg_catalog, of course, checking whether
the object is defined under pg_catalog schema or not is enough
for the purpose. But pg_is_user_object() is also not enough
because such users can change oid.The discussion seems assuming the feature is related to some security
measure. But I think I haven't seen the objective or use case for the
feature. I don't see how we should treat them according the result
from the "user-defined objects detection" feature.For example, we could decide a function whether to be pushed-out or
not to remote server on postgres_fdw. In this case, we need to ask "is
the behavior of this function known to us?", in short, "is this
function is predefined?". In this use case, we have no concern if DBA
have added some functions as "not user-defined", since it's their own
risk.I don't come up with another use cases but, anyway, I think we need to
clarify the scope of the feature.Agreed. Also we would need to consider that the existing approach
(e.g., checking whether the object is defined under pg_catalog or not,
or seeing pg_stat_user_functions, _indexes, and _tables) is enough
for the use cases. If enough, new function might not be necessary.
If not enough, we might also need to reconsider the definitions of
pg_stat_user_xxx after considering the function.
Originally the motivation of this feature is that while studying PCI
DSS 2.2.5 I thought that a running PostgreSQL server is not able to
prove that there is no malicious function in database. PCI DSS 2.2.5
states "Remove all unnecessary functionality, such as scripts,
drivers, features, subsystems, file systems, and unnecessary web
servers." If we want to clarify unnecessary or malicious functions we
can check public schema and user schema but once a function is created
on pg_proc we cannot distinguish whether it's a built-in (i.g. safe)
function or not. I totally agree that if malicious someone logs in as
a superuser he/she can do anything more serious than changing catalog
contents but I wanted to have a way to prove soundness of running
database.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
At Thu, 5 Mar 2020 15:21:49 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
I don't come up with another use cases but, anyway, I think we need to
clarify the scope of the feature.Agreed. Also we would need to consider that the existing approach
(e.g., checking whether the object is defined under pg_catalog or not,
or seeing pg_stat_user_functions, _indexes, and _tables) is enough
for the use cases. If enough, new function might not be necessary.
If not enough, we might also need to reconsider the definitions of
pg_stat_user_xxx after considering the function.Originally the motivation of this feature is that while studying PCI
DSS 2.2.5 I thought that a running PostgreSQL server is not able to
prove that there is no malicious function in database. PCI DSS 2.2.5
states "Remove all unnecessary functionality, such as scripts,
drivers, features, subsystems, file systems, and unnecessary web
servers." If we want to clarify unnecessary or malicious functions we
can check public schema and user schema but once a function is created
on pg_proc we cannot distinguish whether it's a built-in (i.g. safe)
function or not. I totally agree that if malicious someone logs in as
a superuser he/she can do anything more serious than changing catalog
contents but I wanted to have a way to prove soundness of running
database.
Thanks for the elaboration. It doesn't seem to me as the
resposibility of PostgreSQL program. The same can be said to OSes.
I think the section is not saying that "keep you system only with
defaultly installed components", but "remove all features unncecessary
to your system even if it is defaultly installed as far as you can".
And whether A system is needing a feature or not cannot be the matter
of PostgreSQL or OSes.
So you need to remove some system-admistrative functions if you know
it is not required by your system in order to comform the
requirement. But they would be "non-user-defined" objects.
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
On Thu, 5 Mar 2020 at 16:36, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
At Thu, 5 Mar 2020 15:21:49 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
I don't come up with another use cases but, anyway, I think we need to
clarify the scope of the feature.Agreed. Also we would need to consider that the existing approach
(e.g., checking whether the object is defined under pg_catalog or not,
or seeing pg_stat_user_functions, _indexes, and _tables) is enough
for the use cases. If enough, new function might not be necessary.
If not enough, we might also need to reconsider the definitions of
pg_stat_user_xxx after considering the function.Originally the motivation of this feature is that while studying PCI
DSS 2.2.5 I thought that a running PostgreSQL server is not able to
prove that there is no malicious function in database. PCI DSS 2.2.5
states "Remove all unnecessary functionality, such as scripts,
drivers, features, subsystems, file systems, and unnecessary web
servers." If we want to clarify unnecessary or malicious functions we
can check public schema and user schema but once a function is created
on pg_proc we cannot distinguish whether it's a built-in (i.g. safe)
function or not. I totally agree that if malicious someone logs in as
a superuser he/she can do anything more serious than changing catalog
contents but I wanted to have a way to prove soundness of running
database.Thanks for the elaboration. It doesn't seem to me as the
resposibility of PostgreSQL program. The same can be said to OSes.I think the section is not saying that "keep you system only with
defaultly installed components", but "remove all features unncecessary
to your system even if it is defaultly installed as far as you can".
Agreed.
And whether A system is needing a feature or not cannot be the matter
of PostgreSQL or OSes.So you need to remove some system-admistrative functions if you know
it is not required by your system in order to comform the
requirement. But they would be "non-user-defined" objects.
I think normally users don't want to remove built-in functions because
they think these functions are trusted and it's hard to restore them
when they want later. So I thought user want to search functions that
is unnecessary but not a built-in function.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
At Thu, 5 Mar 2020 18:06:26 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
On Thu, 5 Mar 2020 at 16:36, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
At Thu, 5 Mar 2020 15:21:49 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
I don't come up with another use cases but, anyway, I think we need to
clarify the scope of the feature.Agreed. Also we would need to consider that the existing approach
(e.g., checking whether the object is defined under pg_catalog or not,
or seeing pg_stat_user_functions, _indexes, and _tables) is enough
for the use cases. If enough, new function might not be necessary.
If not enough, we might also need to reconsider the definitions of
pg_stat_user_xxx after considering the function.Originally the motivation of this feature is that while studying PCI
DSS 2.2.5 I thought that a running PostgreSQL server is not able to
prove that there is no malicious function in database. PCI DSS 2.2.5
states "Remove all unnecessary functionality, such as scripts,
drivers, features, subsystems, file systems, and unnecessary web
servers." If we want to clarify unnecessary or malicious functions we
can check public schema and user schema but once a function is created
on pg_proc we cannot distinguish whether it's a built-in (i.g. safe)
function or not. I totally agree that if malicious someone logs in as
a superuser he/she can do anything more serious than changing catalog
contents but I wanted to have a way to prove soundness of running
database.Thanks for the elaboration. It doesn't seem to me as the
resposibility of PostgreSQL program. The same can be said to OSes.I think the section is not saying that "keep you system only with
defaultly installed components", but "remove all features unncecessary
to your system even if it is defaultly installed as far as you can".Agreed.
And whether A system is needing a feature or not cannot be the matter
of PostgreSQL or OSes.So you need to remove some system-admistrative functions if you know
it is not required by your system in order to comform the
requirement. But they would be "non-user-defined" objects.I think normally users don't want to remove built-in functions because
they think these functions are trusted and it's hard to restore them
when they want later. So I thought user want to search functions that
is unnecessary but not a built-in function.
I'm not sure those who wants to introduce PCI-DSS are under a normal
sitautation, though:p
That seems beside the point. pg_read_file is known to be usable for
drawing out database files. If you leave the function alone, the
security officer (designer?) have to consider the possibility that
someone draws out files in the database system using the function and
have to plan the action for the threat. In that context,
built-in-or-not distinction is useless.
In the first place, if you assume that someone may install malicious
functions in the server after beginning operation, distinction by OID
doesn't work at all because who can illegally install a malicious
function also be able to modify its OID with quite low odds.
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
On Thu, 5 Mar 2020 at 18:39, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
At Thu, 5 Mar 2020 18:06:26 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
On Thu, 5 Mar 2020 at 16:36, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
At Thu, 5 Mar 2020 15:21:49 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
I don't come up with another use cases but, anyway, I think we need to
clarify the scope of the feature.Agreed. Also we would need to consider that the existing approach
(e.g., checking whether the object is defined under pg_catalog or not,
or seeing pg_stat_user_functions, _indexes, and _tables) is enough
for the use cases. If enough, new function might not be necessary.
If not enough, we might also need to reconsider the definitions of
pg_stat_user_xxx after considering the function.Originally the motivation of this feature is that while studying PCI
DSS 2.2.5 I thought that a running PostgreSQL server is not able to
prove that there is no malicious function in database. PCI DSS 2.2.5
states "Remove all unnecessary functionality, such as scripts,
drivers, features, subsystems, file systems, and unnecessary web
servers." If we want to clarify unnecessary or malicious functions we
can check public schema and user schema but once a function is created
on pg_proc we cannot distinguish whether it's a built-in (i.g. safe)
function or not. I totally agree that if malicious someone logs in as
a superuser he/she can do anything more serious than changing catalog
contents but I wanted to have a way to prove soundness of running
database.Thanks for the elaboration. It doesn't seem to me as the
resposibility of PostgreSQL program. The same can be said to OSes.I think the section is not saying that "keep you system only with
defaultly installed components", but "remove all features unncecessary
to your system even if it is defaultly installed as far as you can".Agreed.
And whether A system is needing a feature or not cannot be the matter
of PostgreSQL or OSes.So you need to remove some system-admistrative functions if you know
it is not required by your system in order to comform the
requirement. But they would be "non-user-defined" objects.I think normally users don't want to remove built-in functions because
they think these functions are trusted and it's hard to restore them
when they want later. So I thought user want to search functions that
is unnecessary but not a built-in function.I'm not sure those who wants to introduce PCI-DSS are under a normal
sitautation, though:pThat seems beside the point. pg_read_file is known to be usable for
drawing out database files. If you leave the function alone, the
security officer (designer?) have to consider the possibility that
someone draws out files in the database system using the function and
have to plan the action for the threat. In that context,
built-in-or-not distinction is useless.
So how do you check if unnecessary, malicious or unauthorized function
exists in database after that, for example when periodical security
check? Functions defined after initdb must be checked under user's
responsibility but since normally there are many built-in functions in
pg_proc the check in pg_proc could be cumbersome. So the idea of this
feature is to make that check easier by marking built-in functions.
In the first place, if you assume that someone may install malicious
functions in the server after beginning operation, distinction by OID
doesn't work at all because who can illegally install a malicious
function also be able to modify its OID with quite low odds.
Yes, that's what Fujii-san also pointed out. It's better to find a way
to distinct functions while not relying on OID.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
At Sun, 8 Mar 2020 11:55:06 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
On Thu, 5 Mar 2020 at 18:39, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
At Thu, 5 Mar 2020 18:06:26 +0900, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote in
On Thu, 5 Mar 2020 at 16:36, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
I think normally users don't want to remove built-in functions because
they think these functions are trusted and it's hard to restore them
when they want later. So I thought user want to search functions that
is unnecessary but not a built-in function.I'm not sure those who wants to introduce PCI-DSS are under a normal
sitautation, though:pThat seems beside the point. pg_read_file is known to be usable for
drawing out database files. If you leave the function alone, the
security officer (designer?) have to consider the possibility that
someone draws out files in the database system using the function and
have to plan the action for the threat. In that context,
built-in-or-not distinction is useless.So how do you check if unnecessary, malicious or unauthorized function
exists in database after that, for example when periodical security
check? Functions defined after initdb must be checked under user's
responsibility but since normally there are many built-in functions in
pg_proc the check in pg_proc could be cumbersome. So the idea of this
feature is to make that check easier by marking built-in functions.
I think there's no easy way to accomplish it. If PostgreSQL
documentation says that "Yeah, the function tells if using the
function or feature complies the request of PCI-DSS 2.2.5!" and it
tells safe for all built-in functions, it is an outright lie even if
the server is just after initdb'ed.
Sparating from PCI-DSS and we document it just as "the function tells
if the function is built-in or not", it's true. (But I'm not sure
about its usage..)
I might be misunderstanding about the operation steps in your mind.
In the first place, if you assume that someone may install malicious
functions in the server after beginning operation, distinction by OID
doesn't work at all because who can illegally install a malicious
function also be able to modify its OID with quite low odds.Yes, that's what Fujii-san also pointed out. It's better to find a way
to distinct functions while not relying on OID.
And it is out-of-scope of PCI-DSS 2.2.5. It mentions design or
system-building time.
Apart from PCI-DSS, if you are concerning operation-time threats. If
once someone malicious could install a function to the server, I think
that kind of feature with any criteria no longer work as a
countermeasure for further threats. Maybe something like tripwire
would work. That is, maybe a kind of checksum over system catalogs.
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
On 4 Mar 2020, at 12:06, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote:
Attached updated patch that incorporated comments from Amit and Vignesh.
This patch fails to compile due to an Oid collision in pg_proc.dat. Please
submit a new version with an Oid from the recommended range for new patches:
8000-9999. See the below documentation page for more information on this.
https://www.postgresql.org/docs/devel/system-catalog-initial-data.html
I'm marking the entry Waiting on Author in the meantime.
cheers ./daniel
On 1 Jul 2020, at 14:15, Daniel Gustafsson <daniel@yesql.se> wrote:
On 4 Mar 2020, at 12:06, Masahiko Sawada <masahiko.sawada@2ndquadrant.com> wrote:
Attached updated patch that incorporated comments from Amit and Vignesh.
This patch fails to compile due to an Oid collision in pg_proc.dat. Please
submit a new version with an Oid from the recommended range for new patches:
8000-9999. See the below documentation page for more information on this.https://www.postgresql.org/docs/devel/system-catalog-initial-data.html
I'm marking the entry Waiting on Author in the meantime.
As no new patch has been presented, and the thread contains doubts over the
proposed functionality, I'm marking this returned with feedback.
cheers ./daniel