Skip to Main Content
  • Questions
  • Behavior of Oracle VPD considering Stored Procedures

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Avik.

Asked: November 18, 2016 - 9:04 pm UTC

Last updated: November 23, 2016 - 5:09 am UTC

Version: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production

Viewed 1000+ times

You Asked

Hi Tom,

I have a particular question related to Oracle VPD (Virtual Private Database) behavior in case of Stored Procedures. I tried searching in Oracle Documentation as well as in the questions answered in AskTom, but couldn't find anything that might help me.

I'm working on a project where I need to restrict the Application users to specific records in a table.

For example, let's take a sample table ORGANIZATION_DETAILS


CREATE TABLE ORGANIZATION_DETAILS
  (ORG_KEY NUMBER, 
   ORG_NAME VARCHAR2(20)
  );



INSERT INTO ORGANIZATION_DETAILS VALUES
  (1,'ORG_A'
  );
INSERT INTO ORGANIZATION_DETAILS VALUES
  (2,'ORG_B'
  );
INSERT INTO ORGANIZATION_DETAILS VALUES
  (3,'ORG_C'
  );



The bare-bone functionality of Oracle VPD (when we talk about Row Level Security) is to append customized filters (WHERE Clause) to a skeleton Query fired by the user, based on the access level of that user.

So if User1 has a Policy defined for him which allows his to access only ORG_KEY = 1, he will only be able to SELECT the record with ORG_KEY = 1 from ORGANIZATION_DETAILS table.

When the User fires
SELECT * FROM ORGANIZATION_DETAILS
, VPD will append the filter
WHERE ORG_KEY = 1


The same goes for INSERT/UPDATE/DELETE statements as well

My question is : VPD works for explicit SQL statements, whereby the User writes S/I/U/D queries on some table and executes them.

However, let's assume there's a Stored procedure (PROC_UPDATE_ORG_DETAILS), which Updates a specific record in the ORGANIZATION_DETAILS table with some user-supplied value:


CREATE OR REPLACE PROCEDURE PROC_UPDATE_ORG_DETAILS(
    in_org_key      NUMBER,
    in_new_org_name VARCHAR2)
AS
BEGIN
  UPDATE ORGANIZATION_DETAILS
  SET ORG_NAME  = in_new_org_name
  WHERE ORG_KEY = in_org_key;
  COMMIT;
END;




EXEC PROC_UPDATE_ORG_DETAILS(1,'NEW_ORG_A');



SELECT * FROM ORGANIZATION_DETAILS;


ORG_KEY ORG_NAME
1 new_org_a
2 ORG_B
3 ORG_C

3 rows selected.



Since User1 has access only to ORG_KEY = 1, he can't update the record ORG_KEY = 1 via a direct Update statement.

However, can he execute this procedure by passing in_org_key as 2 and update that record (Of course provided he has execute privilege on that procedure)?

As far as I know, Procedures and Packages are created with AUTHID DEFINER by default.

If I want to restrict that user from accessing any other records except ORG_KEY = 1, will Creating the procedure with AUTHID CURRENT_USER be of any help?

In short, I'm not sure how VPD behaves when there's a Procedure in the picture. All I know is how VPD behaves with Explicit SQL queries.

Can you please help me with this?





My Oracle DB Version:

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production



with LiveSQL Test Case:

and Connor said...

Thanks for the livesql test case - because of that your question is the first one I'm looking at today :-)

Even *within* the procedure, you'll be protected, eg


SQL> conn connor/admin
Connected.

SQL> drop table ORGANIZATION_DETAILS purge;

Table dropped.

SQL>
SQL> CREATE TABLE ORGANIZATION_DETAILS
  2  (ORG_KEY NUMBER,
  3  ORG_NAME VARCHAR2(20)
  4  );

Table created.

SQL>
SQL> INSERT INTO ORGANIZATION_DETAILS VALUES
  2    (1,'ORG_A'
  3    );

1 row created.

SQL>
SQL> INSERT INTO ORGANIZATION_DETAILS VALUES
  2    (2,'ORG_B'
  3    );

1 row created.

SQL>
SQL> INSERT INTO ORGANIZATION_DETAILS VALUES
  2    (3,'ORG_C'
  3    );

1 row created.

SQL>
SQL> SELECT * FROM ORGANIZATION_DETAILS;

   ORG_KEY ORG_NAME
---------- --------------------
         1 ORG_A
         2 ORG_B
         3 ORG_C

3 rows selected.

SQL>
SQL> CREATE OR REPLACE
  2  PROCEDURE PROC_UPDATE_ORG_DETAILS(
  3      in_org_key      NUMBER,
  4      in_new_org_name VARCHAR2)
  5  AS
  6  BEGIN
  7    UPDATE ORGANIZATION_DETAILS
  8    SET ORG_NAME  = in_new_org_name
  9    WHERE ORG_KEY = in_org_key;
 10
 11    dbms_output.put_line('rows = '||sql%rowcount);
 12    COMMIT;
 13  END;
 14  /

Procedure created.

SQL>
SQL> grant execute on connor.PROC_UPDATE_ORG_DETAILS to demo;

Grant succeeded.

SQL>
SQL> conn demo/demo
Connected.

SQL> set serverout on
SQL> exec connor.PROC_UPDATE_ORG_DETAILS(1,'x');
rows = 1

PL/SQL procedure successfully completed.

SQL> exec connor.PROC_UPDATE_ORG_DETAILS(2,'x');
rows = 1

PL/SQL procedure successfully completed.

SQL> exec connor.PROC_UPDATE_ORG_DETAILS(3,'x');
rows = 1

PL/SQL procedure successfully completed.

SQL>
SQL> conn connor/admin
Connected.

SQL> create or replace
  2  function my_security_function(
  3                       p_schema in varchar2,
  4                       p_object in varchar2 )
  5  return varchar2
  6  as
  7  begin
  8      return 'ORG_KEY = 3';
  9  end;
 10  /

Function created.

SQL>
SQL> begin
  2      begin
  3              dbms_rls.drop_policy( user, 'ORGANIZATION_DETAILS', 'MY_POLICY' );
  4      exception
  5              when others then null;
  6      end;
  7      dbms_rls.add_policy
  8      ( object_schema => user,
  9        object_name => 'ORGANIZATION_DETAILS',
 10        policy_name => 'MY_POLICY',
 11        function_schema => user,
 12        policy_function => 'MY_SECURITY_FUNCTION',
 13        statement_types => 'select,insert,update,delete',
 14        update_check=>true);
 15  end;
 16  /

PL/SQL procedure successfully completed.

SQL>
SQL> conn demo/demo
Connected.

SQL> set serverout on
SQL> exec connor.PROC_UPDATE_ORG_DETAILS(1,'x');
rows = 0

PL/SQL procedure successfully completed.

SQL> exec connor.PROC_UPDATE_ORG_DETAILS(2,'x');
rows = 0

PL/SQL procedure successfully completed.

SQL> exec connor.PROC_UPDATE_ORG_DETAILS(3,'x');
rows = 1

PL/SQL procedure successfully completed.

SQL>


Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thanks Connor !

Avik Dutta, November 21, 2016 - 5:58 pm UTC

Thanks for the detailed demo, Connor! I've tried in my local DB what you showed, and it certainly works.

I have a followup question on this.

The procedure PROC_UPDATE_ORG_DETAILS will by default be created with AUTHID DEFINER. Now let's assume we have created the policy and defined it for demo user so that he can only access ORG_KEY = 3. As we've seem in your example, demo user can only update ORG_KEY = 3 and not the other rows.

However, as in our example, demo user doesn't have direct access to the table ORGANIZATION_DETAILS (He can't query the table in connor schema by logging in as demo, unless explicitly granted S/I/U/D access on the table, may be via a role), the VPD policy still holds good. As we can see, demo user can still update ORG_KEY = 3 row just by executing the procedure.

Isn't this a security loophole? I was thinking whether it will be better to declare the procedure as AUTHID CURRENT_USER, so that if demo user doesn't have S/I/U/D access on that table, even executing the procedure won't have any impact, although the VPD policy allows him to access ORG_KEY = 3.

The question may sound stupid (more so since English is not my native language) but still I'd like to know your opinion !

Appreciate your help again !
Connor McDonald
November 22, 2016 - 1:09 am UTC

"Isn't this a security loophole?"

No - it is exactly the opposite. In this case, we *granted* DEMO to right to execute that procedure. (Without the grant, they can do nothing). So by wrapping the access within a procedure, we have total control over DEMO's ability to update the ORGANIZATION_DETAILS table.

For example, if we wanted to audit the fact by logging the update in another table, or restrict it to only certain times of the day, or some other critical business requirement, we can put in the procedure, safe in the knowledge that this procedure is the *only* way that DEMO can ever update the table.

If you can wrap all access to the data within a PL/SQL layer, you have total control over security.

Makes Perfect sense

Avik Dutta, November 22, 2016 - 3:03 pm UTC

Thanks Connor for explaining that! I guess I was looking at it from a completely opposite perspective. But now it all makes sense ! Thanks again !

Connor McDonald
November 23, 2016 - 5:09 am UTC

glad we could help

More to Explore

Security

All of the vital components for a secure database are covered in the Security guide.