Blob Blame History Raw
From ad6890564c9408c299460decf013c02ce8f44e2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patrik=20Novotn=C3=BD?= <panovotn@redhat.com>
Date: Tue, 30 Mar 2021 11:27:29 +0200
Subject: [PATCH] In security-restricted operations, block enqueue of at-commit
 user code.

Original commit: 0c3185e963d9f9dd0608214f7d732b84aa0888fe

Original commit message:

    Specifically, this blocks DECLARE ... WITH HOLD and firing of deferred
    triggers within index expressions and materialized view queries.  An
    attacker having permission to create non-temp objects in at least one
    schema could execute arbitrary SQL functions under the identity of the
    bootstrap superuser.  One can work around the vulnerability by disabling
    autovacuum and not manually running ANALYZE, CLUSTER, REINDEX, CREATE
    INDEX, VACUUM FULL, or REFRESH MATERIALIZED VIEW.  (Don't restore from
    pg_dump, since it runs some of those commands.)  Plain VACUUM (without
    FULL) is safe, and all commands are fine when a trusted user owns the
    target object.  Performance may degrade quickly under this workaround,
    however.  Back-patch to 9.5 (all supported versions).
---
 src/backend/commands/portalcmds.c |  6 ++++++
 src/backend/commands/trigger.c    | 13 +++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index e458adfad1..ea1b198dc8 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -27,6 +27,7 @@
 #include "commands/portalcmds.h"
 #include "executor/executor.h"
 #include "executor/tstoreReceiver.h"
+#include "miscadmin.h"
 #include "tcop/pquery.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
@@ -67,6 +68,11 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
 	 */
 	if (!(cstmt->options & CURSOR_OPT_HOLD))
 		RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
+	else if (InSecurityRestrictedOperation())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("cannot create a cursor WITH HOLD within security-restricted operation")));
+
 
 	/*
 	 * Create a portal and copy the plan and queryString into its memory.
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 357313dffa..5a0c65fcaa 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3480,6 +3480,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
 					   bool immediate_only)
 {
 	bool		found = false;
+	bool		deferred_found = false;
 	AfterTriggerEvent event;
 	AfterTriggerEventChunk *chunk;
 
@@ -3515,6 +3516,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
 		 */
 		if (defer_it && move_list != NULL)
 		{
+			deferred_found = true;
 			/* add it to move_list */
 			afterTriggerAddEvent(move_list, event, evtshared);
 			/* mark original copy "done" so we don't do it again */
@@ -3522,6 +3524,17 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
 		}
 	}
 
+		/*
+	 * We could allow deferred triggers if, before the end of the
+	 * security-restricted operation, we were to verify that a SET CONSTRAINTS
+	 * ... IMMEDIATE has fired all such triggers.  For now, don't bother.
+	 */
+	if (deferred_found && InSecurityRestrictedOperation())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("cannot fire deferred trigger within security-restricted operation")));
+
+
 	return found;
 }
 
-- 
2.26.2