Update sepgsql to add mandatory access control for TRUNCATE
authorJoe Conway <mail@joeconway.com>
Sat, 23 Nov 2019 15:41:52 +0000 (10:41 -0500)
committerJoe Conway <mail@joeconway.com>
Sat, 23 Nov 2019 15:46:44 +0000 (10:46 -0500)
Use SELinux "db_table: { truncate }" to check if permission is granted to
TRUNCATE. Update example SELinux policy to grant needed permission for
TRUNCATE. Add new regression test to demonstrate a positive and negative
cases. Test will only be run if the loaded SELinux policy has the
"db_table: { truncate }" permission. Makes use of recent commit which added
object TRUNCATE hook. Patch by Yuli Khodorkovskiy with minor
editorialization by me. Not back-patched because the object TRUNCATE hook
was not.

Author: Yuli Khodorkovskiy
Reviewed-by: Joe Conway
Discussion: https://postgr.es/m/CAFL5wJcomybj1Xdw7qWmPJRpGuFukKgNrDb6uVBaCMgYS9dkaA%40mail.gmail.com

contrib/sepgsql/expected/truncate.out [new file with mode: 0644]
contrib/sepgsql/hooks.c
contrib/sepgsql/relation.c
contrib/sepgsql/selinux.c
contrib/sepgsql/sepgsql-regtest.te
contrib/sepgsql/sepgsql.h
contrib/sepgsql/sql/truncate.sql [new file with mode: 0644]
contrib/sepgsql/test_sepgsql

diff --git a/contrib/sepgsql/expected/truncate.out b/contrib/sepgsql/expected/truncate.out
new file mode 100644 (file)
index 0000000..e2cabd7
--- /dev/null
@@ -0,0 +1,46 @@
+--
+-- Regression Test for TRUNCATE
+--
+--
+-- Setup
+--
+CREATE TABLE julio_claudians (name text, birth_date date);
+SECURITY LABEL ON TABLE julio_claudians IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0';
+INSERT INTO julio_claudians VALUES ('Augustus', 'September 23, 63 BC'), ('Tiberius', 'November 16, 42 BC'), ('Caligula', 'August 31, 0012'), ('Claudius', 'August 1, 0010'), ('Nero', 'December 15, 0037');
+CREATE TABLE flavians (name text, birth_date date);
+SECURITY LABEL ON TABLE flavians IS 'system_u:object_r:sepgsql_table_t:s0';
+INSERT INTO flavians VALUES ('Vespasian', 'November 17, 0009'), ('Titus', 'December 30, 0039'), ('Domitian', 'October 24, 0051');
+SELECT * from julio_claudians;
+   name   |  birth_date   
+----------+---------------
+ Augustus | 09-23-0063 BC
+ Tiberius | 11-16-0042 BC
+ Caligula | 08-31-0012
+ Claudius | 08-01-0010
+ Nero     | 12-15-0037
+(5 rows)
+
+SELECT * from flavians;
+   name    | birth_date 
+-----------+------------
+ Vespasian | 11-17-0009
+ Titus     | 12-30-0039
+ Domitian  | 10-24-0051
+(3 rows)
+
+TRUNCATE TABLE julio_claudians;                        -- ok
+TRUNCATE TABLE flavians;                       -- failed
+ERROR:  SELinux: security policy violation
+SELECT * from julio_claudians;
+ name | birth_date 
+------+------------
+(0 rows)
+
+SELECT * from flavians;
+   name    | birth_date 
+-----------+------------
+ Vespasian | 11-17-0009
+ Titus     | 12-30-0039
+ Domitian  | 10-24-0051
+(3 rows)
+
index 49f32ac4d3334e28bf3d7b895902b69930853f06..cdf1452cf5718848cac9774f1cd67a3589bdd318 100644 (file)
@@ -188,6 +188,20 @@ sepgsql_object_access(ObjectAccessType access,
                        }
                        break;
 
+               case OAT_TRUNCATE:
+                       {
+                               switch (classId)
+                               {
+                                       case RelationRelationId:
+                                               sepgsql_relation_truncate(objectId);
+                                               break;
+                                       default:
+                                               /* Ignore unsupported object classes */
+                                               break;
+                               }
+                       }
+                       break;
+
                case OAT_POST_ALTER:
                        {
                                ObjectAccessPostAlter *pa_arg = arg;
index 714cffed9737f3009611eab3b1a116fe9bacd57c..fa34221509a615e9f55d79a4b8d9605e5423b73c 100644 (file)
@@ -516,6 +516,46 @@ sepgsql_relation_drop(Oid relOid)
        }
 }
 
+/*
+ * sepgsql_relation_truncate
+ *
+ * Check privileges to TRUNCATE the supplied relation.
+ */
+void
+sepgsql_relation_truncate(Oid relOid)
+{
+       ObjectAddress object;
+       char       *audit_name;
+       uint16_t        tclass = 0;
+       char            relkind = get_rel_relkind(relOid);
+
+       switch (relkind)
+       {
+               case RELKIND_RELATION:
+               case RELKIND_PARTITIONED_TABLE:
+                       tclass = SEPG_CLASS_DB_TABLE;
+                       break;
+               default:
+                       /* ignore other relkinds */
+                       return;
+       }
+
+       /*
+        * check db_table:{truncate} permission
+        */
+       object.classId = RelationRelationId;
+       object.objectId = relOid;
+       object.objectSubId = 0;
+       audit_name = getObjectIdentity(&object);
+
+       sepgsql_avc_check_perms(&object,
+                                                       tclass,
+                                                       SEPG_DB_TABLE__TRUNCATE,
+                                                       audit_name,
+                                                       true);
+       pfree(audit_name);
+}
+
 /*
  * sepgsql_relation_relabel
  *
index b7c489cc33625c036a1585971958be2b31ef4b35..5e6189a4c3180bc57dffa91ad24fd4162593cec2 100644 (file)
@@ -359,6 +359,9 @@ static struct
                        {
                                "lock", SEPG_DB_TABLE__LOCK
                        },
+                       {
+                               "truncate", SEPG_DB_TABLE__TRUNCATE
+                       },
                        {
                                NULL, 0UL
                        },
index 5d9af1a0ddb4bc0c527911fcf2d7f05a46e2a58e..569c4da95b92b31c32d58ea97f2c2b6ffeff20b2 100644 (file)
@@ -150,6 +150,14 @@ allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_table { getattr selec
 allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_column { getattr select update insert };
 allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_tuple { select update insert delete };
 
+optional_policy(`
+       gen_require(`
+               class db_table { truncate };
+       ')
+
+       allow sepgsql_regtest_superuser_t sepgsql_regtest_foo_table_t:db_table { truncate };
+')
+
 optional_policy(`
        gen_require(`
                role unconfined_r;
index 4787934650aa3572779834599202c1b36bcb1621..31828e9eea5cd791255b42cdb9fa156a061af1e4 100644 (file)
 #define SEPG_DB_TABLE__INSERT                          (1<<8)
 #define SEPG_DB_TABLE__DELETE                          (1<<9)
 #define SEPG_DB_TABLE__LOCK                                    (1<<10)
+#define SEPG_DB_TABLE__TRUNCATE                                (1<<11)
 
 #define SEPG_DB_SEQUENCE__CREATE                       (SEPG_DB_DATABASE__CREATE)
 #define SEPG_DB_SEQUENCE__DROP                         (SEPG_DB_DATABASE__DROP)
@@ -312,6 +313,7 @@ extern void sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 extern void sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum);
 extern void sepgsql_relation_post_create(Oid relOid);
 extern void sepgsql_relation_drop(Oid relOid);
+extern void sepgsql_relation_truncate(Oid relOid);
 extern void sepgsql_relation_relabel(Oid relOid, const char *seclabel);
 extern void sepgsql_relation_setattr(Oid relOid);
 
diff --git a/contrib/sepgsql/sql/truncate.sql b/contrib/sepgsql/sql/truncate.sql
new file mode 100644 (file)
index 0000000..3748a1b
--- /dev/null
@@ -0,0 +1,24 @@
+--
+-- Regression Test for TRUNCATE
+--
+
+--
+-- Setup
+--
+CREATE TABLE julio_claudians (name text, birth_date date);
+SECURITY LABEL ON TABLE julio_claudians IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0';
+INSERT INTO julio_claudians VALUES ('Augustus', 'September 23, 63 BC'), ('Tiberius', 'November 16, 42 BC'), ('Caligula', 'August 31, 0012'), ('Claudius', 'August 1, 0010'), ('Nero', 'December 15, 0037');
+
+CREATE TABLE flavians (name text, birth_date date);
+SECURITY LABEL ON TABLE flavians IS 'system_u:object_r:sepgsql_table_t:s0';
+
+INSERT INTO flavians VALUES ('Vespasian', 'November 17, 0009'), ('Titus', 'December 30, 0039'), ('Domitian', 'October 24, 0051');
+
+SELECT * from julio_claudians;
+SELECT * from flavians;
+
+TRUNCATE TABLE julio_claudians;                        -- ok
+TRUNCATE TABLE flavians;                       -- failed
+
+SELECT * from julio_claudians;
+SELECT * from flavians;
index 7530363d2cc465eb16c7d15aa9ab5e03894dc61d..3a29556d1ffc78f1a2e631b6f12f9ab38ad23c47 100755 (executable)
@@ -287,6 +287,20 @@ echo "found ${NUM}"
 echo
 echo "============== running sepgsql regression tests       =============="
 
-make REGRESS="label dml ddl alter misc" REGRESS_OPTS="--launcher ./launcher" installcheck
+tests="label dml ddl alter misc"
 
+# Check if the truncate permission exists in the loaded policy, and if so,
+# run the truncate test
+#
+# Testing the TRUNCATE regression test can be done by manually adding
+# the permission with CIL if necessary:
+#     sudo semodule -cE base
+#     sudo sed -i -E 's/(class db_table.*?) \)/\1 truncate\)/' base.cil
+#     sudo semodule -i base.cil
+
+if [ -f /sys/fs/selinux/class/db_table/perms/truncate ]; then
+       tests+=" truncate"
+fi
+
+make REGRESS="$tests" REGRESS_OPTS="--launcher ./launcher" installcheck
 # exit with the exit code provided by "make"