Обсуждение: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

Поиск
Список
Период
Сортировка

[PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ignat Remizov
Дата:
Hi Postgres hackers,

Attached is a patch that introduces a new server-level configuration
parameter, "enable_copy_program", that allows administrators to disable
COPY ... PROGRAM functionality at the PostgreSQL server level.


MOTIVATION
==========

COPY ... PROGRAM is a powerful feature that allows executing arbitrary
shell commands from within PostgreSQL. While access is controlled via
the pg_execute_server_program role, some deployments may want to
completely disable this capability as a defense-in-depth measure.
This GUC provides that option.

In practice, COPY ... PROGRAM is a common code execution vector in
PostgreSQL-based attacks. It is:

  - Simple to use (single SQL statement)
  - Requires no additional extensions or setup
  - Frequently targeted by automated botnets and malware campaigns
  - Often the first technique attempted by attackers who gain superuser access

While this GUC is not a comprehensive security solution, it serves as a
mitigating control that removes some of the lowest-hanging fruit for
attackers.


IMPORTANT SECURITY CONTEXT
==========================

This is a mitigating control, not a security boundary.

There is ongoing ecosystem friction around the disputed CVE-2019-9193 entry
in the NVD. The PostgreSQL Security Team has stated that this CVE does not
represent a security bug in PostgreSQL and was filed in error, but NVD and
other CVE databases still list it as a remote code execution issue via COPY
TO/FROM PROGRAM, and several commercial scanners and IDS/IPS signatures treat
it as a high-severity vulnerability. This patch is not intended as a fix for
that CVE; it simply provides an explicit configuration knob for administrators
whose security tooling or policies require disabling program execution via COPY.

Superusers retain multiple other avenues for executing operating system
commands, including but not limited to:

  - Untrusted procedural languages (CREATE EXTENSION plpythonu, plperlu, etc.)
  - Custom extensions that provide shell access
  - The LOAD command to load arbitrary shared libraries
  - pg_read_file() / pg_write_file() combined with other techniques
  - Foreign data wrappers with program execution capabilities
  - Background workers in custom extensions

Disabling COPY ... PROGRAM does NOT make PostgreSQL secure against a
malicious superuser. However, it does:

  1. Block a very common and highly automated attack vector – many botnet
     payloads and exploit scripts specifically target COPY ... PROGRAM
     because it requires no prerequisites.

  2. Raise the bar for exploitation – attackers must use more complex,
     less portable, or more detectable methods.

  3. Reduce drive-by attacks – automated scanners and opportunistic
     attackers often give up when their standard payload fails.

  4. Help meet compliance requirements – some security frameworks mandate
     disabling specific high-risk features.

  5. Provide defense in depth – one layer in a broader security strategy.


KEY CHANGES
===========

New GUC parameter:

  - Name: enable_copy_program
  - Type: boolean
  - Default: on (preserves existing behavior)
  - Context: PGC_POSTMASTER (requires server restart to change)
  - Flags: GUC_SUPERUSER_ONLY | GUC_DISALLOW_IN_AUTO_FILE

Implementation:

  - copy.c
      * Add a check in DoCopy() to reject COPY PROGRAM when disabled.
  - guc_parameters.dat
      * Register the new GUC parameter.
  - guc.h
      * Declare the enable_copy_program variable.

Documentation:

  - config.sgml
      * Document the parameter in the authentication section, including its
        scope and limitations.
  - copy.sgml
      * Cross-reference the new parameter in the COPY command documentation.
  - postgresql.conf.sample
      * Add a commented sample entry.

Tests:

  - copy.sql (regression tests) verifying:
      * Default value is "on".
      * ALTER SYSTEM SET is rejected (due to GUC_DISALLOW_IN_AUTO_FILE).
      * SET is rejected (due to PGC_POSTMASTER context).
  - t/050_copy_program.pl (TAP test) verifying:
      * COPY TO PROGRAM works when enabled.
      * COPY FROM PROGRAM works when enabled.
      * Both are rejected with the expected error when disabled.
  - Updated expected output files accordingly.

Misc:

  - Fixed a typo in a copy.c comment:
      * "pstdout | pstdout" -> "pstdin | pstdout".


BEHAVIOR
========

  enable_copy_program   COPY PROGRAM behavior
  -------------------   ------------------------------------------
  on (default)          Allowed (subject to role privileges)
  off                   Rejected with error, even for superusers

Error message when disabled:

  ERROR:  COPY PROGRAM is disabled
  HINT:   Set enable_copy_program = on to allow COPY TO/FROM PROGRAM.


TESTING
=======

  - All regression tests pass.
  - Recovery TAP tests pass (injection-point skips are expected).
  - New TAP test t/050_copy_program.pl validates both enabled and
    disabled scenarios.


BACKWARD COMPATIBILITY
======================

This change is fully backward compatible. The default value "on"
preserves existing behavior. No action is required for existing
deployments unless they wish to disable COPY PROGRAM.

--
Ignat Remizov


From c642f17d0b44112ba5426be6412004e03d1a5e03 Mon Sep 17 00:00:00 2001
From: Ignat Remizov <ignat980@gmail.com>
Date: Wed, 3 Dec 2025 11:01:31 +0200
Subject: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

Introduce a new postmaster-only GUC parameter enable_copy_program to
control the use of COPY ... PROGRAM. By default this parameter is set
to on, allowing the use of COPY ... PROGRAM and preserving existing
behavior. When set to off, attempts to use COPY ... PROGRAM are
rejected, even for superusers or roles with the pg_execute_server_program
privilege.

Key changes:
- Add enable_copy_program as a postmaster-only GUC parameter.
- Update COPY command logic to enforce the enable_copy_program setting.
- Update documentation to reflect the new parameter and its behavior.
- Add regression and TAP tests to verify the functionality.

This hardening measure allows administrators to disable COPY ... PROGRAM
at the server level as a defense-in-depth control. The enable_copy_program
parameter requires a server restart to take effect and cannot be altered
via ALTER SYSTEM.
---
 doc/src/sgml/config.sgml                      | 21 +++++++
 doc/src/sgml/ref/copy.sgml                    |  4 +-
 src/backend/commands/copy.c                   | 10 +++
 src/backend/utils/misc/guc_parameters.dat     |  8 +++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/bin/psql/copy.c                           |  2 +-
 src/include/utils/guc.h                       |  1 +
 src/test/recovery/meson.build                 |  1 +
 src/test/recovery/t/050_copy_program.pl       | 62 +++++++++++++++++++
 src/test/regress/expected/copy.out            | 11 ++++
 src/test/regress/expected/sysviews.out        |  3 +-
 src/test/regress/sql/copy.sql                 |  5 ++
 12 files changed, 126 insertions(+), 3 deletions(-)
 create mode 100644 src/test/recovery/t/050_copy_program.pl

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 737b90736bf..9b6575999cd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1171,6 +1171,27 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-copy-program" xreflabel="enable_copy_program">
+      <term><varname>enable_copy_program</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_copy_program</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Controls whether <command>COPY ... PROGRAM</command> is allowed.  The
+        default is <literal>on</literal>; when set to <literal>off</literal>,
+        attempts to use <literal>PROGRAM</literal> are rejected even for
+        superusers or roles with <literal>pg_execute_server_program</literal>.
+        This setting does not affect <command>\copy</command> or
+        <command>COPY</command> to or from <literal>STDIN</literal>/<literal>STDOUT</literal>.
+        Changes require a server restart and cannot be made with
+        <command>ALTER SYSTEM</command>. This is a hardening measure, but not a security
+        boundary; superusers retain other ways to execute operating system commands.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
       <term><varname>krb_server_keyfile</varname> (<type>string</type>)
       <indexterm>
diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index 53b0ea8f573..e7a02e8403d 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -590,7 +590,9 @@ COPY <replaceable class="parameter">count</replaceable>
     <literal>pg_write_server_files</literal>,
     or <literal>pg_execute_server_program</literal>, since it allows reading
     or writing any file or running a program that the server has privileges to
-    access.
+    access.  To disable program execution for <command>COPY</command>, set
+    <xref linkend="guc-enable-copy-program"/> to <literal>off</literal>
+    (restart required).
    </para>
 
    <para>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28e878c3688..63fa55f631d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -39,6 +39,9 @@
 #include "utils/rel.h"
 #include "utils/rls.h"
 
+/* Controls whether COPY PROGRAM is permitted at all. */
+bool enable_copy_program = true;
+
 /*
  * DoCopy executes the SQL COPY statement
  *
@@ -78,6 +81,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
  {
  if (stmt->is_program)
  {
+ if (!enable_copy_program)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("COPY PROGRAM is disabled"),
+ errhint("Set enable_copy_program = on to allow COPY "
+ "TO/FROM PROGRAM.")));
+
  if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
  ereport(ERROR,
  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..22f0618ea58 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -819,6 +819,14 @@
   boot_val => 'true',
 },
 
+{ name => 'enable_copy_program', type => 'bool', context => 'PGC_POSTMASTER', group => 'CONN_AUTH_AUTH',
+  short_desc => 'Enables COPY to and from an external program.',
+  long_desc => 'When disabled, COPY PROGRAM is rejected even for superusers.  This can only be changed at server start.',
+  flags => 'GUC_SUPERUSER_ONLY | GUC_DISALLOW_IN_AUTO_FILE',
+  variable => 'enable_copy_program',
+  boot_val => 'true',
+},
+
 { name => 'enable_distinct_reordering', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
   short_desc => 'Enables reordering of DISTINCT keys.',
   flags => 'GUC_EXPLAIN',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..7f3a2a63766 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -97,6 +97,7 @@
 #password_encryption = scram-sha-256    # scram-sha-256 or (deprecated) md5
 #scram_iterations = 4096
 #md5_password_warnings = on             # display md5 deprecation warnings?
+#enable_copy_program = on               # allow COPY ... PROGRAM commands (restart)
 #oauth_validator_libraries = '' # comma-separated list of trusted validator modules
 
 # GSSAPI using Kerberos
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 92c955b637a..04553c3a33f 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -33,7 +33,7 @@
  * \copy ( query stmt ) to filename [options]
  *
  * where 'filename' can be one of the following:
- * '<file path>' | PROGRAM '<command>' | stdin | stdout | pstdout | pstdout
+ * '<file path>' | PROGRAM '<command>' | stdin | stdout | pstdin | pstdout
  * and 'query' can be one of the following:
  * SELECT | UPDATE | INSERT | DELETE
  *
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..a19c3ebb2a6 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -291,6 +291,7 @@ extern PGDLLIMPORT bool check_function_bodies;
 extern PGDLLIMPORT bool current_role_is_superuser;
 
 extern PGDLLIMPORT bool AllowAlterSystem;
+extern PGDLLIMPORT bool enable_copy_program;
 extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 523a5cd5b52..4055b782e30 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -58,6 +58,7 @@ tests += {
       't/047_checkpoint_physical_slot.pl',
       't/048_vacuum_horizon_floor.pl',
       't/049_wait_for_lsn.pl',
+      't/050_copy_program.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/050_copy_program.pl b/src/test/recovery/t/050_copy_program.pl
new file mode 100644
index 00000000000..5ec6c146377
--- /dev/null
+++ b/src/test/recovery/t/050_copy_program.pl
@@ -0,0 +1,62 @@
+#
+# Verify COPY PROGRAM behavior when enabled.
+#
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node_on = PostgreSQL::Test::Cluster->new('enable_copy_program_on');
+$node_on->init;
+$node_on->start;
+
+# use perl as the external program for portability; $^X is portable enough
+my $perlbin = $^X;
+my $out_file = $node_on->basedir . '/enable_copy_program.out';
+
+# COPY ... TO PROGRAM should succeed when enabled.
+$node_on->safe_psql(
+ 'postgres',
+ "COPY (SELECT 42) TO PROGRAM "
+  . "'$perlbin -e \"print qq(42\\\\n)\" > $out_file'"
+);
+is(slurp_file($out_file), "42\n",
+ 'COPY TO PROGRAM writes to external program when enabled');
+
+# COPY ... FROM PROGRAM should succeed when enabled.
+my $in_prog = $node_on->basedir . '/enable_copy_program.sh';
+append_to_file($in_prog, "printf \"99\\n\";\n");
+chmod 0755, $in_prog or die "chmod failed for $in_prog: $!";
+$node_on->safe_psql('postgres', "CREATE TABLE copy_program_enabled(a int)");
+$node_on->safe_psql('postgres',
+ "COPY copy_program_enabled FROM PROGRAM '$in_prog'");
+is(
+ $node_on->safe_psql('postgres',
+ "TABLE copy_program_enabled ORDER BY 1"),
+ "99",
+ 'COPY FROM PROGRAM reads from external program when enabled');
+
+# COPY PROGRAM can be disabled at postmaster start.
+my $node_off = PostgreSQL::Test::Cluster->new('enable_copy_program_off');
+$node_off->init;
+$node_off->append_conf('postgresql.conf', "enable_copy_program=off");
+$node_off->start;
+my $should_not_write = $node_off->basedir . '/should_not_write';
+my ($ret, $stdout, $stderr) = $node_off->psql(
+ 'postgres',
+ "COPY (SELECT 1) TO PROGRAM "
+  . "'$perlbin -e \"print qq(1\\\\n)\" > $should_not_write'"
+);
+isnt($ret, 0, 'COPY PROGRAM fails when disabled');
+like(
+ $stderr,
+ qr/COPY PROGRAM is disabled/,
+ 'COPY PROGRAM disabled error shown');
+
+$node_on->stop;
+$node_off->stop;
+
+done_testing();
diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out
index 24e0f472f14..1034f6431f7 100644
--- a/src/test/regress/expected/copy.out
+++ b/src/test/regress/expected/copy.out
@@ -393,3 +393,14 @@ id val
 5 15
 6 16
 DROP TABLE PP;
+-- COPY PROGRAM guard is postmaster-only and not alterable at runtime
+SHOW enable_copy_program;
+ enable_copy_program
+---------------------
+ on
+(1 row)
+
+ALTER SYSTEM SET enable_copy_program = off;  -- fail (auto conf)
+ERROR:  parameter "enable_copy_program" cannot be changed
+SET enable_copy_program = off;  -- fail (postmaster)
+ERROR:  parameter "enable_copy_program" cannot be changed without restarting the server
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 3b37fafa65b..ac1ba6522f3 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -150,6 +150,7 @@ select name, setting from pg_settings where name like 'enable%';
 --------------------------------+---------
  enable_async_append            | on
  enable_bitmapscan              | on
+ enable_copy_program            | on
  enable_distinct_reordering     | on
  enable_eager_aggregate         | on
  enable_gathermerge             | on
@@ -173,7 +174,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(25 rows)
+(26 rows)
 
 -- There are always wait event descriptions for various types.  InjectionPoint
 -- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql
index 676a8b342b5..c98a6564479 100644
--- a/src/test/regress/sql/copy.sql
+++ b/src/test/regress/sql/copy.sql
@@ -419,3 +419,8 @@ CREATE TABLE pp_510 PARTITION OF pp_2 FOR VALUES FROM (5) TO (10);
 INSERT INTO pp SELECT g, 10 + g FROM generate_series(1,6) g;
 COPY pp TO stdout(header);
 DROP TABLE PP;
+
+-- COPY PROGRAM guard is postmaster-only and not alterable at runtime
+SHOW enable_copy_program;
+ALTER SYSTEM SET enable_copy_program = off;  -- fail (auto conf)
+SET enable_copy_program = off;  -- fail (postmaster)
--
2.43.0

Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ashutosh Bapat
Дата:
On Wed, Dec 3, 2025 at 4:08 PM Ignat Remizov <ignat980@gmail.com> wrote:
>
> Hi Postgres hackers,
>
> Attached is a patch that introduces a new server-level configuration
> parameter, "enable_copy_program", that allows administrators to disable
> COPY ... PROGRAM functionality at the PostgreSQL server level.
>
>
> MOTIVATION
> ==========
>
> COPY ... PROGRAM is a powerful feature that allows executing arbitrary
> shell commands from within PostgreSQL. While access is controlled via
> the pg_execute_server_program role, some deployments may want to
> completely disable this capability as a defense-in-depth measure.
> This GUC provides that option.
>
> In practice, COPY ... PROGRAM is a common code execution vector in
> PostgreSQL-based attacks. It is:
>
>   - Simple to use (single SQL statement)
>   - Requires no additional extensions or setup
>   - Frequently targeted by automated botnets and malware campaigns
>   - Often the first technique attempted by attackers who gain superuser access
>
> While this GUC is not a comprehensive security solution, it serves as a
> mitigating control that removes some of the lowest-hanging fruit for
> attackers.
>
>
> IMPORTANT SECURITY CONTEXT
> ==========================
>
> This is a mitigating control, not a security boundary.
>
> There is ongoing ecosystem friction around the disputed CVE-2019-9193 entry
> in the NVD. The PostgreSQL Security Team has stated that this CVE does not
> represent a security bug in PostgreSQL and was filed in error, but NVD and
> other CVE databases still list it as a remote code execution issue via COPY
> TO/FROM PROGRAM, and several commercial scanners and IDS/IPS signatures treat
> it as a high-severity vulnerability. This patch is not intended as a fix for
> that CVE; it simply provides an explicit configuration knob for administrators
> whose security tooling or policies require disabling program execution via COPY.
>
> Superusers retain multiple other avenues for executing operating system
> commands, including but not limited to:
>
>   - Untrusted procedural languages (CREATE EXTENSION plpythonu, plperlu, etc.)
>   - Custom extensions that provide shell access
>   - The LOAD command to load arbitrary shared libraries
>   - pg_read_file() / pg_write_file() combined with other techniques
>   - Foreign data wrappers with program execution capabilities
>   - Background workers in custom extensions
>
> Disabling COPY ... PROGRAM does NOT make PostgreSQL secure against a
> malicious superuser. However, it does:
>
>   1. Block a very common and highly automated attack vector – many botnet
>      payloads and exploit scripts specifically target COPY ... PROGRAM
>      because it requires no prerequisites.
>
>   2. Raise the bar for exploitation – attackers must use more complex,
>      less portable, or more detectable methods.
>
>   3. Reduce drive-by attacks – automated scanners and opportunistic
>      attackers often give up when their standard payload fails.
>
>   4. Help meet compliance requirements – some security frameworks mandate
>      disabling specific high-risk features.
>
>   5. Provide defense in depth – one layer in a broader security strategy.
>

If pg_execute_server_program is not granted to any user, the
functionality is already disabled right? Why do we need additional GUC
to enable/disable this feature?

--
Best Wishes,
Ashutosh Bapat



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ignat Remizov
Дата:
Thanks for looking, Ashutosh.

pg_execute_server_program is sufficient for non‑superusers, but superusers
always bypass it. In the incident that prompted this, the attacker obtained
superuser via weak/default creds on an exposed instance (common in shared dev
or staging setups). From there, COPY PROGRAM is the simplest, most common RCE
vector used by botnets. The GUC is a defense‑in‑depth knob to let an admin
disable that specific path even for superuser, while leaving the feature
available by default for existing users.

The patch just removes the lowest‑hanging RCE primitive when you explicitly
turn it off (requiring a restart, not ALTER SYSTEM/SET). Default remains on to
preserve current behavior.

--
Ignat Remizov




On Wed, Dec 3, 2025 at 1:31 PM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:
On Wed, Dec 3, 2025 at 4:08 PM Ignat Remizov <ignat980@gmail.com> wrote:
>
> Hi Postgres hackers,
>
> Attached is a patch that introduces a new server-level configuration
> parameter, "enable_copy_program", that allows administrators to disable
> COPY ... PROGRAM functionality at the PostgreSQL server level.
>
>
> MOTIVATION
> ==========
>
> COPY ... PROGRAM is a powerful feature that allows executing arbitrary
> shell commands from within PostgreSQL. While access is controlled via
> the pg_execute_server_program role, some deployments may want to
> completely disable this capability as a defense-in-depth measure.
> This GUC provides that option.
>
> In practice, COPY ... PROGRAM is a common code execution vector in
> PostgreSQL-based attacks. It is:
>
>   - Simple to use (single SQL statement)
>   - Requires no additional extensions or setup
>   - Frequently targeted by automated botnets and malware campaigns
>   - Often the first technique attempted by attackers who gain superuser access
>
> While this GUC is not a comprehensive security solution, it serves as a
> mitigating control that removes some of the lowest-hanging fruit for
> attackers.
>
>
> IMPORTANT SECURITY CONTEXT
> ==========================
>
> This is a mitigating control, not a security boundary.
>
> There is ongoing ecosystem friction around the disputed CVE-2019-9193 entry
> in the NVD. The PostgreSQL Security Team has stated that this CVE does not
> represent a security bug in PostgreSQL and was filed in error, but NVD and
> other CVE databases still list it as a remote code execution issue via COPY
> TO/FROM PROGRAM, and several commercial scanners and IDS/IPS signatures treat
> it as a high-severity vulnerability. This patch is not intended as a fix for
> that CVE; it simply provides an explicit configuration knob for administrators
> whose security tooling or policies require disabling program execution via COPY.
>
> Superusers retain multiple other avenues for executing operating system
> commands, including but not limited to:
>
>   - Untrusted procedural languages (CREATE EXTENSION plpythonu, plperlu, etc.)
>   - Custom extensions that provide shell access
>   - The LOAD command to load arbitrary shared libraries
>   - pg_read_file() / pg_write_file() combined with other techniques
>   - Foreign data wrappers with program execution capabilities
>   - Background workers in custom extensions
>
> Disabling COPY ... PROGRAM does NOT make PostgreSQL secure against a
> malicious superuser. However, it does:
>
>   1. Block a very common and highly automated attack vector – many botnet
>      payloads and exploit scripts specifically target COPY ... PROGRAM
>      because it requires no prerequisites.
>
>   2. Raise the bar for exploitation – attackers must use more complex,
>      less portable, or more detectable methods.
>
>   3. Reduce drive-by attacks – automated scanners and opportunistic
>      attackers often give up when their standard payload fails.
>
>   4. Help meet compliance requirements – some security frameworks mandate
>      disabling specific high-risk features.
>
>   5. Provide defense in depth – one layer in a broader security strategy.
>

If pg_execute_server_program is not granted to any user, the
functionality is already disabled right? Why do we need additional GUC
to enable/disable this feature?

--
Best Wishes,
Ashutosh Bapat

Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ashutosh Bapat
Дата:
On Wed, Dec 3, 2025 at 6:07 PM Ignat Remizov <ignat980@gmail.com> wrote:
>
> Thanks for looking, Ashutosh.
>
> pg_execute_server_program is sufficient for non‑superusers, but superusers
> always bypass it. In the incident that prompted this, the attacker obtained
> superuser via weak/default creds on an exposed instance (common in shared dev
> or staging setups). From there, COPY PROGRAM is the simplest, most common RCE
> vector used by botnets. The GUC is a defense‑in‑depth knob to let an admin
> disable that specific path even for superuser, while leaving the feature
> available by default for existing users.
>
> The patch just removes the lowest‑hanging RCE primitive when you explicitly
> turn it off (requiring a restart, not ALTER SYSTEM/SET). Default remains on to
> preserve current behavior.
>

Adding a feature which allows a system to run with compromisable
superuser credentials doesn't seem like something the community
usually accepts.

--
Best Wishes,
Ashutosh Bapat



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Tom Lane
Дата:
Ignat Remizov <ignat980@gmail.com> writes:
> pg_execute_server_program is sufficient for non‑superusers, but superusers
> always bypass it. In the incident that prompted this, the attacker obtained
> superuser via weak/default creds on an exposed instance (common in shared
> dev
> or staging setups). From there, COPY PROGRAM is the simplest, most common
> RCE
> vector used by botnets. The GUC is a defense‑in‑depth knob to let an admin
> disable that specific path even for superuser, while leaving the feature
> available by default for existing users.

This argument is nonsense, because if you've got superuser you can
just change the GUC's setting again.  Not to mention all the *other*
ways that a superuser can break out to the OS level.  I don't think
this proposal adds anything except more complication, which is not
a good attribute for security-critical considerations.

            regards, tom lane



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ignat Remizov
Дата:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> writes:
> Adding a feature which allows a system to run with compromisable
> superuser credentials doesn't seem like something the community
> usually accepts.

Ashutosh,

I think there’s a misunderstanding. This doesn’t "allow" running with weak
superuser creds; it’s a hardening toggle for admins who already recognize
the risk and want to remove one of the easiest RCE primitives if superuser
does get compromised. Superuser remains fully trusted; the default stays on
to preserve behavior and is fully backwards compatible. When an operator
explicitly sets it to off and restarts, COPY … PROGRAM is blocked even for
superuser, reducing blast radius in misconfigured/legacy environments (e.g.,
weak/default creds on shared exposed dev/staging stacks).

The intent is defense-in-depth, not to encourage running with compromisable 
credentials.


Tom Lane <tgl@sss.pgh.pa.us> writes:
> This argument is nonsense, because if you've got superuser you can
> just change the GUC's setting again.  Not to mention all the *other*
> ways that a superuser can break out to the OS level.  I don't think
> this proposal adds anything except more complication, which is not
> a good attribute for security-critical considerations.

Tom,

A quick clarification: enable_copy_program is PGC_POSTMASTER and
GUC_DISALLOW_IN_AUTO_FILE. SET/ALTER SYSTEM both error (I added regression
tests), and a reload call won’t change it. The only way to flip it is to edit
postgresql.conf (or startup params) and restart. So a compromised superuser
session cannot just turn it back on.

I agree it doesn’t sandbox superuser or block other breakout paths; the intent
is narrowly to remove the most commonly exploited RCE primitive (COPY PROGRAM)
when an admin explicitly opts out. Default remains on to preserve behavior.

--
Ignat Remizov

Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
"Euler Taveira"
Дата:
On Wed, Dec 3, 2025, at 7:37 AM, Ignat Remizov wrote:
> In practice, COPY ... PROGRAM is a common code execution vector in
> PostgreSQL-based attacks. It is:
>
>   - Simple to use (single SQL statement)
>   - Requires no additional extensions or setup
>   - Frequently targeted by automated botnets and malware campaigns
>   - Often the first technique attempted by attackers who gain superuser access
>
> While this GUC is not a comprehensive security solution, it serves as a
> mitigating control that removes some of the lowest-hanging fruit for
> attackers.
>

You are blocking one of the various ways to run malicious code using the
postgres user. If it doesn't work the attacker will try another method. If you
want to prevent the majority of attacks, you need to forbid COPY [ TO | FROM ],
untrusted PLs, confine LOAD to a controlled list and/or path(s), large objects,
user-defined functions (LANGUAGE C), some file system access functions. (Maybe I
forgot other popular methods.) In summary, to (almost) close the gap that you
are concerned, you have to disallow some really popular features like COPY TO. I
don't think that's an acceptable solution. You are basically closing gap A but
there are still gap B, C and D.

> Superusers retain multiple other avenues for executing operating system
> commands, including but not limited to:
>
>   - Untrusted procedural languages (CREATE EXTENSION plpythonu, plperlu, etc.)
>   - Custom extensions that provide shell access
>   - The LOAD command to load arbitrary shared libraries
>   - pg_read_file() / pg_write_file() combined with other techniques
>   - Foreign data wrappers with program execution capabilities
>   - Background workers in custom extensions
>

A concrete plan involves thinking about (almost) all of these possibilities. If
we have a consensus on this topic there should be a central place to control
them.  Several options to close the gaps A, B, C and D is a really bad plan
when you can just pull the curtain aside.

> KEY CHANGES
> ===========
>
> New GUC parameter:
>
>   - Name: enable_copy_program
>   - Type: boolean
>   - Default: on (preserves existing behavior)
>   - Context: PGC_POSTMASTER (requires server restart to change)
>   - Flags: GUC_SUPERUSER_ONLY | GUC_DISALLOW_IN_AUTO_FILE
>

That's not a review because I didn't read your patch. I would call it "enable"
because you cannot enable it unless you restart the service. Instead, I would
use "allow" (same verb as in allow_alter_system).

Why not GUC_DISALLOW_IN_FILE? A command-line option would be harder for the
attacker to control the proposed GUC. (Service files are generally owned by root
so to add/modify an option, it requires a non-default privileges.)

> Misc:
>
>   - Fixed a typo in a copy.c comment:
>       * "pstdout | pstdout" -> "pstdin | pstdout".
>

Please create a separate patch. That's an unrelated change.

> From c642f17d0b44112ba5426be6412004e03d1a5e03 Mon Sep 17 00:00:00 2001
> From: Ignat Remizov <ignat980@gmail.com>
> Date: Wed, 3 Dec 2025 11:01:31 +0200
> Subject: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM
>

We usually attach patches. Simple patches can be shared inline to rapidly
demonstrate your idea. IMO long inline patch takes some time to extract than if
you simply download it.


-- 
Euler Taveira
EDB   https://www.enterprisedb.com/



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ignat Remizov
Дата:
Thanks for the feedback Euler.

On Wed, Dec 3, 2025 at 5:59 PM Euler Taveira <euler@eulerto.com> wrote:
> You are blocking one of the various ways to run malicious code using the
> postgres user. If it doesn't work the attacker will try another method. If you
> want to prevent the majority of attacks, you need to forbid COPY [ TO | FROM ],
> untrusted PLs, confine LOAD to a controlled list and/or path(s), large objects,
> user-defined functions (LANGUAGE C), some file system access functions. (Maybe I
> forgot other popular methods.) In summary, to (almost) close the gap that you
> are concerned, you have to disallow some really popular features like COPY TO. I
> don't think that's an acceptable solution. You are basically closing gap A but
> there are still gap B, C and D.

This patch is intentionally "small": it only removes the most commonly
exploited primitive (COPY PROGRAM). I agree a more comprehensive approach
would be ideal (covering extensions/untrusted PLs, LOAD, filesystem functions,
etc.), but that would take more time to design and implement properly, and was
already in my plans over the next few weekends, ideally via a single control 
point. Something that flips the assumption that superuser is a trusted role, 
and instead requires explicit enabling of potentially dangerous features.

I saw the earlier discussion about seccomp() filters, but that seemed
orthogonal to the problem of controlling what features are available to
superusers as it is.

This small step was what I put together first for my own use and to share.

I initially considered allow_copy_program as a GUC name, but went with
enable_copy_program to match other boolean GUCs like enable_nestloop,
enable_hashagg, etc. The idea is that it enables the feature when true,
instead of only allowing users to use it.

I chose GUC_DISALLOW_IN_AUTO_FILE so that deploys could wire it into
postgresql.conf. GUC_DISALLOW_IN_FILE seemed too restrictive for a 
control people might change later if they want to re-enable the feature 
after additional system hardening. The intent was to allow conf edits 
while blocking ALTER SYSTEM from a compromised superuser.

I'll make a separate patch for the typo. Thanks again.

--
Ignat Remizov

Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Nathan Bossart
Дата:
On Wed, Dec 03, 2025 at 10:02:44AM -0500, Tom Lane wrote:
> This argument is nonsense, because if you've got superuser you can
> just change the GUC's setting again.  Not to mention all the *other*
> ways that a superuser can break out to the OS level.  I don't think
> this proposal adds anything except more complication, which is not
> a good attribute for security-critical considerations.

See also this recent discussion about a --with-copy-program compile flag:

    https://postgr.es/m/flat/CAGRrpza_WUY_jaN4P-xkN%3DTdqfxH%2BeJJazZAo5gg%3DkQoEaQnVw%40mail.gmail.com

-- 
nathan



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Kirill Reshke
Дата:
On Wed, 3 Dec 2025 at 21:45, Ignat Remizov <ignat980@gmail.com> wrote:
>
> Thanks for the feedback Euler.
>
> On Wed, Dec 3, 2025 at 5:59 PM Euler Taveira <euler@eulerto.com> wrote:
> > You are blocking one of the various ways to run malicious code using the
> > postgres user. If it doesn't work the attacker will try another method. If you
> > want to prevent the majority of attacks, you need to forbid COPY [ TO | FROM ],
> > untrusted PLs, confine LOAD to a controlled list and/or path(s), large objects,
> > user-defined functions (LANGUAGE C), some file system access functions. (Maybe I
> > forgot other popular methods.) In summary, to (almost) close the gap that you
> > are concerned, you have to disallow some really popular features like COPY TO. I
> > don't think that's an acceptable solution. You are basically closing gap A but
> > there are still gap B, C and D.
>
> This patch is intentionally "small": it only removes the most commonly
> exploited primitive (COPY PROGRAM). I agree a more comprehensive approach
> would be ideal (covering extensions/untrusted PLs, LOAD, filesystem functions,
> etc.), but that would take more time to design and implement properly, and was
> already in my plans over the next few weekends, ideally via a single control
> point. Something that flips the assumption that superuser is a trusted role,
> and instead requires explicit enabling of potentially dangerous features.
>
> I saw the earlier discussion about seccomp() filters, but that seemed
> orthogonal to the problem of controlling what features are available to
> superusers as it is.
>
> This small step was what I put together first for my own use and to share.
>
> I initially considered allow_copy_program as a GUC name, but went with
> enable_copy_program to match other boolean GUCs like enable_nestloop,
> enable_hashagg, etc. The idea is that it enables the feature when true,
> instead of only allowing users to use it.
>
> I chose GUC_DISALLOW_IN_AUTO_FILE so that deploys could wire it into
> postgresql.conf. GUC_DISALLOW_IN_FILE seemed too restrictive for a
> control people might change later if they want to re-enable the feature
> after additional system hardening. The intent was to allow conf edits
> while blocking ALTER SYSTEM from a compromised superuser.
>

HI! As mentioned here and in nearby threads there is no security
boundary there between pg superuser and os.

Particularly, PGC_POSTMASTER restricts nothing, and
GUC_DISALLOW_IN_AUTO_FILE does not prevent superuser access to
postgresql configure file

Example:

```


db1=# show data_directory;
          data_directory
----------------------------------
 /home/reshke/spqrclusterdata/sh4
(1 row)
db1=# create table t(t text);
CREATE TABLE
db1=# insert into t values ('a=b');
INSERT 0 1
db1=# copy t to '/home/reshke/spqrclusterdata/sh4/postgresql.conf';
COPY 1
```

Even without COPY TO/COPY FROM feature, I believe there are no
practical way of preventic superuser to execute arbitrary code with OS
user privileges

--
Best regards,
Kirill Reshke



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ignat Remizov
Дата:
On Wed, Dec 3, 2025 at 7:23 PM Kirill Reshke <reshkekirill@gmail.com> wrote:
> HI! As mentioned here and in nearby threads there is no security
> boundary there between pg superuser and os.
>
> Particularly, PGC_POSTMASTER restricts nothing, and
> GUC_DISALLOW_IN_AUTO_FILE does not prevent superuser access to
> postgresql configure file
>
> Example:
>
> ```
>
>
> db1=# show data_directory;
>           data_directory
> ----------------------------------
>  /home/reshke/spqrclusterdata/sh4
> (1 row)
> db1=# create table t(t text);
> CREATE TABLE
> db1=# insert into t values ('a=b');
> INSERT 0 1
> db1=# copy t to '/home/reshke/spqrclusterdata/sh4/postgresql.conf';
> COPY 1
> ```
>
> Even without COPY TO/COPY FROM feature, I believe there are no
> practical way of preventic superuser to execute arbitrary code with OS
> user privileges

Hi Kirill,

This patch does not create a hard boundary between PostgreSQL superuser and
the OS user. Making enable_copy_program PGC_POSTMASTER +
GUC_DISALLOW_IN_AUTO_FILE blocks SET/ALTER SYSTEM; flipping the GUC requires
editing postgresql.conf *and* a restart.

From your example, a superuser can indeed overwrite postgresql.conf (including
this GUC) using COPY or other mechanisms. But the attacker would then need to
also restart the service somehow.

As far as I know at present, from SQL you cannot restart the postmaster to
make the change effective.

The threat model I am trying to address is the very common "compromised
superuser password over the wire" case, where the attacker has only a SQL
connection, no shell, and no ability to restart the service.

So the patch removes the one-line RCE primitive in the scenario currently
abused by botnets.

If an attacker already has enough control to edit config files and restart the
service (or crash/restart it) or use other mechanisms, then yes, they can
regain code execution; this doesn’t sandbox the superuser. It just raises the
bar in the common case by removing COPY PROGRAM when an admin explicitly 
disables it.

If the consensus ends up being that we should instead design a more general
"dangerous features" control (covering all breakout paths) behind a single
switch, I would happily work on that in my free time. This patch is just a
small, concrete step in that direction that I already had working.

--
Ignat Remizov

Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Kirill Reshke
Дата:
On Wed, 3 Dec 2025 at 23:02, Ignat Remizov <ignat980@gmail.com> wrote:
>
> On Wed, Dec 3, 2025 at 7:23 PM Kirill Reshke <reshkekirill@gmail.com> wrote:
> > HI! As mentioned here and in nearby threads there is no security
> > boundary there between pg superuser and os.
> >
> > Particularly, PGC_POSTMASTER restricts nothing, and
> > GUC_DISALLOW_IN_AUTO_FILE does not prevent superuser access to
> > postgresql configure file
> >
> > Example:
> >
> > ```
> >
> >
> > db1=# show data_directory;
> >           data_directory
> > ----------------------------------
> >  /home/reshke/spqrclusterdata/sh4
> > (1 row)
> > db1=# create table t(t text);
> > CREATE TABLE
> > db1=# insert into t values ('a=b');
> > INSERT 0 1
> > db1=# copy t to '/home/reshke/spqrclusterdata/sh4/postgresql.conf';
> > COPY 1
> > ```
> >
> > Even without COPY TO/COPY FROM feature, I believe there are no
> > practical way of preventic superuser to execute arbitrary code with OS
> > user privileges
>
> Hi Kirill,
>
> This patch does not create a hard boundary between PostgreSQL superuser and
> the OS user. Making enable_copy_program PGC_POSTMASTER +
> GUC_DISALLOW_IN_AUTO_FILE blocks SET/ALTER SYSTEM; flipping the GUC requires
> editing postgresql.conf *and* a restart.

Yes, editing postgresql.conf and restarting. This is still the same as
editing postgresql.conf, efficiently.
requiring restart does not make the system any more safe.

For example, superuser can provoke postgresql to panic using plain sql
by corrupting critical files.
maybe something like
```
 copy (select 1) to '$datadir/global/pg_control'
```

will do. We can also corrupt pgwal. (I did derive the exact example
when postgresql immediately restarts after some SQL but im 100% there
is such thing )

--
Best regards,
Kirill Reshke



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Kirill Reshke
Дата:
On Wed, 3 Dec 2025 at 23:17, I wrote:
> (I did derive the exact example
> when postgresql immediately restarts after some SQL but im 100% there
> is such thing )

Shame on me


select repeat('a',1024*1024*1023) from generate_series(1, 100);


-- 
Best regards,
Kirill Reshke



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Jacob Champion
Дата:
On Wed, Dec 3, 2025 at 9:03 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
> See also this recent discussion about a --with-copy-program compile flag:
>
>         https://postgr.es/m/flat/CAGRrpza_WUY_jaN4P-xkN%3DTdqfxH%2BeJJazZAo5gg%3DkQoEaQnVw%40mail.gmail.com

Yeah, these conversations tend to get stuck right at this point.
Restricting superuser so that it's somehow not superuser is a huge
(intractable?) undertaking. Doing it a piece at a time doesn't make a
lot of sense if we're not sure that an endpoint exists. But the
ability to escape from the database into the system around it still
seems like a legitimate concern.

A lot of work has been done recently to split apart these privileges
into smaller roles. So what if we just didn't hand out superuser by
default?

Could initdb be made to instead give you a user with the power to
manage almost all of the database (i.e. pg_maintain/pg_monitor), but
without the power to touch anything outside it or execute arbitrary
code? When you needed true superuser, you could still unlock it from
the outside, and at that point it shouldn't be surprising that you can
escape.

--Jacob



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Ignat Remizov
Дата:
Hi Kirill,

These are great examples, thanks. I wasn't aware it was that easy to chain
config overwrite and crash/restart from plain SQL.

Taken together, that makes it clear this GUC buys less than I'd hoped, and
is probably not worth the extra complexity on its own.

Please consider this patch withdrawn for now. I'll go back and think about
a more comprehensive approach (e.g. a single control over superuser
features), and if something useful comes out of that I'll post a separate
proposal. I'll also play with the panic cases you mentioned as part of that.

Thanks again for the detailed explanations.

--
Ignat Remizov

Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Nathan Bossart
Дата:
On Wed, Dec 03, 2025 at 11:35:07AM -0800, Jacob Champion wrote:
> Yeah, these conversations tend to get stuck right at this point.
> Restricting superuser so that it's somehow not superuser is a huge
> (intractable?) undertaking. Doing it a piece at a time doesn't make a
> lot of sense if we're not sure that an endpoint exists. But the
> ability to escape from the database into the system around it still
> seems like a legitimate concern.

Yeah, I have a feeling that we're going to continue to receive proposals in
this area.  Perhaps a good first step is to start listing all the
functionality that crosses the OS/database user boundary.  Then we might be
able to better approximate the effort required and whether we feel
comfortable maintaining such a boundary.

> A lot of work has been done recently to split apart these privileges
> into smaller roles. So what if we just didn't hand out superuser by
> default?
> 
> Could initdb be made to instead give you a user with the power to
> manage almost all of the database (i.e. pg_maintain/pg_monitor), but
> without the power to touch anything outside it or execute arbitrary
> code? When you needed true superuser, you could still unlock it from
> the outside, and at that point it shouldn't be surprising that you can
> escape.

IIRC there's been some discussion about that over the years, including in
my old thread about compiling out untrusted languages [0].

[0] https://postgr.es/m/flat/20220520225619.GA876272@nathanxps13

-- 
nathan



Re: [PATCH] Add enable_copy_program GUC to control COPY PROGRAM

От
Tom Lane
Дата:
Nathan Bossart <nathandbossart@gmail.com> writes:
> On Wed, Dec 03, 2025 at 11:35:07AM -0800, Jacob Champion wrote:
>> Could initdb be made to instead give you a user with the power to
>> manage almost all of the database (i.e. pg_maintain/pg_monitor), but
>> without the power to touch anything outside it or execute arbitrary
>> code? When you needed true superuser, you could still unlock it from
>> the outside, and at that point it shouldn't be surprising that you can
>> escape.

> IIRC there's been some discussion about that over the years, including in
> my old thread about compiling out untrusted languages [0].

I think the idea of putting training wheels on superuser is pretty
hopeless; there's too many ways in which that allows escape to the OS,
and even if we could close them all, the resulting system would be
very much less useful than today.

The right thing is to move people away from using superuser so much.
Compare this to the Unix root situation.  The OS guys have not tried
to cripple root, but they have started to offer setups where there's
no way to log in as root.  And there's protections like sshd not
allowing login as root (with its default settings anyway).  I like
Jacob's idea of requiring some external input, eg a config file
change, before you could become superuser.  I don't necessarily
want to be forced to operate in that world, but we could make it
easier to set up installations that have such restrictions.

            regards, tom lane