POC for a function trust mechanism

Started by Tom Laneover 7 years ago12 messageshackers
Jump to latest
#1Tom Lane
tgl@sss.pgh.pa.us

This is sort of a counter-proposal to Noah's discussion of search path
security checking in <20180805080441.GH1688868@rfd.leadboat.com>.
(There's no technical reason we couldn't do both things, but I think
this'd be more useful to most people.)

Some back story here is that the PG security team has been aware of the
issues in CVE-2018-1058 for an embarrassing number of years, and we'd
been vainly working to find a fix that was both non-invasive to users
and practical to back-patch. Eventually our hands were forced by an
outside security researcher who discovered some of those problems, and
naturally wanted to publish on a fairly short time scale. So we ended
up with the decidedly not non-invasive approach of locking down
search_path in especially critical places, and otherwise telling people
that they had to worry about this themselves. Of the various ideas that
we'd kicked around and not been able to finish, the one that seemed most
promising to me was to invent a "function trust" mechanism.

The core idea here is to prevent security problems not by changing an
application's rules for operator/function name resolution, but by
detecting an attempted compromise and preventing the trojan-horse code
from being executed. Essentially, a user or application is to declare
a list of roles that it trusts functions owned by, and the system will
then refuse to execute functions owned by other not-trusted roles.
So, if $badguy creates a trojan-horse operator and manages to capture
a call from your SQL code, he'll nonetheless not be able to execute
code as you.

To reduce the overhead of the mechanism and chance of unintentionally
breaking things, superuser-owned functions (particularly, all built-in
functions) are always trusted by everybody. A superuser who wants to
become you can do so trivially, with no need for a trojan horse, so
this restriction isn't creating any new security hole.

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

So we'd kind of decided that the GUC solution wasn't good enough, but
it didn't seem like catalog additions would be feasible as a back-patched
security fix, which is why this didn't go anywhere. But it could work
as a new feature.

Anyway, I had written a small POC that did use a GUC for this, and just
checked function calls without any attempts to switch the active
trusted_roles setting in places like autovacuum. I've rebased it up to
HEAD and here it is.

regards, tom lane

Attachments:

trusted-roles-0.1.patchtext/x-diff; charset=us-ascii; name=trusted-roles-0.1.patchDownload+219-18
#2Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#1)
Re: POC for a function trust mechanism

On Wed, Aug 8, 2018 at 01:15:38PM -0400, Tom Lane wrote:

This is sort of a counter-proposal to Noah's discussion of search path
security checking in <20180805080441.GH1688868@rfd.leadboat.com>.
(There's no technical reason we couldn't do both things, but I think
this'd be more useful to most people.)

Yes, the query from Noah below confirmed that schema qualification just
isn't a realistic approach:

CREATE FUNCTION latitude(earth)
RETURNS float8
LANGUAGE SQL
IMMUTABLE STRICT
PARALLEL SAFE
AS 'SELECT CASE
WHEN @cube_schema(at)(dot)cube_ll_coord($1, 3) OPERATOR(pg_catalog./)
@extschema(at)(dot)earth() OPERATOR(pg_catalog.<) -1 THEN -90::pg_catalog.float8
WHEN @cube_schema(at)(dot)cube_ll_coord($1, 3) OPERATOR(pg_catalog./)
@extschema(at)(dot)earth() OPERATOR(pg_catalog.>) 1 THEN 90::pg_catalog.float8
ELSE pg_catalog.degrees(pg_catalog.asin(@cube_schema(at)(dot)cube_ll_coord($1, 3)
OPERATOR(pg_catalog./) @extschema(at)(dot)earth()))
END';

Of course, with the limitations of backpatching and security-only
discussion, that was the best we could do in the past.

The core idea here is to prevent security problems not by changing an
application's rules for operator/function name resolution, but by
detecting an attempted compromise and preventing the trojan-horse code
from being executed. Essentially, a user or application is to declare
a list of roles that it trusts functions owned by, and the system will
then refuse to execute functions owned by other not-trusted roles.
So, if $badguy creates a trojan-horse operator and manages to capture
a call from your SQL code, he'll nonetheless not be able to execute
code as you.

Yes, this is the only reasonable approach I can think of.

To reduce the overhead of the mechanism and chance of unintentionally
breaking things, superuser-owned functions (particularly, all built-in
functions) are always trusted by everybody. A superuser who wants to
become you can do so trivially, with no need for a trojan horse, so
this restriction isn't creating any new security hole.

Agreed.

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

Yes, works for me.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

I can't think of any other places we do transitive permissions, except
for role membership. I don't see the logic in adding such transitivity
to function/operator calls, or even a per-function GUC. I assume most
sites have a small number of extensions installed by a predefined group
of users, usually superusers. If there is a larger group, a group role
should be created and those people put in the role, and the group role
trusted.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#3David Kohn
djk447@gmail.com
In reply to: Bruce Momjian (#2)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 12:11 PM Bruce Momjian <bruce@momjian.us> wrote:

...

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

Yes, works for me.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

I can't think of any other places we do transitive permissions, except
for role membership. I don't see the logic in adding such transitivity
to function/operator calls, or even a per-function GUC. I assume most
sites have a small number of extensions installed by a predefined group
of users, usually superusers. If there is a larger group, a group role
should be created and those people put in the role, and the group role
trusted.

I am wondering how this will interact with the inheritance of roles. For
instance, if two users are members of the same role, and one creates a
function the expectation would be that other users in the same role will
not trust that function. However, do I trust functions that are owned by
the roles that I am a member of? Or do I have to list any nested roles
explicitly? If the former, I suppose we'd have to modify how alter function
set owner works. It is currently allowed for roles that you are a member of
(and would then create a security hole). However, not trusting functions
owned by roles that I am a member of seems to also be a bit
counterintuitive.
Best,
David

#4Bruce Momjian
bruce@momjian.us
In reply to: David Kohn (#3)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 02:12:41PM -0400, David Kohn wrote:

On Thu, Aug 9, 2018 at 12:11 PM Bruce Momjian <bruce@momjian.us> wrote:
I can't think of any other places we do transitive permissions, except
for role membership.� I don't see the logic in adding such transitivity
to function/operator calls, or even a per-function GUC.� I assume most
sites have a small number of extensions installed by a predefined group
of users, usually superusers. If there is a larger group, a group role
should be created and those people put in the role, and the group role
trusted.

I am wondering how this will interact with the inheritance of roles. For
instance, if two users are members of the same role, and one creates a function
the expectation would be that other users in the same role will not trust that
function.

Well, right now, if you want to give members of a role rights to
something, you have to specifically grant rights to that role. I would
assume the same thing would happen here --- if you want to trust a group
role, you have to mention that group role in the GUC list (not
function-level GUC).

Do we allow any GUC on a function? Would not allowing this be confusing?

If we did transitive permissions, I could trust someone, and that person
could call a function of someone else they trust, and after a while you
don't know who you are trusting, which is why I think complex setups
like that are unwise.

However, do I trust functions that are owned by the roles that I am a
member of? Or do I have to list any nested roles explicitly? If the former, I
suppose we'd have to modify how alter function set owner works. It is currently
allowed for roles that you are a member of (and would then create a security
hole). However, not trusting functions owned by roles that I am a member of
seems to also be a bit counterintuitive.

Well, if someone adds me to the 'bad' role, do I have any control over
that? Seems someone adding me to their role is not something I am
requesting. Let's look at the docs on GRANT ROLE:

GRANT on Roles
This variant of the GRANT command grants membership in a role to
one or more other roles. Membership in a role is significant because
it conveys the privileges granted to a role to each of its members.

If WITH ADMIN OPTION is specified, the member can in turn grant
membership in the role to others, and revoke membership in the role
as well. Without the admin option, ordinary users cannot do that. A
role is not considered to hold WITH ADMIN OPTION on itself, but it
may grant or revoke membership in itself from a database session
where the session user matches the role. Database superusers can
grant or revoke membership in any role to anyone. Roles having
CREATEROLE privilege can grant or revoke membership in any role
that is not a superuser.

Unlike the case with privileges, membership in a role cannot be
granted to PUBLIC. Note also that this form of the command does
not allow the noise word GROUP.

The point is that it is granting the role _access_ to something, not
something trust that the role accepts. The WITH ADMIN OPTION would
allow ordinary users to add roles for whoever they want to attack.

Basically, as it is now, someone adding me to their role membership has
no downside for me. To trust my own role membership adds a downside to
role membership that I don't think we want to do --- it makes role
membership too complex in what it grants _and_ trusts.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#5Nico Williams
nico@cryptonector.com
In reply to: Tom Lane (#1)
Re: POC for a function trust mechanism

On Wed, Aug 08, 2018 at 01:15:38PM -0400, Tom Lane wrote:

This is sort of a counter-proposal to Noah's discussion of search path
security checking in <20180805080441.GH1688868@rfd.leadboat.com>.
(There's no technical reason we couldn't do both things, but I think
this'd be more useful to most people.)

So, this is why I always fully-qualify all references to functions,
tables, etc. I also always set a search_path on each function just in
case I accidentally leave a non-fully-qualified symbol.

I would like to have a way to request that all non-fully-qualified
symbols be resolved at function create/replace time and that the
resolution results be made permanent for the function. If I have
several schemas in a search_path at function definition time, this would
not allow me to move dependencies around without replacing the
dependents -- that's OK for me.

Nico
--

#6David Kohn
djk447@gmail.com
In reply to: Bruce Momjian (#4)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 3:04 PM Bruce Momjian <bruce@momjian.us> wrote:

Well, right now, if you want to give members of a role rights to
something, you have to specifically grant rights to that role. I would
assume the same thing would happen here --- if you want to trust a group
role, you have to mention that group role in the GUC list (not
function-level GUC).

Sure, but if I grant execute on a function to a role, members of that role
will be able to execute that function. Now, each member will (potentially)
need to update their trust list before doing that. Which seems a bit odd.
Or will I be able to modify the some sort of default trust list of the
group role? If not, it seems like it could be an administrative nightmare,
if so there are potential issues with who is allowed to modify the list of
trusted users that then gets inherited.

...

Basically, as it is now, someone adding me to their role membership has
no downside for me. To trust my own role membership adds a downside to
role membership that I don't think we want to do --- it makes role
membership too complex in what it grants _and_ trusts.

Makes sense, and I can see how that could get out of hand in terms of

figuring out who you trust. I guess I don't know of other cases where this
concept of trusting comes about in our current permissions system? And it
seems to introduce a lot of odd cases where you end up with a sort of
permissions error or I guess a trust error in this case.

One possibility that might help this would be to only use the check this if
a) the user who created the function isn't in the trust list and b) there
is a function with the same name and equivalent argument classes that would
be called if you weren't to call the untrusted user's function. So it is
only used for disambiguation.

Best,
David

#7Bruce Momjian
bruce@momjian.us
In reply to: David Kohn (#6)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 04:01:09PM -0400, David Kohn wrote:

On Thu, Aug 9, 2018 at 3:04 PM Bruce Momjian <bruce@momjian.us> wrote:

Well, right now, if you want to give members of a role rights to
something, you have to specifically grant rights to that role.� I would
assume the same thing would happen here --- if you want to trust a group
role, you have to mention that group role in the GUC list (not
function-level GUC).

Sure, but if I grant execute on a function to a role, members of that role will
be able to execute that function. Now, each member will (potentially) need to
update their trust list before doing that. Which seems a bit odd. Or will I be

Look at your wording above, "I grant execute" --- you are opening up
permissions to them. There is no approval on their part that signifies
they should trust you. If I open permissions for a file on my web
server, it doesn't mean people should trust me more than before.

able to modify the some sort of default trust list of the group role? If not,
it seems like it could be an administrative nightmare, if so there are
potential issues with who is allowed to modify the list of trusted users that
then gets inherited.

We certainly don't want to double-down on extending trust by allowing
someone to modify someone else's trusted role list. Practically, if you
are opening up permissions to someone, you will need to create a group
that you both belong to first, and have them trust the group, or they
can trust you directly. The benefit of a group role is that other
people can be added to that group without having to modify the trusted
role list. Basically, the function caller is trusting whoever controls
membership to that group role. This is different from having someone
trusting a role just because they were added to it (perhaps without
their knowledge).

Basically, as it is now, someone adding me to their role membership has
no downside for me.� To trust my own role membership adds a downside to
role membership that I don't think we want to do --- it makes role
membership too complex in what it grants _and_ trusts.

Makes sense, and I can see how that could get out of hand in terms of figuring
out who you trust. I guess I don't know of other cases where this concept of
trusting comes about in our current permissions system? And it seems to
introduce a lot of odd cases where you end up with a sort of permissions error
or I guess a trust error in this case.�

Yep.

One possibility that might help this would be to only use the check this if a)
the user who created the function isn't in the trust list and b) there is a
function with the same name and equivalent argument classes that would be
called if you weren't to call the untrusted user's function. So it is only used
for disambiguation.�

You can't do that because if you do, someone creating a ambiguous
function would cause a denial-of-service attack --- better to fail at
the time of the first function, when you are likely watching the output.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#8David Kohn
djk447@gmail.com
In reply to: Bruce Momjian (#7)
Re: POC for a function trust mechanism

We certainly don't want to double-down on extending trust by allowing
someone to modify someone else's trusted role list. Practically, if you
are opening up permissions to someone, you will need to create a group
that you both belong to first, and have them trust the group, or they
can trust you directly. The benefit of a group role is that other
people can be added to that group without having to modify the trusted
role list. Basically, the function caller is trusting whoever controls
membership to that group role. This is different from having someone
trusting a role just because they were added to it (perhaps without
their knowledge).

I think if you trust a group role you are implicitly trusting any members
of that group, because one can always alter the function owner from
themselves to the group role, because they are a member of that group. So
what you'd need to do is create a special group role that only owned the
functions and then not make anyone an actual member of that group, but you
could trust that group role. Then a separate group role that everyone would
be a member of and you'd do grants from the first role to the second.
So for what you proposed, if you are opening up permissions to someone by
using a role that you are both members of, then you implicitly open up
permissions from them to you as well.

Anyway, I guess all of this seems to introduce a lot more complexity into
an already complex permissions management system...is this all about the
public schema? Can we just make create function/operator etc something you
have to grant even in the public schema? It seems like that could be
significantly more user friendly than this. Or otherwise, would functions
owned by the database or schema owner be exempt from this? Because there
are many setups where people try to avoid superuser usage by creating
database or schema owner users who can do things like create function,
which a normal users can now use. Would checks be skipped if the function
call is schema qualified because then there's no reasonable way to think
that someone is being fooled about which function they are executing?

Best,
David

#9Isaac Morland
isaac.morland@gmail.com
In reply to: David Kohn (#8)
Re: POC for a function trust mechanism

On 9 August 2018 at 18:18, David Kohn <djk447@gmail.com> wrote:

Anyway, I guess all of this seems to introduce a lot more complexity into

an already complex permissions management system...is this all about the
public schema? Can we just make create function/operator etc something you
have to grant even in the public schema? It seems like that could be
significantly more user friendly than this.

Already true, if you do:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;

Which I do, in all my databases, and which is probably a good idea in most
scenarios.

Or otherwise, would functions owned by the database or schema owner be
exempt from this? Because there are many setups where people try to avoid
superuser usage by creating database or schema owner users who can do
things like create function, which a normal users can now use. Would checks
be skipped if the function call is schema qualified because then there's no
reasonable way to think that someone is being fooled about which function
they are executing?

At present, permissions are completely separate from ownership: your
ability to use an object does not depend on who owns what (I believe you
can even revoke your own rights to use your own stuff). I suspect changing
this is probably not a good idea.

#10Bruce Momjian
bruce@momjian.us
In reply to: David Kohn (#8)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 06:18:16PM -0400, David Kohn wrote:

We certainly don't want to double-down on extending trust by allowing
someone to modify someone else's trusted role list.� Practically, if you
are opening up permissions to someone, you will need to create a group
that you both belong to first, and have them trust the group, or they
can trust you directly.� The benefit of a group role is that other
people can be added to that group without having to modify the trusted
role list.� Basically, the function caller is trusting whoever controls
membership to that group role.� This is different from having someone
trusting a role just because they were added to it (perhaps without
their knowledge).

I think if you trust a group role you are implicitly trusting any members of
that group, because one can always alter the function owner from themselves to
the group role, because they are a member of that group. So what you'd need to
do is create a special group role that only owned the functions and then not
make anyone an actual member of that group, but you could trust that group
role. Then a separate group role that everyone would be a member of and you'd

Good point. I think you are right that if you trust a role, you trust
whoever controls role membership to add only trust-worthy people --- that
seems obvious because any member can create functions you will trust,
even if they don't change the function owner.

do grants from the first role to the second.�
So for what you proposed, if you are opening up permissions to someone by using
a role that you are both members of, then you implicitly open up permissions
from them to you as well.�

I don't see the value in that. My point is that you can't trust anyone
in a role you are a member of, by default --- you have to make that
decision as a user.

Anyway, I guess all of this seems to introduce a lot more complexity into an
already complex permissions management system...is this all about the public
schema? Can we just make create function/operator etc something you have to
grant even in the public schema? It seems like that could be significantly more
user friendly than this. Or otherwise, would functions owned by the database or

I think 95% of users are proabably creating these things as the
accessing user or super-user.

schema owner be exempt from this? Because there are many setups where people
try to avoid superuser usage by creating database or schema owner users who can
do things like create function, which a normal users can now use. Would checks
be skipped if the function call is schema qualified because then there's no
reasonable way to think that someone is being fooled about which function they
are executing?�

I think most setups will be pretty simple.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#11Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#1)
Re: POC for a function trust mechanism

On Wed, Aug 8, 2018 at 1:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

that they had to worry about this themselves. Of the various ideas that
we'd kicked around and not been able to finish, the one that seemed most
promising to me was to invent a "function trust" mechanism.

In the interest of giving credit where credit is due, I went back and
researched this. I discovered that you were the first one to suggest
this idea, and that Noah was the first to produce a completed patch
for it. That was in 2013.

(Also in 2013, I was arguing that the first thing we should do is fix
pg_dump and our in-core tools to be secure against this sort of
attack. Noah had a patch for it, in 2013. Five years later, that was
indeed the first thing we did. We should have done it back then.)

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

Back in 2013, I pointed out that we need to consider the case where
the database owner has done ALTER DATABASE .. SET search_path =
'malevolence', hoping that the superuser will log in and do something
that allows control to be captured. If trusted_roles is merely a GUC,
then the database owner could set that, too. If we want this solution
to be water-tight, I think we need a way to make very sure that a user
never uses a trust setting configured by someone else. We could
implement a special rule (as you proposed back at the time) that
limits the ability to ALTER DATABASE .. SET trusted_roles, but I'm a
little worried that there may be other ways that one user could end up
using a trusted_roles setting configured by somebody else. I wonder
whether thoroughly isolating the manner of configuring trust from the
GUC system might make it easier to avoid unpredictable interactions.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

Yeah, I think these are all good points. It seems natural for trust
to be a property of a role, for just the reasons that you mention.
However, there does also seem to be a use case for varying it
temporarily on a per-session or per-function basis, and I'm not
exactly sure how to cater to those needs. I have a feeling that it
would be really useful to attach a listed of trusted roles, and for
that matter also a search path, to the *lexical scope* of a function.

So we'd kind of decided that the GUC solution wasn't good enough, but
it didn't seem like catalog additions would be feasible as a back-patched
security fix, which is why this didn't go anywhere. But it could work
as a new feature.

Check.

Anyway, I had written a small POC that did use a GUC for this, and just
checked function calls without any attempts to switch the active
trusted_roles setting in places like autovacuum. I've rebased it up to
HEAD and here it is.

I wonder if Noah would like to rebase and post his version also.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#11)
Re: POC for a function trust mechanism

On Sun, Aug 12, 2018 at 10:40:30PM -0400, Robert Haas wrote:

On Wed, Aug 8, 2018 at 1:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

Yeah, I think these are all good points. It seems natural for trust
to be a property of a role, for just the reasons that you mention.
However, there does also seem to be a use case for varying it
temporarily on a per-session or per-function basis, and I'm not
exactly sure how to cater to those needs.

Yep. A GUC is great for src/bin sessions, since many of those applications
consistently want tight trust settings.

I wonder if Noah would like to rebase and post his version also.

Sure, attached (last merge at 757c518). The owner_trustcheck() header comment
is a good starting point for reading it. Tom Lane later asserted that it's
okay to perform the function trust check in fmgr_info_cxt_security() instead
of doing it in most fmgr_info() callers and some *FunctionCall* callers.
While I suspected he was right, I have not made that change in this rebase.
(I also haven't audited every fmgr_info() call added after -v1.) When I
shared -v1 with the security team, I included the following submission notes:

===

Here's an implementation. Design decisions:

1. A role trusts itself implicitly

2. Default pg_auth_trust entry for "public TRUST public"

This effectively disables the new restrictions; concerned administrators will
test their applications with it removed, then remove it.

3. Default pg_auth_trust entry for "public TRUST <bootstrap superuser>"

Almost everyone will leave this untouched.

4. Changing trust for a role requires ADMIN OPTION on the role.

Trust is the next step down from granting role membership. ("ALTER ROLE
public TRUST ..." does require superuser.)

5. Non-inheritance of trust

Trust is like a reverse permission; I find it helpful to think of "ALTER ROLE
foo TRUST bar" as "GRANT may_supply_functions_to_foo TO bar". It would be
reasonable to have that trust relationship be effective for INHERITS members
of bar; after all, they could always "ALTER FUNCTION ... OWNER TO bar". For
now, trust relationships are not subject to inheritance. This keeps things
simpler to understand and poses no major downsides. For similar reasons, I
have not made a role trust its own members implicitly.

6. Non-transitivity of trust

If I trust only alice, alice trusts only bob, and I call alice's function that
calls bob's function, should the call succeed? This is a tough one. In favor
of "yes", this choice would allow alice to refactor her function without
needing her callers to revise their trust settings. That is, she could switch
from bob's function to carol's function, and her callers would never notice.
My trust in alice is probably misplaced if she chooses her friends badly.

In favor of "no", making the trust relationship transitive seems to mix two
otherwise-separate concepts: alice's willingness to run code as herself and
alice's willingness to certify third-party functions to her friends. In
particular, current defects in the system are at odds with conflating those
concepts. Everyone should trust the bootstrap superuser, but it currently
owns functions like integer_pl_date that are not careful in what they call.
That closed the issue for me; trust is not transitive by default. Even so, I
think there would be value in a facility for requesting trust transitivity in
specific situations.

7. (Lack of) negative trust entries

There's no way to opt out of a PUBLIC trust relationship; until a superuser
removes the "public TRUST public" entry, no user can achieve anything with
ALTER USER [NO] TRUST. I considered adding the concept of a negative trust
relationship to remedy this problem; it could also be used to remove the
implicit self-trust for testing purposes. I have refrained; I don't know
whether the benefits would pay for the extra user-facing complexity.

8. Applicable roles during trust checks

We had examined whether the check could be looser for SECURITY DEFINER
functions, since those can't perform arbitrary actions as the caller. I
described some challenges then, but a deeper look turned up more: a SECURITY
DEFINER function can do things like "SET search_path = ..." that affect the
caller. Consequently, I concluded that all roles on the active call stack
should have a stake in whether a particular function owner is permitted. Each
trust check walks the call stack and checks every role represented there; if
any lacks the trust relationships to approve the newly-contemplated function
call, the call is rejected.

The stack traversal may and does stop at the edge of a security-restricted
operation; since those restrictions must prevent important session changes,
the stack entries active before the operation started need not be concerned
with the code running therein. A CREATE FUNCTION option specifying voluntary
use of a security-restricted context would provide the "facility for
requesting trust transitivity" mentioned earlier.

To make walking the role stack possible, I redid the API for changing the
current user ID. SetUserIdAndSecContext() gives way to PushTransientUser()
and PopTransientUser(); xact.c has its own little API for unwinding this stack
at (sub)transaction abort. This patch just removes the old APIs, but that
probably wouldn't cut it for the back branches; I do have a design sketch for
reintroducing backward-compatible versions.

9. Trust checks on other object types

Despite trust checks on functions, hostile users can make mischief with
something like "CREATE OPERATOR * (PROCEDURE = int4pl, LEFTARG = int4,
RIGHTARG = int4)". Therefore, I also caused trust checks to be enforced on
operators themselves. As I opine in the comments at owner_trustcheck(), where
to stop is more a judgement call than a science.

The following work remains TODO:
- Documentation for the new concepts, syntax and system catalog
- pg_dumpall support
- Testing the performance impact and perhaps adding a cache
- Backward-compatible versions of removed miscinit.c functions

Attachments:

proc-trust-v2.patchtext/plain; charset=us-asciiDownload+1401-362