diff --git a/contrib/test_decoding/expected/permissions.out b/contrib/test_decoding/expected/permissions.out
index 8d100646ce6..6e94d7106bd 100644
--- a/contrib/test_decoding/expected/permissions.out
+++ b/contrib/test_decoding/expected/permissions.out
@@ -29,6 +29,17 @@ SELECT pg_drop_replication_slot('regression_slot');
(1 row)
RESET ROLE;
+-- no users can load an untrusted plugin
+SET output_plugin_libraries = pgoutput;
+SET ROLE regress_lr_replication;
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
+ERROR: library "test_decoding" may not be used as an output plugin
+HINT: If it is safe for all REPLICATION users to use this library as an output plugin, add it to "output_plugin_libraries" and reload the server configuration.
+RESET ROLE;
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
+ERROR: library "test_decoding" may not be used as an output plugin
+HINT: If it is safe for all REPLICATION users to use this library as an output plugin, add it to "output_plugin_libraries" and reload the server configuration.
+RESET output_plugin_libraries;
-- replication user can control replication
SET ROLE regress_lr_replication;
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
diff --git a/contrib/test_decoding/expected/repack.out b/contrib/test_decoding/expected/repack.out
index c4ff41be690..5ddc63238c5 100644
--- a/contrib/test_decoding/expected/repack.out
+++ b/contrib/test_decoding/expected/repack.out
@@ -106,7 +106,6 @@ CREATE TABLE repack_plugin (a int);
SELECT * FROM pg_create_logical_replication_slot('s_repack', 'pgrepack');
ERROR: unsupported use of logical decoding plugin "pgrepack"
DETAIL: This plugin can only be used by REPACK (CONCURRENTLY).
-CONTEXT: slot "s_repack", output plugin "pgrepack", in the startup callback
INSERT INTO repack_plugin VALUES (1);
SELECT * FROM pg_logical_slot_get_binary_changes('s_repack', NULL, NULL);
ERROR: replication slot "s_repack" does not exist
diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 7de03c79f6f..cf0136455b0 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -30,8 +30,11 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t2', 'tes
init
(1 row)
+BEGIN;
+SET LOCAL output_plugin_libraries = nonexistent;
SELECT pg_create_logical_replication_slot('foo', 'nonexistent');
ERROR: could not access file "nonexistent": No such file or directory
+ROLLBACK;
-- here we want to start a new session and wait till old one is gone
select pg_backend_pid() as oldpid \gset
\c -
diff --git a/contrib/test_decoding/sql/permissions.sql b/contrib/test_decoding/sql/permissions.sql
index 94db936aee2..b3d8b1a8e57 100644
--- a/contrib/test_decoding/sql/permissions.sql
+++ b/contrib/test_decoding/sql/permissions.sql
@@ -15,6 +15,14 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
SELECT pg_drop_replication_slot('regression_slot');
RESET ROLE;
+-- no users can load an untrusted plugin
+SET output_plugin_libraries = pgoutput;
+SET ROLE regress_lr_replication;
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
+RESET ROLE;
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
+RESET output_plugin_libraries;
+
-- replication user can control replication
SET ROLE regress_lr_replication;
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 580e3ae3bef..50a950341fc 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -9,7 +9,10 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t2', 'test_decoding', true);
+BEGIN;
+SET LOCAL output_plugin_libraries = nonexistent;
SELECT pg_create_logical_replication_slot('foo', 'nonexistent');
+ROLLBACK;
-- here we want to start a new session and wait till old one is gone
select pg_backend_pid() as oldpid \gset
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 1352ae85c10..88ed724b639 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4858,6 +4858,63 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
+
+ output_plugin_libraries (string)
+
+ output_plugin_libraries configuration parameter
+
+
+
+
+ Lists the libraries installed in
+ that are also trusted for use as logical output plugins by replication
+ clients. Any logical decoding
+ or replication requests for
+ other libraries will be refused. All users are subject to this
+ restriction. The default is 'pgoutput, test_decoding',
+ which are the two logical output plugins included in the standard
+ PostgreSQL distribution.
+
+
+ The format is a comma-separated list of library names, where each name
+ is interpreted as for the LOAD
+ command (but logical decoding clients must specify a plugin name that
+ exactly matches an entry in the list, without
+ variations in case or path structure). Whitespace between entries is
+ ignored; surround a library name with double quotes if you need to
+ include whitespace or commas in the name.
+
+
+ It is the responsibility of the server administrator to ensure that
+ libraries added to this list do not unintentionally give additional
+ privileges to non-superusers when they are loaded into the server.
+
+
+
+ When updating the server from a version that does not have the
+ output_plugin_libraries parameter, the following
+ query can help construct the list of plugins that are required by all
+ persistent logical replication slots:
+
+SELECT DISTINCT plugin FROM pg_replication_slots WHERE plugin IS NOT NULL;
+
+ Review the list carefully for safety before adjusting
+ output_plugin_libraries.
+
+
+ The above query can only display plugins which were successfully
+ added to replication slots at some point in the past. Newly refused
+ requests will appear in the logs with a message similar to
+
+ERROR: library "..." may not be used as an output plugin
+DETAIL: The configuration parameter "output_plugin_libraries" (currently 'pgoutput, test_decoding') does not name this library as a trusted output plugin.
+HINT: If it is safe for all REPLICATION users to use this library as an output plugin, add it to "output_plugin_libraries" and reload the server configuration.
+
+
+
+
+
+
wal_keep_size (integer)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index efbed7b3bcb..938c939e97d 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -2559,6 +2559,15 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
and it must have the LOGIN attribute.
+
+ The name of the output plugin used by the replication connection must be
+ included in the server's . (For
+ subscriptions, the plugin name that is used is pgoutput.)
+ Superusers may modify the trusted list per-connection, by including
+ options=-coutput_plugin_libraries=... in the connection
+ string.
+
+
In order to be able to copy the initial table data or synchronize
sequences, the role used for the replication connection must have the
@@ -2800,7 +2809,9 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
The output plugins referenced by the slots in the old cluster must be
- installed in the new PostgreSQL executable directory.
+ installed in the new PostgreSQL executable directory. They must also be
+ included in the new cluster's ;
+ see that parameter's documentation for safety information.
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index c30d40a8641..98e5f1dd8f9 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -31,6 +31,7 @@
#include "access/xact.h"
#include "access/xlog_internal.h"
#include "access/xlogutils.h"
+#include "commands/repack.h"
#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -42,9 +43,11 @@
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
+#include "utils/guc.h"
#include "utils/injection_point.h"
#include "utils/inval.h"
#include "utils/memutils.h"
+#include "utils/varlena.h"
/* data for errcontext callback */
typedef struct LogicalErrorCallbackState
@@ -54,6 +57,9 @@ typedef struct LogicalErrorCallbackState
XLogRecPtr report_location;
} LogicalErrorCallbackState;
+/* GUC variables */
+char *output_plugin_libraries_string;
+
/* wrappers around output plugin callbacks */
static void output_plugin_error_callback(void *arg);
static void startup_cb_wrapper(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
@@ -144,6 +150,7 @@ StartupDecodingContext(List *output_plugin_options,
bool need_full_snapshot,
bool fast_forward,
bool in_create,
+ bool for_repack,
XLogReaderRoutine *xl_routine,
LogicalOutputPluginWriterPrepareWrite prepare_write,
LogicalOutputPluginWriterWrite do_write,
@@ -170,7 +177,86 @@ StartupDecodingContext(List *output_plugin_options,
* now.
*/
if (!fast_forward)
- LoadOutputPlugin(&ctx->callbacks, NameStr(slot->data.plugin));
+ {
+ /*
+ * Before loading this library, make sure it's been blessed for
+ * logical decoding.
+ */
+ const char *plugin = NameStr(slot->data.plugin);
+ bool plugin_allowed = false;
+
+ if (strcmp(plugin, "pgrepack") == 0)
+ {
+ /*
+ * REPACK is a special case -- print a more helpful error message
+ * here if we're not being called in the correct context.
+ */
+ if (!for_repack || !AmRepackWorker())
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported use of logical decoding plugin \"%s\"",
+ "pgrepack"),
+ errdetail("This plugin can only be used by %s.",
+ "REPACK (CONCURRENTLY)"));
+
+ plugin_allowed = true;
+ }
+ else if (output_plugin_libraries_string &&
+ output_plugin_libraries_string[0])
+ {
+ /* Check this plugin against output_plugin_libraries. */
+ char *rawstring;
+ List *elemlist = NIL;
+
+ /* Need a modifiable copy */
+ rawstring = pstrdup(output_plugin_libraries_string);
+
+ if (!SplitGUCList(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ ereport(LOG,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid list syntax in parameter \"%s\"",
+ "output_plugin_libraries")));
+
+ list_free(elemlist);
+ elemlist = NIL;
+ }
+
+ foreach_ptr(char, allowed, elemlist)
+ {
+ if (strcmp(allowed, plugin) == 0)
+ {
+ plugin_allowed = true;
+ break;
+ }
+ }
+
+ list_free(elemlist);
+ pfree(rawstring);
+ }
+
+ if (!plugin_allowed)
+ {
+ /*
+ * Use the same error message as check_restricted_library_name(),
+ * but provide additional context for the DBA in the logs. (The
+ * HINT will be sent to the client, but that's not a secret.)
+ */
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("library \"%s\" may not be used as an output plugin",
+ plugin),
+ /*- translator: The second %s is the value of the output_plugin_libraries GUC, which may contain whitespace, commas, and double-quotes (") */
+ errdetail_log("The configuration parameter \"%s\" (currently '%s') does not name this library as a trusted output plugin.",
+ "output_plugin_libraries",
+ output_plugin_libraries_string),
+ errhint("If it is safe for all REPLICATION users to use this library as an output plugin, add it to \"%s\" and reload the server configuration.",
+ "output_plugin_libraries"));
+ }
+
+ LoadOutputPlugin(&ctx->callbacks, plugin);
+ }
/*
* Now that the slot's xmin has been set, we can announce ourselves as a
@@ -434,7 +520,7 @@ CreateInitDecodingContext(const char *plugin,
ReplicationSlotSave();
ctx = StartupDecodingContext(NIL, restart_lsn, xmin_horizon,
- need_full_snapshot, false, true,
+ need_full_snapshot, false, true, for_repack,
xl_routine, prepare_write, do_write,
update_progress);
@@ -569,7 +655,8 @@ CreateDecodingContext(XLogRecPtr start_lsn,
ctx = StartupDecodingContext(output_plugin_options,
start_lsn, InvalidTransactionId, false,
- fast_forward, false, xl_routine, prepare_write,
+ fast_forward, false, false,
+ xl_routine, prepare_write,
do_write, update_progress);
/* call output plugin initialization callback */
@@ -720,7 +807,9 @@ OutputPluginUpdateProgress(struct LogicalDecodingContext *ctx,
/*
* Load the output plugin, lookup its output plugin init function, and check
- * that it provides the required callbacks.
+ * that it provides the required callbacks. The caller must have checked that
+ * the current user has the necessary privileges to load the given plugin;
+ * standard LOAD restrictions are not applied here.
*/
static void
LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugin)
diff --git a/src/backend/replication/pgrepack/pgrepack.c b/src/backend/replication/pgrepack/pgrepack.c
index 5c5095bde4e..a1cbc8db68a 100644
--- a/src/backend/replication/pgrepack/pgrepack.c
+++ b/src/backend/replication/pgrepack/pgrepack.c
@@ -54,12 +54,10 @@ repack_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
RepackDecodingState *dstate;
if (!AmRepackWorker())
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("unsupported use of logical decoding plugin \"%s\"",
- "pgrepack"),
- errdetail("This plugin can only be used by %s.",
- "REPACK (CONCURRENTLY)"));
+ {
+ /* StartupDecodingContext() should have caught this case already */
+ elog(FATAL, "unexpected pgrepack startup outside of repack worker");
+ }
/* Initial setup of our private state */
Assert(CurrentMemoryContext == ctx->context);
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 2cd115deaee..15f9261dab6 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2308,6 +2308,15 @@
ifdef => 'DEBUG_BOUNDED_SORT',
},
+{ name => 'output_plugin_libraries', type => 'string', context => 'PGC_SUSET', group => 'REPLICATION_SENDING',
+ short_desc => 'Lists libraries that may be named as logical decoding output plugins.',
+ long_desc => 'Users with REPLICATION privileges may only use plugins in this list when creating logical replication slots.',
+ # Note that src/bin/pg_upgrade/check.c assumes GUC_LIST_QUOTE here.
+ flags => 'GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_SUPERUSER_ONLY',
+ variable => 'output_plugin_libraries_string',
+ boot_val => '"pgoutput, test_decoding"',
+},
+
{ name => 'parallel_leader_participation', type => 'bool', context => 'PGC_USERSET', group => 'RESOURCES_WORKER_PROCESSES',
short_desc => 'Controls whether Gather and Gather Merge also run subplans.',
long_desc => 'Should gather nodes also run subplans or just gather tuples?',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 290ccbc543e..7fd83081462 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -72,6 +72,7 @@
#include "postmaster/syslogger.h"
#include "postmaster/walsummarizer.h"
#include "postmaster/walwriter.h"
+#include "replication/logical.h"
#include "replication/logicallauncher.h"
#include "replication/slot.h"
#include "replication/slotsync.h"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 23dd27957be..f61bd6ad4de 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -863,6 +863,7 @@
#dynamic_library_path = '$libdir'
#extension_control_path = '$system'
#gin_fuzzy_search_limit = 0
+#output_plugin_libraries = 'pgoutput, test_decoding' # approved plugins for logical decoding
#------------------------------------------------------------------------------
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 0d9f817875d..a08afaf8de5 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -734,6 +734,7 @@ variable_is_guc_list_quote(const char *name)
{
if (pg_strcasecmp(name, "local_preload_libraries") == 0 ||
pg_strcasecmp(name, "oauth_validator_libraries") == 0 ||
+ pg_strcasecmp(name, "output_plugin_libraries") == 0 ||
pg_strcasecmp(name, "search_path") == 0 ||
pg_strcasecmp(name, "session_preload_libraries") == 0 ||
pg_strcasecmp(name, "shared_preload_libraries") == 0 ||
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 184379c52af..e298e5658da 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -2210,6 +2210,7 @@ check_new_cluster_replication_slots(void)
int nslots_on_new;
int rdt_slot_on_new;
int max_replication_slots;
+ char *output_plugin_libraries;
char *wal_level;
int i_nslots_on_new;
int i_rdt_slot_on_new;
@@ -2269,10 +2270,10 @@ check_new_cluster_replication_slots(void)
PQclear(res);
res = executeQueryOrDie(conn, "SELECT setting FROM pg_settings "
- "WHERE name IN ('wal_level', 'max_replication_slots') "
+ "WHERE name IN ('wal_level', 'output_plugin_libraries', 'max_replication_slots') "
"ORDER BY name DESC;");
- if (PQntuples(res) != 2)
+ if (PQntuples(res) != 3)
pg_fatal("could not determine parameter settings on new cluster");
wal_level = PQgetvalue(res, 0, 0);
@@ -2282,7 +2283,85 @@ check_new_cluster_replication_slots(void)
pg_fatal("\"wal_level\" must be \"replica\" or \"logical\" but is set to \"%s\"",
wal_level);
- max_replication_slots = atoi(PQgetvalue(res, 1, 0));
+ output_plugin_libraries = PQgetvalue(res, 1, 0);
+
+ /*
+ * Make sure the output_plugin_libraries setting covers all plugins needed
+ * by any migrated slots.
+ */
+ if (nslots_on_old > 0)
+ {
+ char *guc_copy = pg_strdup(output_plugin_libraries);
+ char **allowed_plugins;
+ char output_path[MAXPGPATH];
+ FILE *script = NULL;
+
+ if (!SplitGUCList(guc_copy, ',', &allowed_plugins))
+ {
+ /*
+ * Should not happen. (Frontend and backend GUC_LIST_QUOTE parsing
+ * have to remain compatible for pg_dump at minimum.)
+ */
+ pg_fatal("could not parse \"output_plugin_libraries\" setting '%s'",
+ output_plugin_libraries);
+ }
+
+ snprintf(output_path, sizeof(output_path), "%s/%s",
+ log_opts.basedir,
+ "disallowed_output_plugins.txt");
+
+ for (int dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
+ {
+ LogicalSlotInfoArr *slot_arr = &old_cluster.dbarr.dbs[dbnum].slot_arr;
+
+ for (int slotnum = 0; slotnum < slot_arr->nslots; slotnum++)
+ {
+ LogicalSlotInfo *slot = &slot_arr->slots[slotnum];
+ bool allowed = false;
+
+ /*
+ * We expect the output_plugin_libraries length to be small in
+ * practice; O(n*m) shouldn't be a problem here.
+ */
+ for (char **p = allowed_plugins; *p; p++)
+ {
+ if (strcmp(slot->plugin, *p) == 0)
+ {
+ allowed = true;
+ break;
+ }
+ }
+
+ if (!allowed)
+ {
+ if (script == NULL &&
+ (script = fopen_priv(output_path, "w")) == NULL)
+ pg_fatal("could not open file \"%s\": %m", output_path);
+
+ fprintf(script, "The slot \"%s\" uses plugin \"%s\"\n",
+ slot->slotname, slot->plugin);
+ }
+ }
+ }
+
+ if (script)
+ {
+ fclose(script);
+
+ pg_log(PG_REPORT, "fatal");
+ pg_fatal("Your installation contains logical replication slots with plugins\n"
+ "that are not allowed by the new cluster's output_plugin_libraries\n"
+ "setting. You can add trusted plugins to output_plugin_libraries\n"
+ "and/or remove affected slots, and then restart the upgrade.\n"
+ "A list of the problematic slots is in the file:\n"
+ " %s", output_path);
+ }
+
+ pg_free(allowed_plugins);
+ pg_free(guc_copy);
+ }
+
+ max_replication_slots = atoi(PQgetvalue(res, 2, 0));
if (old_cluster.sub_retain_dead_tuples &&
nslots_on_old + 1 > max_replication_slots)
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 05918aad935..01ab82402ae 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -62,7 +62,7 @@ chdir ${PostgreSQL::Test::Utils::tmp_check};
$oldpub->start;
$oldpub->safe_psql(
'postgres', qq[
- SELECT pg_create_logical_replication_slot('test_slot1', 'test_decoding');
+ SELECT pg_create_logical_replication_slot('test_slot1', 'pgoutput');
SELECT pg_create_logical_replication_slot('test_slot2', 'test_decoding');
SELECT pg_create_logical_replication_slot('test_slot3', 'test_decoding');
]);
@@ -90,6 +90,52 @@ ok(-d $newpub->data_dir . "/pg_upgrade_output.d",
# old cluster. Both slots will be used for subsequent tests.
$newpub->append_conf('postgresql.conf', "max_replication_slots = 3");
+# ------------------------------
+# TEST: Confirm pg_upgrade fails when slot plugins are prohibited by the new cluster
+
+$newpub->append_conf('postgresql.conf',
+ "output_plugin_libraries = 'pgoutput'");
+
+command_checks_all(
+ [@pg_upgrade_cmd],
+ 1,
+ [
+ qr/Your installation contains logical replication slots with plugins/,
+ qr/that are not allowed by the new cluster's output_plugin_libraries/,
+ ],
+ [qr//],
+ 'run of pg_upgrade where the old cluster has untrusted output plugins');
+
+my $slots_filename;
+
+# Find a txt file that contains a list of logical replication slots that cannot
+# be upgraded. We cannot predict the file's path because the output directory
+# contains a milliseconds timestamp. File::Find::find must be used.
+find(
+ sub {
+ if ($File::Find::name =~ m/disallowed_output_plugins\.txt/)
+ {
+ $slots_filename = $File::Find::name;
+ }
+ },
+ $newpub->data_dir . "/pg_upgrade_output.d");
+
+# Check the report.
+my $content = slurp_file($slots_filename);
+like(
+ $content,
+ qr/The slot "test_slot2" uses plugin "test_decoding"/m,
+ 'the previous test failed due to prohibited plugins');
+like(
+ $content,
+ qr/The slot "test_slot3" uses plugin "test_decoding"/m,
+ 'the previous test failed due to prohibited plugins');
+unlike($content, qr/test_slot1/m, 'allowed plugin is not reported');
+
+# Fix things for the next tests.
+$newpub->append_conf('postgresql.conf',
+ "output_plugin_libraries = 'pgoutput, test_decoding'");
+
# ------------------------------
# TEST: Confirm pg_upgrade fails when the slot still has unconsumed WAL records
@@ -127,11 +173,6 @@ command_checks_all(
);
# Verify the reason why the logical replication slot cannot be upgraded
-my $slots_filename;
-
-# Find a txt file that contains a list of logical replication slots that cannot
-# be upgraded. We cannot predict the file's path because the output directory
-# contains a milliseconds timestamp. File::Find::find must be used.
find(
sub {
if ($File::Find::name =~ m/invalid_logical_slots\.txt/)
diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h
index 6e0b7628001..abc88c49ade 100644
--- a/src/include/replication/logical.h
+++ b/src/include/replication/logical.h
@@ -154,6 +154,9 @@ extern XLogRecPtr LogicalReplicationSlotCheckPendingWal(XLogRecPtr end_of_wal,
extern XLogRecPtr LogicalSlotAdvanceAndCheckSnapState(XLogRecPtr moveto,
bool *found_consistent_snapshot);
+/* GUCs */
+extern PGDLLIMPORT char *output_plugin_libraries_string;
+
/*
* This macro determines the log level for messages about starting logical
diff --git a/src/test/subscription/t/100_bugs.pl b/src/test/subscription/t/100_bugs.pl
index 075c52f98fd..335efd86bca 100644
--- a/src/test/subscription/t/100_bugs.pl
+++ b/src/test/subscription/t/100_bugs.pl
@@ -477,13 +477,13 @@ $result =
is( $result, qq(2|f
3|t), 'check replicated update on subscriber');
-# Test create and immediate drop of replication slot via replication commands
-# (this exposed a memory-management bug in v18)
my $publisher_host = $node_publisher->host;
my $publisher_port = $node_publisher->port;
my $connstr_db =
"host=$publisher_host port=$publisher_port replication=database dbname=postgres";
+# Test create and immediate drop of replication slot via replication commands
+# (this exposed a memory-management bug in v18)
is( $node_publisher->psql(
'postgres',
qq[
@@ -495,6 +495,27 @@ is( $node_publisher->psql(
0,
'create and immediate drop of replication slot');
+# REPLICATION users should not be able to bypass LOAD restrictions.
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ CREATE USER repluser REPLICATION;
+));
+
+my ($ret, $stdout, $stderr) = $node_publisher->psql(
+ 'postgres',
+ qq[
+ SET ROLE repluser;
+ CREATE_REPLICATION_SLOT fail_slot LOGICAL regress;',
+ ],
+ timeout => $PostgreSQL::Test::Utils::timeout_default,
+ extra_params => [ '-d', $connstr_db ]);
+
+is($ret, 3, 'loading unblessed output plugin fails');
+like(
+ $stderr,
+ qr/ERROR: library "regress" may not be used as an output plugin/,
+ 'loading unblessed output plugin fails: stderr');
+
$node_publisher->stop('fast');
$node_subscriber->stop('fast');
@@ -592,7 +613,7 @@ $node_publisher->safe_psql(
CREATE SUBSCRIPTION regress_sub1 CONNECTION '$publisher_connstr' PUBLICATION regress_pub WITH (connect=false);
));
-my ($ret, $stdout, $stderr) =
+($ret, $stdout, $stderr) =
$node_publisher->psql('postgres', q{DROP SUBSCRIPTION regress_sub1});
isnt($ret, 0, "replication slot does not exist: exit code not 0");