Changeset: 11a67b229142 for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=11a67b229142
Added Files:
        sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.py
        sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.err
        sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.out
Modified Files:
        sql/server/rel_updates.c
        sql/test/BugTracker-2020/Tests/All
        sql/test/astro/Tests/update.sql
        sql/test/astro/Tests/update.stable.out
        sql/test/mapi/Tests/sql_int128.stable.out.int128
Branch: Oct2020
Log Message:

Added test and fix for bug 7035, ie dont be too aggressive on select privileges 
on update queries. Reduce column visibility likewise in read queries


diffs (truncated from 451 to 300 lines):

diff --git a/sql/server/rel_updates.c b/sql/server/rel_updates.c
--- a/sql/server/rel_updates.c
+++ b/sql/server/rel_updates.c
@@ -1075,11 +1075,23 @@ update_table(sql_query *query, dlist *qn
                return sql_error(sql, 02, SQLSTATE(3F000) "UPDATE: no such 
schema '%s'", sname);
        t = find_table_on_scope(sql, &s, sname, tname);
        if (update_allowed(sql, t, tname, "UPDATE", "update", 0) != NULL) {
-               sql_rel *r = NULL, *bt = rel_basetable(sql, t, alias ? alias : 
tname), *res = bt;
+               sql_rel *r = NULL, *res = rel_basetable(sql, t, alias ? alias : 
tname);
 
+               /* We have always to reduce the column visibility because of 
the SET clause */
+               if (!table_privs(sql, t, PRIV_SELECT)) {
+                       sql_rel *nres = NULL;
+                       if (!(nres = rel_reduce_on_column_privileges(sql, res, 
t)) && opt_where) /* on global updates the user may be able to upd*/
+                               return sql_error(sql, 02, SQLSTATE(42000) 
"UPDATE: insufficient privileges for user '%s' to update table '%s'", 
sqlvar_get_string(find_global_var(sql, mvc_bind_schema(sql, "sys"), 
"current_user")), tname);
+                       if (!nres) {
+                               res->exps = sa_list(sql->sa); /* hasn't select 
privilege on any column, add just TID column to the list */
+                       } else {
+                               res = nres; /* add TID column to the columns it 
has permission to */
+                       }
+                       list_append(res->exps, exp_column(sql->sa, alias ? 
alias : tname, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 1));
+               }
                if (opt_from) {
                        dlist *fl = opt_from->data.lval;
-                       list *refs = list_append(new_exp_list(sql->sa), (char*) 
rel_name(bt));
+                       list *refs = list_append(new_exp_list(sql->sa), (char*) 
rel_name(res));
 
                        for (dnode *n = fl->h; n && res; n = n->next) {
                                sql_rel *fnd = table_ref(query, NULL, 
n->data.sym, 0, refs);
@@ -1093,8 +1105,6 @@ update_table(sql_query *query, dlist *qn
                                return NULL;
                }
                if (opt_where) {
-                       if (!table_privs(sql, t, PRIV_SELECT))
-                               return sql_error(sql, 02, SQLSTATE(42000) 
"UPDATE: insufficient privileges for user '%s' to update table '%s'", 
sqlvar_get_string(find_global_var(sql, mvc_bind_schema(sql, "sys"), 
"current_user")), tname);
                        if (!(r = rel_logical_exp(query, res, opt_where, 
sql_where)))
                                return NULL;
                        /* handle join */
@@ -1107,7 +1117,7 @@ update_table(sql_query *query, dlist *qn
                } else {        /* update all */
                        r = res;
                }
-               return update_generate_assignments(query, t, r, bt, 
assignmentlist, "UPDATE");
+               return update_generate_assignments(query, t, r, 
rel_basetable(sql, t, alias ? alias : tname), assignmentlist, "UPDATE");
        }
        return NULL;
 }
@@ -1158,8 +1168,11 @@ delete_table(sql_query *query, dlist *qn
                if (opt_where) {
                        sql_exp *e;
 
-                       if (!table_privs(sql, t, PRIV_SELECT))
-                               return sql_error(sql, 02, SQLSTATE(42000) 
"DELETE FROM: insufficient privileges for user '%s' to delete from table '%s'", 
sqlvar_get_string(find_global_var(sql, mvc_bind_schema(sql, "sys"), 
"current_user")), tname);
+                       if (!table_privs(sql, t, PRIV_SELECT)) {
+                               if (!(r = rel_reduce_on_column_privileges(sql, 
r, t)))
+                                       return sql_error(sql, 02, 
SQLSTATE(42000) "DELETE FROM: insufficient privileges for user '%s' to delete 
from table '%s'", sqlvar_get_string(find_global_var(sql, mvc_bind_schema(sql, 
"sys"), "current_user")), tname);
+                               list_append(r->exps, exp_column(sql->sa, alias 
? alias : tname, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 1));
+                       }
                        if (!(r = rel_logical_exp(query, r, opt_where, 
sql_where)))
                                return NULL;
                        e = exp_column(sql->sa, rel_name(r), TID, 
sql_bind_localtype("oid"), CARD_MULTI, 0, 1);
@@ -1255,13 +1268,16 @@ merge_into_table(sql_query *query, dlist
                return sql_error(sql, 02, SQLSTATE(3F000) "MERGE: no such 
schema '%s'", sname);
        if (!(t = find_table_on_scope(sql, &s, sname, tname)))
                return sql_error(sql, 02, SQLSTATE(42S02) "MERGE: no such table 
'%s'", tname);
-       if (!table_privs(sql, t, PRIV_SELECT))
-               return sql_error(sql, 02, SQLSTATE(42000) "MERGE: access denied 
for %s to table %s%s%s'%s'",
-                                                
sqlvar_get_string(find_global_var(sql, mvc_bind_schema(sql, "sys"), 
"current_user")), t->s ? "'":"", t->s ? t->s->base.name : "", t->s ? "'.":"", 
tname);
        if (isMergeTable(t))
                return sql_error(sql, 02, SQLSTATE(42000) "MERGE: merge 
statements not available for merge tables yet");
 
        bt = rel_basetable(sql, t, alias ? alias : tname);
+       if (!table_privs(sql, t, PRIV_SELECT)) {
+               if (!(bt = rel_reduce_on_column_privileges(sql, bt, t)))
+                       return sql_error(sql, 02, SQLSTATE(42000) "MERGE: 
access denied for %s to table %s%s%s'%s'",
+                                                        
sqlvar_get_string(find_global_var(sql, mvc_bind_schema(sql, "sys"), 
"current_user")), t->s ? "'":"", t->s ? t->s->base.name : "", t->s ? "'.":"", 
tname);
+               list_append(bt->exps, exp_column(sql->sa, alias ? alias : 
tname, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 1));
+       }
        joined = table_ref(query, NULL, tref, 0, NULL);
        if (!bt || !joined)
                return NULL;
@@ -1305,7 +1321,7 @@ merge_into_table(sql_query *query, dlist
                                extra_project->exps = 
list_merge(extra_project->exps, rel_projections(sql, joined, NULL, 1, 0), 
(fdup)NULL);
                                list_prepend(extra_project->exps, 
exp_column(sql->sa, bt_name, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 1));
 
-                               upd_del = update_generate_assignments(query, t, 
extra_project, rel_dup(bt), sts->h->data.lval, "MERGE");
+                               upd_del = update_generate_assignments(query, t, 
extra_project, rel_basetable(sql, t, bt_name), sts->h->data.lval, "MERGE");
                        } else if (uptdel == SQL_DELETE) {
                                if (!update_allowed(sql, t, tname, "MERGE", 
"delete", 1))
                                        return NULL;
@@ -1322,7 +1338,7 @@ merge_into_table(sql_query *query, dlist
                                extra_project = rel_project(sql->sa, join_rel, 
rel_projections(sql, bt, NULL, 1, 0));
                                list_prepend(extra_project->exps, 
exp_column(sql->sa, bt_name, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 1));
 
-                               upd_del = rel_delete(sql->sa, rel_dup(bt), 
extra_project);
+                               upd_del = rel_delete(sql->sa, 
rel_basetable(sql, t, bt_name), extra_project);
                        } else {
                                assert(0);
                        }
@@ -1351,7 +1367,7 @@ merge_into_table(sql_query *query, dlist
 
                        if (!(insert = merge_generate_inserts(query, t, 
extra_project, sts->h->data.lval, sts->h->next->data.sym)))
                                return NULL;
-                       if (!(insert = rel_insert(query->sql, rel_dup(bt), 
insert)))
+                       if (!(insert = rel_insert(query->sql, 
rel_basetable(sql, t, bt_name), insert)))
                                return NULL;
                } else {
                        assert(0);
diff --git a/sql/test/BugTracker-2020/Tests/All 
b/sql/test/BugTracker-2020/Tests/All
--- a/sql/test/BugTracker-2020/Tests/All
+++ b/sql/test/BugTracker-2020/Tests/All
@@ -41,3 +41,4 @@ view_with_aggr_column.Bug-7023
 delete-transaction-loose-inserts.Bug-7024
 revokeRoleUserLoggedIN.Bug-7026
 drop-table-with-auto_increment.Bug-7030
+user-update-privs.Bug-7035
diff --git a/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.py 
b/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.py
new file mode 100644
--- /dev/null
+++ b/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.py
@@ -0,0 +1,67 @@
+import os, sys, pymonetdb
+
+
+port = int(os.environ['MAPIPORT'])
+db = os.environ['TSTDB']
+
+conn1 = 
pymonetdb.connect(port=port,database=db,autocommit=True,username='monetdb',password='monetdb')
+cur1 = conn1.cursor()
+cur1.execute("""
+start transaction;
+create table t (a int, b int);
+create user usr with password 'usr' name 'usr' schema sys;
+grant select(a) on t to usr;
+grant update (b) on t to usr;
+grant delete on t to usr;
+commit;
+""")
+cur1.close()
+conn1.close()
+
+conn1 = 
pymonetdb.connect(port=port,database=db,autocommit=True,username='usr',password='usr')
+cur1 = conn1.cursor()
+cur1.execute('select a from t;')
+if cur1.fetchall() != []:
+    sys.stderr.write("[] expected")
+if cur1.execute('update t set b = 23;') != 0:
+     sys.stderr.write("0 rows affected expected")
+if cur1.execute('update t set b = 32 where a = 6;') != 0:
+     sys.stderr.write("0 rows affected expected")
+if cur1.execute('update t set b = a;') != 0:
+     sys.stderr.write("0 rows affected expected")
+if cur1.execute('delete from t;') != 0:
+     sys.stderr.write("0 rows affected expected")
+if cur1.execute('delete from t where a = 2;') != 0:
+     sys.stderr.write("0 rows affected expected")
+try:
+    cur1.execute('update t set b = b;') # error, not allowed
+    sys.stderr.write("Exception expected")
+except pymonetdb.DatabaseError as e:
+    if "SELECT: identifier 'b' unknown" not in str(e):
+        sys.stderr.write('Wrong error %s, expected SELECT: identifier \'b\' 
unknown' % (str(e)))
+try:
+    cur1.execute('update t set b = 1 where b = 5;') # error, not allowed
+    sys.stderr.write("Exception expected")
+except pymonetdb.DatabaseError as e:
+    if "SELECT: identifier 'b' unknown" not in str(e):
+        sys.stderr.write('Wrong error %s, expected SELECT: identifier \'b\' 
unknown' % (str(e)))
+try:
+    cur1.execute('delete from t where b = 5;') # error, not allowed
+    sys.stderr.write("Exception expected")
+except pymonetdb.DatabaseError as e:
+    if "SELECT: identifier 'b' unknown" not in str(e):
+        sys.stderr.write('Wrong error %s, expected SELECT: identifier \'b\' 
unknown' % (str(e)))
+
+cur1.close()
+conn1.close()
+
+conn1 = 
pymonetdb.connect(port=port,database=db,autocommit=True,username='monetdb',password='monetdb')
+cur1 = conn1.cursor()
+cur1.execute("""
+start transaction;
+drop user usr;
+drop table t;
+commit;
+""")
+cur1.close()
+conn1.close()
diff --git 
a/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.err 
b/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.err
new file mode 100644
--- /dev/null
+++ b/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.err
@@ -0,0 +1,12 @@
+stderr of test 'user-update-privs.Bug-7035` in directory 
'sql/test/BugTracker-2020` itself:
+
+
+# 16:38:34 >  
+# 16:38:34 >  "/usr/bin/python3.9" "user-update-privs.Bug-7035.py" 
"user-update-privs.Bug-7035"
+# 16:38:34 >  
+
+
+# 16:38:34 >  
+# 16:38:34 >  "Done."
+# 16:38:34 >  
+
diff --git 
a/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.out 
b/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.out
new file mode 100644
--- /dev/null
+++ b/sql/test/BugTracker-2020/Tests/user-update-privs.Bug-7035.stable.out
@@ -0,0 +1,12 @@
+stdout of test 'user-update-privs.Bug-7035` in directory 
'sql/test/BugTracker-2020` itself:
+
+
+# 16:38:34 >  
+# 16:38:34 >  "/usr/bin/python3.9" "user-update-privs.Bug-7035.py" 
"user-update-privs.Bug-7035"
+# 16:38:34 >  
+
+
+# 16:38:34 >  
+# 16:38:34 >  "Done."
+# 16:38:34 >  
+
diff --git a/sql/test/astro/Tests/update.sql b/sql/test/astro/Tests/update.sql
--- a/sql/test/astro/Tests/update.sql
+++ b/sql/test/astro/Tests/update.sql
@@ -32,3 +32,37 @@ UPDATE fluxz
                   AND cm_flux.filter = fluxz.filter
               )
 ;
+
+-- Run the update to make sure it doesn't crash
+UPDATE fluxz
+   SET (filter
+       ,f_datapoints
+       ,avg_flux
+       ,avg_fluxsq
+       ,avg_w
+       ,avg_wflux
+       ,avg_wfluxsq
+       ,avg_dec_zone_deg
+       )
+       =
+       (SELECT filter
+              ,f_datapoints
+              ,avg_flux
+              ,avg_fluxsq
+              ,avg_w
+              ,avg_wflux
+              ,avg_wfluxsq
+              ,avg_dec_zone_deg
+          FROM cm_flux 
+         WHERE cm_flux.runcat = fluxz.runcat 
+           AND cm_flux.active = TRUE 
+           AND cm_flux.filter = 'g' 
+           AND cm_flux.filter = fluxz.filter
+       )
+ WHERE EXISTS (SELECT runcat
+                 FROM cm_flux
+                WHERE cm_flux.runcat = fluxz.runcat 
+                  AND cm_flux.active = TRUE 
+                  AND cm_flux.filter = 'g' 
+                  AND cm_flux.filter = fluxz.filter
+              );
diff --git a/sql/test/astro/Tests/update.stable.out 
b/sql/test/astro/Tests/update.stable.out
--- a/sql/test/astro/Tests/update.stable.out
+++ b/sql/test/astro/Tests/update.stable.out
@@ -55,7 +55,7 @@ update(
 | single project (
 | | single left outer join (
 | | | semijoin (
-| | | | table("sys"."fluxz") [ "fluxz"."runcat" NOT NULL, "fluxz"."filter" NOT 
NULL, "fluxz"."f_datapoints" NOT NULL, "fluxz"."active" NOT NULL, 
"fluxz"."avg_flux" NOT NULL, "fluxz"."avg_fluxsq" NOT NULL, "fluxz"."avg_w" NOT 
NULL, "fluxz"."avg_wflux" NOT NULL, "fluxz"."avg_wfluxsq" NOT NULL, 
"fluxz"."avg_dec_zone_deg" NOT NULL, "fluxz"."%TID%" NOT NULL ] COUNT ,
+| | | | table("sys"."fluxz") [ "fluxz"."runcat" NOT NULL, "fluxz"."filter" NOT 
NULL, "fluxz"."%TID%" NOT NULL ] COUNT ,
 | | | | select (
 | | | | | table("sys"."cm_flux") [ "cm_flux"."runcat" NOT NULL, 
"cm_flux"."filter" NOT NULL, "cm_flux"."active" NOT NULL ] COUNT 
 | | | | ) [ "cm_flux"."active" NOT NULL = boolean "true", "cm_flux"."filter" 
NOT NULL = char(1) "g" ]
@@ -68,6 +68,30 @@ update(
 | | ) [ "cm_flux"."runcat" NOT NULL = "fluxz"."runcat" NOT NULL, 
"cm_flux"."filter" NOT NULL = "fluxz"."filter" NOT NULL ]
 | ) [ "fluxz"."%TID%" NOT NULL, "%1"."%1" NOT NULL as "fluxz"."filter", 
"%2"."%2" NOT NULL as "fluxz"."f_datapoints", "%3"."%3" NOT NULL as 
"fluxz"."avg_flux", "%4"."%4" NOT NULL as "fluxz"."avg_fluxsq", "%5"."%5" NOT 
NULL as "fluxz"."avg_w", "%6"."%6" NOT NULL as "fluxz"."avg_wflux", "%7"."%7" 
NOT NULL as "fluxz"."avg_wfluxsq", "%10"."%10" NOT NULL as 
"fluxz"."avg_dec_zone_deg" ]
 ) [ "fluxz"."%TID%" NOT NULL, "fluxz"."filter" NOT NULL, 
"fluxz"."f_datapoints" NOT NULL, "fluxz"."avg_flux" NOT NULL, 
"fluxz"."avg_fluxsq" NOT NULL, "fluxz"."avg_w" NOT NULL, "fluxz"."avg_wflux" 
NOT NULL, "fluxz"."avg_wfluxsq" NOT NULL, "fluxz"."avg_dec_zone_deg" NOT NULL ]
+#UPDATE fluxz
+#   SET (filter
+#       ,f_datapoints
+#       ,avg_flux
+#       ,avg_fluxsq
+#       ,avg_w
+#       ,avg_wflux
+#       ,avg_wfluxsq
+#       ,avg_dec_zone_deg
+#       )
+#       =
+#       (SELECT filter
+#              ,f_datapoints
+#              ,avg_flux
+#              ,avg_fluxsq
+#              ,avg_w
+#              ,avg_wflux
+#              ,avg_wfluxsq
+#              ,avg_dec_zone_deg
+#          FROM cm_flux 
+#         WHERE cm_flux.runcat = fluxz.runcat 
_______________________________________________
checkin-list mailing list
[email protected]
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to