From 4ad717d5549cb6ba26a002b69aba7a148a1cd0fc Mon Sep 17 00:00:00 2001 From: Hayato Kuroda Date: Wed, 19 Feb 2025 11:37:26 +0900 Subject: [PATCH v6 2/2] Prohibit slot manipulation while in single-user mode Replication-related commands are rarely used in single-user mode and have been broken for years. This commit prohibits calling slot manipulation SQL functions to avoid additional risk of failures. One exception is pg_drop_replication_slot. It is still allowed because users may want to clean up their mistakes in the mode. --- doc/src/sgml/func/func-admin.sgml | 5 ++ .../replication/logical/logicalfuncs.c | 3 ++ src/backend/replication/slot.c | 12 +++++ src/backend/replication/slotfuncs.c | 14 ++++++ src/backend/utils/adt/pg_upgrade_support.c | 3 ++ src/include/replication/slot.h | 1 + .../t/049_slots_in_single_user_mode.pl | 48 ++++++++++++++----- 7 files changed, 75 insertions(+), 11 deletions(-) diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml index 446fdfe56f4..cfa09e4b987 100644 --- a/doc/src/sgml/func/func-admin.sgml +++ b/doc/src/sgml/func/func-admin.sgml @@ -1012,6 +1012,11 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset are also relevant for replication. + + Note that slot manipulation functions except pg_drop_replication_slot + cannot be used in single-user mode. + + Replication Management Functions diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index ca53caac2f2..30877fdc3ab 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -113,6 +113,9 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin List *options = NIL; DecodingOutputState *p; + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); CheckLogicalDecodingRequirements(); diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index fd0fdb96d42..4ca1227cc94 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1529,6 +1529,18 @@ CheckSlotPermissions(void) "REPLICATION"))); } +/* + * Check whether the instance is in single-user mode. + */ +void +CheckSlotIsInSingleUserMode(void) +{ + if (!IsUnderPostmaster) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots cannot be used in single-user mode"))); +} + /* * Reserve WAL for the currently active slot. * diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 69f4c6157c5..90fe63283a1 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -17,6 +17,7 @@ #include "access/xlogrecovery.h" #include "access/xlogutils.h" #include "funcapi.h" +#include "miscadmin.h" #include "replication/logical.h" #include "replication/slot.h" #include "replication/slotsync.h" @@ -76,6 +77,9 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); CheckSlotRequirements(); @@ -182,6 +186,9 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); CheckLogicalDecodingRequirements(); @@ -521,6 +528,9 @@ pg_replication_slot_advance(PG_FUNCTION_ARGS) Assert(!MyReplicationSlot); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); if (XLogRecPtrIsInvalid(moveto)) @@ -618,9 +628,13 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) TupleDesc tupdesc; HeapTuple tuple; + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); if (logical_slot) diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c index a4f8b4faa90..337f705b34b 100644 --- a/src/backend/utils/adt/pg_upgrade_support.c +++ b/src/backend/utils/adt/pg_upgrade_support.c @@ -296,6 +296,9 @@ binary_upgrade_logical_slot_has_caught_up(PG_FUNCTION_ARGS) */ Assert(has_rolreplication(GetUserId())); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + slot_name = PG_GETARG_NAME(0); /* Acquire the given slot */ diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index e8fc342d1a9..b4d89760f96 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -342,6 +342,7 @@ extern void CheckPointReplicationSlots(bool is_shutdown); extern void CheckSlotRequirements(void); extern void CheckSlotPermissions(void); +extern void CheckSlotIsInSingleUserMode(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); extern const char *GetSlotInvalidationCauseName(ReplicationSlotInvalidationCause cause); diff --git a/src/test/recovery/t/049_slots_in_single_user_mode.pl b/src/test/recovery/t/049_slots_in_single_user_mode.pl index de32160be07..e38a32472b2 100644 --- a/src/test/recovery/t/049_slots_in_single_user_mode.pl +++ b/src/test/recovery/t/049_slots_in_single_user_mode.pl @@ -21,9 +21,31 @@ sub run_test_in_single_user_mode ], \$commands); + return $result; +} + +# Wrapper function for run_test_in_single_user_mode. This would be used when +# the input command succeeds. +sub run_test_in_single_user_mode_success +{ + my ($node, $commands, $testname) = @_; + + my $result = run_test_in_single_user_mode($node, $commands, $testname); + ok($result, $testname); } +# Wrapper function for run_test_in_single_user_mode. This would be used when +# the input command fails. +sub run_test_in_single_user_mode_fail +{ + my ($node, $commands, $testname) = @_; + + my $result = run_test_in_single_user_mode($node, $commands, $testname); + + ok(!$result, $testname); +} + my $slotname = 'test_slot'; # Initialize a node @@ -32,34 +54,38 @@ $node->init(allows_streaming => "logical"); $node->start; # Define initial table -$node->safe_psql('postgres', "CREATE TABLE foo (id int)"); +$node->safe_psql( + 'postgres', qq( +CREATE TABLE foo (id int); +SELECT pg_create_logical_replication_slot('$slotname', 'test_decoding'); +)); # Stop the node to run and test in single-user mode $node->stop; -run_test_in_single_user_mode( +run_test_in_single_user_mode_fail( $node, - "SELECT pg_create_logical_replication_slot('$slotname', 'test_decoding')", - "replication slot can be created in single-user mode"); + "SELECT pg_create_logical_replication_slot('another_slot', 'test_decoding')", + "replication slot cannot be created in single-user mode"); -run_test_in_single_user_mode( +run_test_in_single_user_mode_fail( $node, qq( INSERT INTO foo VALUES (1); SELECT count(1) FROM pg_logical_slot_get_changes('$slotname', NULL, NULL); ), - "logical decoding be done in single-user mode"); + "logical decoding cannot be done in single-user mode"); -run_test_in_single_user_mode( +run_test_in_single_user_mode_fail( $node, "SELECT pg_replication_slot_advance('$slotname', pg_current_wal_lsn())", - "replication slot can be advanced in single-user mode"); + "replication slot cannot be advanced in single-user mode"); -run_test_in_single_user_mode( +run_test_in_single_user_mode_fail( $node, "SELECT pg_copy_logical_replication_slot('$slotname', 'dest_slot')", - "replication slot can be copied in single-user mode"); + "replication slot cannot be copied in single-user mode"); -run_test_in_single_user_mode( +run_test_in_single_user_mode_success( $node, "SELECT pg_drop_replication_slot('$slotname')", "replication slot can be dropped in single-user mode"); -- 2.47.1