Thread: Is this a bug, possible security hole, or wrong assumption?

Is this a bug, possible security hole, or wrong assumption?

From
Mike Mascari
Date:
1. I have a query that looks like this:

SELECT 1
FROM v_offers
WHERE (f_isvalidoffer(v_offers.offer, 747) = 1);

2. The f_isvalidoffer() functions does this:

...
SELECT INTO target
v_offers.store
WHERE v_offers.offer = $1;

IF NOT FOUND THEN
 RAISE EXCEPTION ''f_isvalidoffer: Offer % does not exist'', $1
END IF;
...

3. v_offers is a view, which is defined as this:

SELECT *
FROM offers
WHERE deactive IS NULL;

4. offers is a table which is defined as this:

CREATE TABLE offers (
offer int4 not null,
store int4 not null,
active timestamp not null default now(),
deactive timestamp
);

5. The data in offers is (with X being timestamps):

offer | store | active | deactive
------+-------+--------+---------
   1      1        X
   2      1        X
   3      1        X         X

6. SELECT offer from offers correctly yields:

1
2
3

7. SELECT offer from v_offer correctly yields:

1
2

8. The query in #1 will raise an exception because:

explain select 1
from v_offers
where (f_isvalidoffer(v_offers.offer, 747) = 1);

NOTICE:  QUERY PLAN:

Scan on offers  (cost=0.00..25.00 rows=1 width=0)

EXPLAIN

What appears to me is that the rewriter is just tacking the IS NULL test
onto the parsed query. As a result, a function is called with data from
a view before the evaluation of IS NULL removes those rows from the
selection process. Is that right? If so, is that a security problem? I
have heard people recommend the use of views as a way of implementing
row security. If I have a table salaries:

CREATE TABLE salaries (
userid name not null,
amount float8 not null
);

CREATE VIEW v_salaries AS
SELECT * FROM salaries
WHERE userid = CURRENT_USER();

I then grant SELECT on v_salaries to user1, so that user1 can see his
salary. But if user1 writes a function like:

CREATE FUNCTION f_logsalary(name, float8) RETURNS int4 AS '

BEGIN

INSERT INTO logs
VALUES ($1, $2);

RETURN 1;

END;

' LANGUAGE 'plpgsql';

executes a select like:

SELECT 1
FROM v_salaries
WHERE (f_logsalary(v_salaries.userid, v_salaries.salary) = 1);

user1 can then see everyone's salaries by querying logs. Any hints?

Mike Mascari
mascarm@mascari.com

Re: Is this a bug, possible security hole, or wrong assumption?

From
Tom Lane
Date:
Mike Mascari <mascarm@mascari.com> writes:
> What appears to me is that the rewriter is just tacking the IS NULL test
> onto the parsed query. As a result, a function is called with data from
> a view before the evaluation of IS NULL removes those rows from the
> selection process. Is that right? If so, is that a security problem?

You're essentially asking for a guarantee about the order of evaluation
of WHERE clauses.  There is no such guarantee, and won't be because it
would be a crippling blow to performance.  For example, consider

    create table tab (k int primary key, d text);
    create view v as select * from tab where d is not null;
    select * from v where k = 42;

If the not-null clause must be evaluated before the outer where,
then this query will be unable to use an indexscan on k.  See related
discussion a week or so ago (in pgsql-general if memory serves).

We could possibly tweak the optimizer so that the where-clauses pulled
up from the view are evaluated first in cases where there is no
plan-driven reason to do it the other way 'round, but I doubt this would
provide much security.

            regards, tom lane

Re: Is this a bug, possible security hole, or wrong assumption?

From
Tom Lane
Date:
"Sander Steffann" <sander@steffann.nl> writes:
> But he is right in that his trick works. This proves that views can not be
> safely used for security, which is an important thing to realise...

A different way to look at it is that the privilege of creating
functions shouldn't be handed out willy-nilly.  The trick of hiding
recording operations in a function can be used in other ways besides
this one.

            regards, tom lane

Re: Is this a bug, possible security hole, or wrong assumption?

From
"Sander Steffann"
Date:
Hi,

> Mike Mascari <mascarm@mascari.com> writes:
> > What appears to me is that the rewriter is just tacking the IS NULL test
> > onto the parsed query. As a result, a function is called with data from
> > a view before the evaluation of IS NULL removes those rows from the
> > selection process. Is that right? If so, is that a security problem?
>
> You're essentially asking for a guarantee about the order of evaluation
> of WHERE clauses.  There is no such guarantee, and won't be because it
> would be a crippling blow to performance.

But he is right in that his trick works. This proves that views can not be
safely used for security, which is an important thing to realise...

Sander.



Re: Is this a bug, possible security hole, or wrong

From
Mike Mascari
Date:
Tom Lane wrote:
>
> "Sander Steffann" <sander@steffann.nl> writes:
> > But he is right in that his trick works. This proves that views can not be
> > safely used for security, which is an important thing to realise...
>
> A different way to look at it is that the privilege of creating
> functions shouldn't be handed out willy-nilly.  The trick of hiding
> recording operations in a function can be used in other ways besides
> this one.

Tom,

If a user has permissions to write PL/SQL functions, and the statistics
collector is running with STATS_COMMAND_STRING = true, could not that
user "log" other users' queries using the same technique I described by
querying pg_stat_activity? If so, isn't the labeling of PL/SQL (or
PL/Tcl, PL/Perl, etc.) as 'TRUSTED' an overstatement? ;-)

Mike Mascari
mascarm@mascari.com

Re: Is this a bug, possible security hole, or wrong

From
Mike Mascari
Date:
Tom Lane wrote:
>
> Mike Mascari <mascarm@mascari.com> writes:
> > What appears to me is that the rewriter is just tacking the IS NULL test
> > onto the parsed query. As a result, a function is called with data from
> > a view before the evaluation of IS NULL removes those rows from the
> > selection process. Is that right? If so, is that a security problem?
>
> You're essentially asking for a guarantee about the order of evaluation
> of WHERE clauses.  There is no such guarantee, and won't be because it
> would be a crippling blow to performance.  For example, consider
>
>         create table tab (k int primary key, d text);
>         create view v as select * from tab where d is not null;
>         select * from v where k = 42;
>
> If the not-null clause must be evaluated before the outer where,
> then this query will be unable to use an indexscan on k.  See related
> discussion a week or so ago (in pgsql-general if memory serves).
>
> We could possibly tweak the optimizer so that the where-clauses pulled
> up from the view are evaluated first in cases where there is no
> plan-driven reason to do it the other way 'round, but I doubt this would
> provide much security.

It seems to me that the condition which must be satisfied is this:

If the attribute of a view is used in a user-defined function, then the
conditional expressions associated with the WHERE condition of the view
*must* be evaluated before the user-defined function is called (if
ever). That would not limit the use of an index scan in the above
example. Other RDBMS allow for both server-side functions and the use of
views for security. In fact, SQL92 states (as an example):

In each catalog in an SQL-environment, there is a schema, the
Information Schema, with the name INFORMATION_SCHEMA, containing a
number of view descriptors, one base table descriptor, and several
domain descriptors. The data accessible through these views is a
representation of all of the descriptors in all of the schemas in that
catalog. The <query expression> of each view ensures that a given user
can access only those rows of the view that represent descriptors on
which he has privileges.

Now obviously PostgreSQL does not yet have the INFORMATION_SCHEMA, but
the statement implies that view implementations ought to be able to
provide for row security...

Mike Mascari
mascarm@mascari.com

Re: Is this a bug, possible security hole, or wrong

From
Tom Lane
Date:
Mike Mascari <mascarm@mascari.com> writes:
> If a user has permissions to write PL/SQL functions, and the statistics
> collector is running with STATS_COMMAND_STRING = true, could not that
> user "log" other users' queries using the same technique I described by
> querying pg_stat_activity?

Not unless he's superuser.

            regards, tom lane

Re: Is this a bug, possible security hole, or wrong

From
Mike Mascari
Date:
I wrote:
>
> Tom Lane wrote:
> >
> > You're essentially asking for a guarantee about the order of evaluation
> > of WHERE clauses.  There is no such guarantee, and won't be because it
> > would be a crippling blow to performance.
>
> It seems to me that the condition which must be satisfied is this:
>
> If the attribute of a view is used in a user-defined function, then the
> conditional expressions associated with the WHERE condition of the view
> *must* be evaluated before the user-defined function is called (if
> ever). That would not limit the use of an index scan in the above
> example. Other RDBMS allow for both server-side functions and the use of
> views for security.

I apologize. The pg_stat_activity view is a good example of using views
atop functions to provide security. Its not exactly obvious, but it can
be done. And with the SRFs coming, I suppose fixing views is a pretty
low priority...

Mike Mascari
mascarm@mascari.com

Re: Is this a bug, possible security hole, or wrong

From
Tom Lane
Date:
Mike Mascari <mascarm@mascari.com> writes:
> I apologize. The pg_stat_activity view is a good example of using views
> atop functions to provide security. Its not exactly obvious, but it can
> be done. And with the SRFs coming, I suppose fixing views is a pretty
> low priority...

I've applied the attached patch, which changes the behavior in your
example case.  However, in general I do not think it's possible or
desirable to guarantee anything about order of evaluation of WHERE
clauses.

            regards, tom lane

*** src/backend/optimizer/plan/planner.c.orig    Sat May 18 14:49:41 2002
--- src/backend/optimizer/plan/planner.c    Thu Jun 13 11:01:09 2002
***************
*** 656,662 ****
                  if (childlen <= 1 || (childlen + myothers) <= geqo_rels / 2)
                  {
                      newlist = nconc(newlist, subf->fromlist);
!                     f->quals = make_and_qual(f->quals, subf->quals);
                  }
                  else
                      newlist = lappend(newlist, child);
--- 656,662 ----
                  if (childlen <= 1 || (childlen + myothers) <= geqo_rels / 2)
                  {
                      newlist = nconc(newlist, subf->fromlist);
!                     f->quals = make_and_qual(subf->quals, f->quals);
                  }
                  else
                      newlist = lappend(newlist, child);