Thread: Restrict EXPLAIN (ANALYZE) for RLS and security_barrier views

Restrict EXPLAIN (ANALYZE) for RLS and security_barrier views

From
Laurenz Albe
Date:
Currently, it is pretty easy to subvert the restrictions imposed
by row-level security and security_barrier views.  All you have to
to is use EXPLAIN (ANALYZE) and see how many rows were filtered
out by the RLS policy or the view condition.

This is not considered a security bug (I asked), but I still think
it should be fixed.

My idea is to forbid EXPLAIN (ANALYZE) for ordinary users whenever
a statement uses either of these features.  But restricting it to
superusers would be too restrictive (with a superuser, you can never
observe RLS, since superusers are exempt) and it would also be
dangerous (you shouldn't perform DML on untrusted tables as superuser).

So I thought we could restrict the use of EXPLAIN (ANALYZE) in these
situations to the members of a predefined role.  That could be a new
predefined role, but I think it might as well be "pg_read_all_stats",
since that role allows you to view sensitive data like the MCV in
pg_statistic, and EXPLAIN (ANALYZE) can be seen as provideing executor
statistics.

Attached is a POC patch that implements that (documentation and
regression tests are still missing) to form a basis for a discussion.

There are a few things I would like feedback about:

- is it OK to use "pg_read_all_stats" for that?

- should the check be moved to standard_ExplainOneQuery()?

Yours,
Laurenz Albe

Attachment

Re: Restrict EXPLAIN (ANALYZE) for RLS and security_barrier views

From
Laurenz Albe
Date:
On Mon, 2024-05-06 at 16:46 +0200, Laurenz Albe wrote:
> Attached is a POC patch that implements that (documentation and
> regression tests are still missing) to form a basis for a discussion.

... and here is a complete patch with regression tests and documentation.

Yours,
Laurenz Albe

Attachment

Re: Restrict EXPLAIN (ANALYZE) for RLS and security_barrier views

From
Dean Rasheed
Date:
On Mon, 6 May 2024 at 15:46, Laurenz Albe <laurenz.albe@cybertec.at> wrote:
>
> Currently, it is pretty easy to subvert the restrictions imposed
> by row-level security and security_barrier views.  All you have to
> to is use EXPLAIN (ANALYZE) and see how many rows were filtered
> out by the RLS policy or the view condition.
>
> This is not considered a security bug (I asked), but I still think
> it should be fixed.
>
> My idea is to forbid EXPLAIN (ANALYZE) for ordinary users whenever
> a statement uses either of these features.
>

Hmm, but there are other ways to see how many rows were filtered out:

 - Use pg_stat_get_tuples_returned()
 - Use pg_class.reltuples
 - Use the row estimates from a plain EXPLAIN

and probably more.

Given that this isn't really a security bug, I think EXPLAIN should
probably be left as-is. Otherwise, you'd have to go round closing all
those other "holes" too.

Regards,
Dean



Re: Restrict EXPLAIN (ANALYZE) for RLS and security_barrier views

From
Laurenz Albe
Date:
On Tue, 2024-07-16 at 18:36 +0100, Dean Rasheed wrote:
> On Mon, 6 May 2024 at 15:46, Laurenz Albe <laurenz.albe@cybertec.at> wrote:
> >
> > Currently, it is pretty easy to subvert the restrictions imposed
> > by row-level security and security_barrier views.  All you have to
> > to is use EXPLAIN (ANALYZE) and see how many rows were filtered
> > out by the RLS policy or the view condition.
> >
> > This is not considered a security bug (I asked), but I still think
> > it should be fixed.
> >
> > My idea is to forbid EXPLAIN (ANALYZE) for ordinary users whenever
> > a statement uses either of these features.
>
> Hmm, but there are other ways to see how many rows were filtered out:
>
>  - Use pg_stat_get_tuples_returned()

That is true, but it will only work if there is no concurrent DML activity
on the database.  Still, I agree that it would be good to improve that,
for example by imposing a similar restriction on viewing these statistics.

>  - Use pg_class.reltuples

I don't accept that.  The estimated row count doesn't tell me anything
about the contents of the table.

>  - Use the row estimates from a plain EXPLAIN

I don't buy that either.  plain EXPLAIN will just tell you how many
rows PostgreSQL estimates for each node, and it won't tell you how
many rows will get filtered out by a RLS policy.  Also, these are only
estimates.

> and probably more.
>
> Given that this isn't really a security bug, I think EXPLAIN should
> probably be left as-is. Otherwise, you'd have to go round closing all
> those other "holes" too.

The only reason this is not a security bug is because we document these
weaknesses of row-level security and security barrier views.  I don't
think that should count as an argument against improving the situation.

With the exception of the table statistics you mention above, all the
other ways to derive knowledge about the "hidden" data leak very little
information, as does examining the query execution time (which is mentioned
in the documentation).

The information you can glean from EXPLAIN (ANALYZE) is much more
conclusive: "rows removed by filter: 1"

The patch that I am proposing certainly won't close all possibilities
to make an educated guess about "hidden" data, but it will close the
simplest way to reliably subvert RLS and security barrier views.

Is that not a worthy goal?
Shouldn't we plug the most glaring hole in PostgreSQL's data security
features?

I am aware that this change will make performance analysis more
cumbersome, but that's the price to pay for improved security.
I'd be ready to look at restricting pg_stat_get_tuples_returned(),
but perhaps that should be a separate patch.

Yours,
Laurenz Albe



Re: Restrict EXPLAIN (ANALYZE) for RLS and security_barrier views

From
Laurenz Albe
Date:
On Mon, 2024-05-06 at 16:46 +0200, Laurenz Albe wrote:
> Currently, it is pretty easy to subvert the restrictions imposed
> by row-level security and security_barrier views.  All you have to
> to is use EXPLAIN (ANALYZE) and see how many rows were filtered
> out by the RLS policy or the view condition.
>
> This is not considered a security bug (I asked), but I still think
> it should be fixed.
>
> My idea is to forbid EXPLAIN (ANALYZE) for ordinary users whenever
> a statement uses either of these features.  But restricting it to
> superusers would be too restrictive (with a superuser, you can never
> observe RLS, since superusers are exempt) and it would also be
> dangerous (you shouldn't perform DML on untrusted tables as superuser).
>
> So I thought we could restrict the use of EXPLAIN (ANALYZE) in these
> situations to the members of a predefined role.  That could be a new
> predefined role, but I think it might as well be "pg_read_all_stats",
> since that role allows you to view sensitive data like the MCV in
> pg_statistic, and EXPLAIN (ANALYZE) can be seen as provideing executor
> statistics.

After a discussion on the pgsql-security list (ZppxEDkyPd6tCSiq@msg.df7cb.de),
I am going to mark this patch as rejected.

The gist of that discussion was that even without EXPLAIN (ANALYZE),
it is easy enough for a determined attacker who can run arbitrary
SQL to subvert row-level security.
Therefore, restricting EXPLAIN (ANALYZE) will do more harm than good,
since it will make analyzing query performance harder without a
real security gain.

Yours,
Laurenz Albe