From 8601d05eff7de2b91f1cf75ab083597471e9d357 Mon Sep 17 00:00:00 2001
From: Yuli Khodorkovskiy <yuli@crunchydata.com>
Date: Mon, 26 Aug 2019 04:45:42 -0700
Subject: [PATCH 2/2] Update sepgsql to add MAC for TRUNCATE

- Use `db_table: { truncate }` to check if permission is granted to TRUNCATE.
- Update regression tests to demonstrate a positive and negative test case
  for TRUNCATE. This test will only be run if the loaded SELinux policy has
  the 'db_table: { truncate }' permission.
- Update example SELinux policy to grant permission for TRUNCATE.
---
 contrib/sepgsql/expected/truncate.out | 46 +++++++++++++++++++++++++++
 contrib/sepgsql/hooks.c               | 14 ++++++++
 contrib/sepgsql/relation.c            | 40 +++++++++++++++++++++++
 contrib/sepgsql/selinux.c             |  3 ++
 contrib/sepgsql/sepgsql-regtest.te    |  8 +++++
 contrib/sepgsql/sepgsql.h             |  2 ++
 contrib/sepgsql/sql/truncate.sql      | 24 ++++++++++++++
 contrib/sepgsql/test_sepgsql          |  9 +++++-
 8 files changed, 145 insertions(+), 1 deletion(-)
 create mode 100644 contrib/sepgsql/expected/truncate.out
 create mode 100644 contrib/sepgsql/sql/truncate.sql

diff --git a/contrib/sepgsql/expected/truncate.out b/contrib/sepgsql/expected/truncate.out
new file mode 100644
index 0000000000..e2cabd77b3
--- /dev/null
+++ b/contrib/sepgsql/expected/truncate.out
@@ -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)
+
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index ebfa441b47..6f6afa2c04 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -189,6 +189,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;
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 061527559c..8de51486fa 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -517,6 +517,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:{delete} 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
  *
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 192aabea0b..73b894c908 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -359,6 +359,9 @@ static struct
 			{
 				"lock", SEPG_DB_TABLE__LOCK
 			},
+			{
+				"truncate", SEPG_DB_TABLE__TRUNCATE
+			},
 			{
 				NULL, 0UL
 			},
diff --git a/contrib/sepgsql/sepgsql-regtest.te b/contrib/sepgsql/sepgsql-regtest.te
index 5d9af1a0dd..569c4da95b 100644
--- a/contrib/sepgsql/sepgsql-regtest.te
+++ b/contrib/sepgsql/sepgsql-regtest.te
@@ -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;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index 4787934650..31828e9eea 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -145,6 +145,7 @@
 #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
index 0000000000..3748a1bdbe
--- /dev/null
+++ b/contrib/sepgsql/sql/truncate.sql
@@ -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;
diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql
index 7530363d2c..1bc574819f 100755
--- a/contrib/sepgsql/test_sepgsql
+++ b/contrib/sepgsql/test_sepgsql
@@ -287,6 +287,13 @@ 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
+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"
-- 
2.21.0

