diff --git a/0000-postgresql-var-run-socket.patch b/0001-postgresql-var-run-socket.patch similarity index 100% rename from 0000-postgresql-var-run-socket.patch rename to 0001-postgresql-var-run-socket.patch diff --git a/0000-rpm-pgsql.patch b/0002-rpm-pgsql.patch similarity index 100% rename from 0000-rpm-pgsql.patch rename to 0002-rpm-pgsql.patch diff --git a/6000-CVE-2019-10164-1.patch b/0003-CVE-2019-10164-1.patch similarity index 100% rename from 6000-CVE-2019-10164-1.patch rename to 0003-CVE-2019-10164-1.patch diff --git a/6001-CVE-2019-10164-2.patch b/0004-CVE-2019-10164-2.patch similarity index 100% rename from 6001-CVE-2019-10164-2.patch rename to 0004-CVE-2019-10164-2.patch diff --git a/CVE-2019-10208.patch b/0005-CVE-2019-10208.patch similarity index 100% rename from CVE-2019-10208.patch rename to 0005-CVE-2019-10208.patch diff --git a/CVE-2018-16850.patch b/0006-CVE-2018-16850.patch similarity index 100% rename from CVE-2018-16850.patch rename to 0006-CVE-2018-16850.patch diff --git a/CVE-2019-10130.patch b/0007-CVE-2019-10130.patch similarity index 100% rename from CVE-2019-10130.patch rename to 0007-CVE-2019-10130.patch diff --git a/CVE-2020-1720.patch b/0008-CVE-2020-1720.patch similarity index 100% rename from CVE-2020-1720.patch rename to 0008-CVE-2020-1720.patch diff --git a/0009-CVE-2020-14349-1.patch b/0009-CVE-2020-14349-1.patch new file mode 100644 index 0000000000000000000000000000000000000000..42ef13864649a39b12ba4a067b61a24f29081ba5 --- /dev/null +++ b/0009-CVE-2020-14349-1.patch @@ -0,0 +1,96 @@ +From 11da97024abbe76b8c81e3f2375b2a62e9717c67 Mon Sep 17 00:00:00 2001 +From: Noah Misch +Date: Mon, 10 Aug 2020 09:22:54 -0700 +Subject: [PATCH] Empty search_path in logical replication apply worker and + walsender. + +This is like CVE-2018-1058 commit +582edc369cdbd348d68441fc50fa26a84afd0c1a. Today, a malicious user of a +publisher or subscriber database can invoke arbitrary SQL functions +under an identity running replication, often a superuser. This fix may +cause "does not exist" or "no schema has been selected to create in" +errors in a replication process. After upgrading, consider watching +server logs for these errors. Objects accruing schema qualification in +the wake of the earlier commit are unlikely to need further correction. +Back-patch to v10, which introduced logical replication. + +Security: CVE-2020-14349 +reason: fix CVE-2020-14349 +https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=11da97024abbe76b8c81e3f2375b2a62e9717c67 + +Signed-off-by: Noah Misch +--- + .../libpqwalreceiver/libpqwalreceiver.c | 17 +++++++++++++++++ + src/backend/replication/logical/worker.c | 6 ++++++ + src/test/subscription/t/001_rep_changes.pl | 4 ++++ + 3 files changed, 27 insertions(+) + +diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c +index 37b481c..564e6d3 100644 +--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c ++++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c +@@ -23,6 +23,7 @@ + #include "pqexpbuffer.h" + #include "access/xlog.h" + #include "catalog/pg_type.h" ++#include "fe_utils/connect.h" + #include "funcapi.h" + #include "mb/pg_wchar.h" + #include "miscadmin.h" +@@ -210,6 +211,22 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname, + return NULL; + } + ++ if (logical) ++ { ++ PGresult *res; ++ ++ res = libpqrcv_PQexec(conn->streamConn, ++ ALWAYS_SECURE_SEARCH_PATH_SQL); ++ if (PQresultStatus(res) != PGRES_TUPLES_OK) ++ { ++ PQclear(res); ++ ereport(ERROR, ++ (errmsg("could not clear search path: %s", ++ pchomp(PQerrorMessage(conn->streamConn))))); ++ } ++ PQclear(res); ++ } ++ + conn->logical = logical; + + return conn; +diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c +index bd60094..07b765a 100644 +--- a/src/backend/replication/logical/worker.c ++++ b/src/backend/replication/logical/worker.c +@@ -1548,6 +1548,12 @@ ApplyWorkerMain(Datum main_arg) + BackgroundWorkerInitializeConnectionByOid(MyLogicalRepWorker->dbid, + MyLogicalRepWorker->userid); + ++ /* ++ * Set always-secure search path, so malicious users can't redirect user ++ * code (e.g. pg_index.indexprs). ++ */ ++ SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE); ++ + /* Load the subscription into persistent memory context. */ + ApplyContext = AllocSetContextCreate(TopMemoryContext, + "ApplyContext", +diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl +index 0136c79..cda275b 100644 +--- a/src/test/subscription/t/001_rep_changes.pl ++++ b/src/test/subscription/t/001_rep_changes.pl +@@ -16,6 +16,10 @@ $node_subscriber->init(allows_streaming => 'logical'); + $node_subscriber->start; + + # Create some preexisting content on publisher ++$node_publisher->safe_psql( ++ 'postgres', ++ "CREATE FUNCTION public.pg_get_replica_identity_index(int) ++ RETURNS regclass LANGUAGE sql AS 'SELECT 1/0'"); # shall not call + $node_publisher->safe_psql('postgres', + "CREATE TABLE tab_notrep AS SELECT generate_series(1,10) AS a"); + $node_publisher->safe_psql('postgres', +-- +2.23.0 \ No newline at end of file diff --git a/0010-CVE-2020-14349-2.patch b/0010-CVE-2020-14349-2.patch new file mode 100644 index 0000000000000000000000000000000000000000..0b88b574e2603096b192c3eba03ea0409490bee9 --- /dev/null +++ b/0010-CVE-2020-14349-2.patch @@ -0,0 +1,54 @@ +From cec57b1a0fbcd3833086ba686897c5883e0a2afc Mon Sep 17 00:00:00 2001 +From: Noah Misch +Date: Mon, 10 Aug 2020 09:22:54 -0700 +Subject: [PATCH] Document clashes between logical replication and untrusted +users. + +Back-patch to v10, which introduced logical replication. + +Security: CVE-2020-14349 +reason: fix CVE-2020-14349 +https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=cec57b1a0fbcd3833086ba686897c5883e0a2afc + +Signed-off-by: Noah Misch +--- + doc/src/sgml/logical-replication.sgml | 22 +++++++++++++++++++--- + 1 file changed, 19 insertions(+), 3 deletions(-) + +diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml +index 41770a4..f5086b2 100644 +--- a/doc/src/sgml/logical-replication.sgml ++++ b/doc/src/sgml/logical-replication.sgml +@@ -490,11 +490,27 @@ + + Security + ++ ++ A user able to modify the schema of subscriber-side tables can execute ++ arbitrary code as a superuser. Limit ownership ++ and TRIGGER privilege on such tables to roles that ++ superusers trust. Moreover, if untrusted users can create tables, use only ++ publications that list tables explicitly. That is to say, create a ++ subscription FOR ALL TABLES only when superusers trust ++ every user permitted to create a non-temp table on the publisher or the ++ subscriber. ++ ++ + + The role used for the replication connection must have +- the REPLICATION attribute (or be a superuser). Access for the role must be +- configured in pg_hba.conf and it must have the +- LOGIN attribute. ++ the REPLICATION attribute (or be a superuser). If the ++ role lacks SUPERUSER and BYPASSRLS, ++ publisher row security policies can execute. If the role does not trust ++ all table owners, include options=-crow_security=off in ++ the connection string; if a table owner then adds a row security policy, ++ that setting will cause replication to halt rather than execute the policy. ++ Access for the role must be configured in pg_hba.conf ++ and it must have the LOGIN attribute. + + + +-- +2.23.0 \ No newline at end of file diff --git a/0011-CVE-2020-14350.patch b/0011-CVE-2020-14350.patch new file mode 100644 index 0000000000000000000000000000000000000000..6140806ae956c81f521cbb15fd562d40e52ceee6 --- /dev/null +++ b/0011-CVE-2020-14350.patch @@ -0,0 +1,1199 @@ +From f43931e9bc3431026092b8d21f5b50b9b4896384 Mon Sep 17 00:00:00 2001 +From: Tom Lane +Date: Mon, 10 Aug 2020 10:44:42 -0400 +Subject: [PATCH] Make contrib modules' installation scripts more secure. + +Hostile objects located within the installation-time search_path could +capture references in an extension's installation or upgrade script. +If the extension is being installed with superuser privileges, this +opens the door to privilege escalation. While such hazards have existed +all along, their urgency increases with the v13 "trusted extensions" +feature, because that lets a non-superuser control the installation path +for a superuser-privileged script. Therefore, make a number of changes +to make such situations more secure: + +* Tweak the construction of the installation-time search_path to ensure +that references to objects in pg_catalog can't be subverted; and +explicitly add pg_temp to the end of the path to prevent attacks using +temporary objects. + +* Disable check_function_bodies within installation/upgrade scripts, +so that any security gaps in SQL-language or PL-language function bodies +cannot create a risk of unwanted installation-time code execution. + +* Adjust lookup of type input/receive functions and join estimator +functions to complain if there are multiple candidate functions. This +prevents capture of references to functions whose signature is not the +first one checked; and it's arguably more user-friendly anyway. + +* Modify various contrib upgrade scripts to ensure that catalog +modification queries are executed with secure search paths. (These +are in-place modifications with no extension version changes, since +it is the update process itself that is at issue, not the end result.) + +Extensions that depend on other extensions cannot be made fully secure +by these methods alone; therefore, revert the "trusted" marking that +commit eb67623c9 applied to earthdistance and hstore_plperl, pending +some better solution to that set of issues. + +Also add documentation around these issues, to help extension authors +write secure installation scripts. + +Patch by me, following an observation by Andres Freund; thanks +to Noah Misch for review. + +Security: CVE-2020-14350 +reason: fix CVE-2020-14349 +https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=7eeb1d9861b0a3f453f8b31c7648396cdd7f1e59 + +Signed-off-by: Tom Lane +--- + contrib/btree_gist/btree_gist--1.1--1.2.sql | 56 +++-- + contrib/citext/citext--1.1--1.2.sql | 26 ++- + contrib/citext/citext--1.2--1.3.sql | 18 +- + contrib/cube/cube--1.1--1.2.sql | 25 +- + contrib/earthdistance/earthdistance--1.1.sql | 2 +- + contrib/hstore/hstore--1.1--1.2.sql | 9 +- + contrib/hstore/hstore--1.3--1.4.sql | 35 ++- + contrib/intagg/intagg--1.0--1.1.sql | 14 +- + contrib/intarray/intarray--1.1--1.2.sql | 27 ++- + contrib/ltree/ltree--1.0--1.1.sql | 37 ++- + contrib/pg_trgm/pg_trgm--1.2--1.3.sql | 25 +- + contrib/seg/seg--1.0--1.1.sql | 23 +- + doc/src/sgml/earthdistance.sgml | 27 ++- + doc/src/sgml/extend.sgml | 228 ++++++++++++++++--- + doc/src/sgml/hstore.sgml | 9 + + doc/src/sgml/ltree.sgml | 9 + + doc/src/sgml/ref/create_extension.sgml | 34 ++- + src/backend/commands/extension.c | 21 +- + src/backend/commands/operatorcmds.c | 26 ++- + src/backend/commands/typecmds.c | 92 +- + 20 files changed, 533 insertions(+), 127 deletions(-) + +diff --git a/contrib/btree_gist/btree_gist--1.1--1.2.sql b/contrib/btree_gist/btree_gist--1.1--1.2.sql +index 8487f9b..d5a8c6c 100644 +--- a/contrib/btree_gist/btree_gist--1.1--1.2.sql ++++ b/contrib/btree_gist/btree_gist--1.1--1.2.sql +@@ -8,56 +8,72 @@ + -- wherein the signatures have been updated already. In that case to_regprocedure() will + -- return NULL and no updates will happen. + ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); ++ + UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) + FROM (VALUES +-(NULL::pg_catalog.text, NULL::pg_catalog.regtype[]), -- establish column types ++(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types + ('gbt_oid_distance(internal,oid,int2,oid)', '{internal,oid,int2,oid,internal}'), + ('gbt_oid_union(bytea,internal)', '{internal,internal}'), +-('gbt_oid_same(internal,internal,internal)', '{gbtreekey8,gbtreekey8,internal}'), ++('gbt_oid_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), + ('gbt_int2_distance(internal,int2,int2,oid)', '{internal,int2,int2,oid,internal}'), + ('gbt_int2_union(bytea,internal)', '{internal,internal}'), +-('gbt_int2_same(internal,internal,internal)', '{gbtreekey4,gbtreekey4,internal}'), ++('gbt_int2_same(internal,internal,internal)', '{SCH.gbtreekey4,SCH.gbtreekey4,internal}'), + ('gbt_int4_distance(internal,int4,int2,oid)', '{internal,int4,int2,oid,internal}'), + ('gbt_int4_union(bytea,internal)', '{internal,internal}'), +-('gbt_int4_same(internal,internal,internal)', '{gbtreekey8,gbtreekey8,internal}'), ++('gbt_int4_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), + ('gbt_int8_distance(internal,int8,int2,oid)', '{internal,int8,int2,oid,internal}'), + ('gbt_int8_union(bytea,internal)', '{internal,internal}'), +-('gbt_int8_same(internal,internal,internal)', '{gbtreekey16,gbtreekey16,internal}'), ++('gbt_int8_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), + ('gbt_float4_distance(internal,float4,int2,oid)', '{internal,float4,int2,oid,internal}'), + ('gbt_float4_union(bytea,internal)', '{internal,internal}'), +-('gbt_float4_same(internal,internal,internal)', '{gbtreekey8,gbtreekey8,internal}'), ++('gbt_float4_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), + ('gbt_float8_distance(internal,float8,int2,oid)', '{internal,float8,int2,oid,internal}'), + ('gbt_float8_union(bytea,internal)', '{internal,internal}'), +-('gbt_float8_same(internal,internal,internal)', '{gbtreekey16,gbtreekey16,internal}'), ++('gbt_float8_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), + ('gbt_ts_distance(internal,timestamp,int2,oid)', '{internal,timestamp,int2,oid,internal}'), + ('gbt_tstz_distance(internal,timestamptz,int2,oid)', '{internal,timestamptz,int2,oid,internal}'), + ('gbt_ts_union(bytea,internal)', '{internal,internal}'), +-('gbt_ts_same(internal,internal,internal)', '{gbtreekey16,gbtreekey16,internal}'), ++('gbt_ts_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), + ('gbt_time_distance(internal,time,int2,oid)', '{internal,time,int2,oid,internal}'), + ('gbt_time_union(bytea,internal)', '{internal,internal}'), +-('gbt_time_same(internal,internal,internal)', '{gbtreekey16,gbtreekey16,internal}'), ++('gbt_time_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), + ('gbt_date_distance(internal,date,int2,oid)', '{internal,date,int2,oid,internal}'), + ('gbt_date_union(bytea,internal)', '{internal,internal}'), +-('gbt_date_same(internal,internal,internal)', '{gbtreekey8,gbtreekey8,internal}'), ++('gbt_date_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), + ('gbt_intv_distance(internal,interval,int2,oid)', '{internal,interval,int2,oid,internal}'), + ('gbt_intv_union(bytea,internal)', '{internal,internal}'), +-('gbt_intv_same(internal,internal,internal)', '{gbtreekey32,gbtreekey32,internal}'), ++('gbt_intv_same(internal,internal,internal)', '{SCH.gbtreekey32,SCH.gbtreekey32,internal}'), + ('gbt_cash_distance(internal,money,int2,oid)', '{internal,money,int2,oid,internal}'), + ('gbt_cash_union(bytea,internal)', '{internal,internal}'), +-('gbt_cash_same(internal,internal,internal)', '{gbtreekey16,gbtreekey16,internal}'), ++('gbt_cash_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), + ('gbt_macad_union(bytea,internal)', '{internal,internal}'), +-('gbt_macad_same(internal,internal,internal)', '{gbtreekey16,gbtreekey16,internal}'), ++('gbt_macad_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), + ('gbt_text_union(bytea,internal)', '{internal,internal}'), +-('gbt_text_same(internal,internal,internal)', '{gbtreekey_var,gbtreekey_var,internal}'), ++('gbt_text_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), + ('gbt_bytea_union(bytea,internal)', '{internal,internal}'), +-('gbt_bytea_same(internal,internal,internal)', '{gbtreekey_var,gbtreekey_var,internal}'), ++('gbt_bytea_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), + ('gbt_numeric_union(bytea,internal)', '{internal,internal}'), +-('gbt_numeric_same(internal,internal,internal)', '{gbtreekey_var,gbtreekey_var,internal}'), ++('gbt_numeric_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), + ('gbt_bit_union(bytea,internal)', '{internal,internal}'), +-('gbt_bit_same(internal,internal,internal)', '{gbtreekey_var,gbtreekey_var,internal}'), ++('gbt_bit_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), + ('gbt_inet_union(bytea,internal)', '{internal,internal}'), +-('gbt_inet_same(internal,internal,internal)', '{gbtreekey16,gbtreekey16,internal}') +-) AS update_data (oldproc, newtypes) +-WHERE oid = pg_catalog.to_regprocedure(oldproc); ++('gbt_inet_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}') ++) AS update_data (oldproc, newtypestext), ++LATERAL ( ++ SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ ++) ls ++WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; +diff --git a/contrib/citext/citext--1.1--1.2.sql b/contrib/citext/citext--1.1--1.2.sql +index 4f0e4bc..a8bba86 100644 +--- a/contrib/citext/citext--1.1--1.2.sql ++++ b/contrib/citext/citext--1.1--1.2.sql +@@ -41,14 +41,28 @@ ALTER FUNCTION replace(citext, citext, citext) PARALLEL SAFE; + ALTER FUNCTION split_part(citext, citext, int) PARALLEL SAFE; + ALTER FUNCTION translate(citext, citext, text) PARALLEL SAFE; + ++-- We have to update aggregates the hard way for lack of ALTER support ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); ++ + UPDATE pg_proc SET proparallel = 's' +-WHERE oid = 'min(citext)'::pg_catalog.regprocedure; ++WHERE oid = (my_schema || '.min(' || my_schema || '.citext)')::pg_catalog.regprocedure; + + UPDATE pg_proc SET proparallel = 's' +-WHERE oid = 'max(citext)'::pg_catalog.regprocedure; ++WHERE oid = (my_schema || '.max(' || my_schema || '.citext)')::pg_catalog.regprocedure; ++ ++UPDATE pg_aggregate SET aggcombinefn = (my_schema || '.citext_smaller')::regproc ++WHERE aggfnoid = (my_schema || '.max(' || my_schema || '.citext)')::pg_catalog.regprocedure; + +-UPDATE pg_aggregate SET aggcombinefn = 'citext_smaller' +-WHERE aggfnoid = 'max(citext)'::pg_catalog.regprocedure; ++UPDATE pg_aggregate SET aggcombinefn = (my_schema || '.citext_larger')::regproc ++WHERE aggfnoid = (my_schema || '.max(' || my_schema || '.citext)')::pg_catalog.regprocedure; + +-UPDATE pg_aggregate SET aggcombinefn = 'citext_larger' +-WHERE aggfnoid = 'max(citext)'::pg_catalog.regprocedure; ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; +diff --git a/contrib/citext/citext--1.2--1.3.sql b/contrib/citext/citext--1.2--1.3.sql +index 4ab8679..24a7145 100644 +--- a/contrib/citext/citext--1.2--1.3.sql ++++ b/contrib/citext/citext--1.2--1.3.sql +@@ -3,5 +3,19 @@ + -- complain if script is sourced in psql, rather than via ALTER EXTENSION + \echo Use "ALTER EXTENSION citext UPDATE TO '1.3'" to load this file. \quit + +-UPDATE pg_aggregate SET aggcombinefn = 'citext_smaller' +-WHERE aggfnoid = 'min(citext)'::pg_catalog.regprocedure; ++-- We have to update aggregates the hard way for lack of ALTER support ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); ++ ++UPDATE pg_aggregate SET aggcombinefn = (my_schema || '.citext_smaller')::regproc ++WHERE aggfnoid = (my_schema || '.min(' || my_schema || '.citext)')::pg_catalog.regprocedure; ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; +diff --git a/contrib/cube/cube--1.1--1.2.sql b/contrib/cube/cube--1.1--1.2.sql +index 64a531e..76aba23 100644 +--- a/contrib/cube/cube--1.1--1.2.sql ++++ b/contrib/cube/cube--1.1--1.2.sql +@@ -7,16 +7,31 @@ + -- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, + -- wherein the signatures have been updated already. In that case to_regprocedure() will + -- return NULL and no updates will happen. ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) + FROM (VALUES +-(NULL::pg_catalog.text, NULL::pg_catalog.regtype[]), -- establish column types +-('g_cube_consistent(internal,cube,int4,oid,internal)', '{internal,cube,int2,oid,internal}'), +-('g_cube_distance(internal,cube,smallint,oid)', '{internal,cube,smallint,oid,internal}') +-) AS update_data (oldproc, newtypes) +-WHERE oid = pg_catalog.to_regprocedure(oldproc); ++(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types ++('g_cube_consistent(internal,SCH.cube,int4,oid,internal)', '{internal,SCH.cube,int2,oid,internal}'), ++('g_cube_distance(internal,SCH.cube,smallint,oid)', '{internal,SCH.cube,smallint,oid,internal}') ++) AS update_data (oldproc, newtypestext), ++LATERAL ( ++ SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ ++) ls ++WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; + + ALTER FUNCTION cube_in(cstring) PARALLEL SAFE; + ALTER FUNCTION cube(float8[], float8[]) PARALLEL SAFE; +diff --git a/contrib/earthdistance/earthdistance--1.1.sql b/contrib/earthdistance/earthdistance--1.1.sql +index 9136a54..9ef20ab 100644 +--- a/contrib/earthdistance/earthdistance--1.1.sql ++++ b/contrib/earthdistance/earthdistance--1.1.sql +@@ -31,7 +31,7 @@ CREATE DOMAIN earth AS cube + CONSTRAINT not_point check(cube_is_point(value)) + CONSTRAINT not_3d check(cube_dim(value) <= 3) + CONSTRAINT on_surface check(abs(cube_distance(value, '(0)'::cube) / +- earth() - 1) < '10e-7'::float8); ++ earth() - '1'::float8) < '10e-7'::float8); + + CREATE FUNCTION sec_to_gc(float8) + RETURNS float8 +diff --git a/contrib/hstore/hstore--1.1--1.2.sql b/contrib/hstore/hstore--1.1--1.2.sql +index a868ffe..cc69fc7 100644 +--- a/contrib/hstore/hstore--1.1--1.2.sql ++++ b/contrib/hstore/hstore--1.1--1.2.sql +@@ -9,10 +9,13 @@ + -- dependent on the extension. + + DO LANGUAGE plpgsql +- + $$ +- ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); + BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + PERFORM 1 + FROM pg_proc p +@@ -27,6 +30,7 @@ BEGIN + + IF NOT FOUND + THEN ++ PERFORM pg_catalog.set_config('search_path', old_path, true); + + CREATE FUNCTION hstore_to_json(hstore) + RETURNS json +@@ -43,6 +47,7 @@ BEGIN + + END IF; + ++PERFORM pg_catalog.set_config('search_path', old_path, true); + END; + + $$; +diff --git a/contrib/hstore/hstore--1.3--1.4.sql b/contrib/hstore/hstore--1.3--1.4.sql +index d68956b..53f26f9 100644 +--- a/contrib/hstore/hstore--1.3--1.4.sql ++++ b/contrib/hstore/hstore--1.3--1.4.sql +@@ -7,23 +7,38 @@ + -- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, + -- wherein the signatures have been updated already. In that case to_regprocedure() will + -- return NULL and no updates will happen. ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) + FROM (VALUES +-(NULL::pg_catalog.text, NULL::pg_catalog.regtype[]), -- establish column types +-('ghstore_same(internal,internal,internal)', '{ghstore,ghstore,internal}'), +-('ghstore_consistent(internal,internal,int4,oid,internal)', '{internal,hstore,int2,oid,internal}'), +-('gin_extract_hstore(internal,internal)', '{hstore,internal}'), +-('gin_extract_hstore_query(internal,internal,int2,internal,internal)', '{hstore,internal,int2,internal,internal}'), +-('gin_consistent_hstore(internal,int2,internal,int4,internal,internal)', '{internal,int2,hstore,int4,internal,internal}') +-) AS update_data (oldproc, newtypes) +-WHERE oid = pg_catalog.to_regprocedure(oldproc); ++(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types ++('ghstore_same(internal,internal,internal)', '{SCH.ghstore,SCH.ghstore,internal}'), ++('ghstore_consistent(internal,internal,int4,oid,internal)', '{internal,SCH.hstore,int2,oid,internal}'), ++('gin_extract_hstore(internal,internal)', '{SCH.hstore,internal}'), ++('gin_extract_hstore_query(internal,internal,int2,internal,internal)', '{SCH.hstore,internal,int2,internal,internal}'), ++('gin_consistent_hstore(internal,int2,internal,int4,internal,internal)', '{internal,int2,SCH.hstore,int4,internal,internal}') ++) AS update_data (oldproc, newtypestext), ++LATERAL ( ++ SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ ++) ls ++WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + + UPDATE pg_catalog.pg_proc SET +- prorettype = 'ghstore'::pg_catalog.regtype +-WHERE oid = pg_catalog.to_regprocedure('ghstore_union(internal,internal)'); ++ prorettype = (my_schema || '.ghstore')::pg_catalog.regtype ++WHERE oid = pg_catalog.to_regprocedure((my_schema || '.ghstore_union(internal,internal)')); ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; + + ALTER FUNCTION hstore_in(cstring) PARALLEL SAFE; + ALTER FUNCTION hstore_out(hstore) PARALLEL SAFE; +diff --git a/contrib/intagg/intagg--1.0--1.1.sql b/contrib/intagg/intagg--1.0--1.1.sql +index b2a2820..c0cc17a 100644 +--- a/contrib/intagg/intagg--1.0--1.1.sql ++++ b/contrib/intagg/intagg--1.0--1.1.sql +@@ -6,6 +6,18 @@ + ALTER FUNCTION int_agg_state(internal, int4) PARALLEL SAFE; + ALTER FUNCTION int_agg_final_array(internal) PARALLEL SAFE; + ALTER FUNCTION int_array_enum(int4[]) PARALLEL SAFE; ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + UPDATE pg_proc SET proparallel = 's' +-WHERE oid = 'int_array_aggregate(int4)'::pg_catalog.regprocedure; ++WHERE oid = (my_schema || '.int_array_aggregate(int4)')::pg_catalog.regprocedure; ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; +diff --git a/contrib/intarray/intarray--1.1--1.2.sql b/contrib/intarray/intarray--1.1--1.2.sql +index 468f245..919340e 100644 +--- a/contrib/intarray/intarray--1.1--1.2.sql ++++ b/contrib/intarray/intarray--1.1--1.2.sql +@@ -7,23 +7,38 @@ + -- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, + -- wherein the signatures have been updated already. In that case to_regprocedure() will + -- return NULL and no updates will happen. ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) + FROM (VALUES +-(NULL::pg_catalog.text, NULL::pg_catalog.regtype[]), -- establish column types ++(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types + ('g_int_consistent(internal,_int4,int4,oid,internal)', '{internal,_int4,int2,oid,internal}'), + ('g_intbig_consistent(internal,internal,int4,oid,internal)', '{internal,_int4,int2,oid,internal}'), +-('g_intbig_same(internal,internal,internal)', '{intbig_gkey,intbig_gkey,internal}'), ++('g_intbig_same(internal,internal,internal)', '{SCH.intbig_gkey,SCH.intbig_gkey,internal}'), + ('ginint4_queryextract(internal,internal,int2,internal,internal,internal,internal)', '{_int4,internal,int2,internal,internal,internal,internal}'), + ('ginint4_consistent(internal,int2,internal,int4,internal,internal,internal,internal)', '{internal,int2,_int4,int4,internal,internal,internal,internal}') +-) AS update_data (oldproc, newtypes) +-WHERE oid = pg_catalog.to_regprocedure(oldproc); ++) AS update_data (oldproc, newtypestext), ++LATERAL ( ++ SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ ++) ls ++WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + + UPDATE pg_catalog.pg_proc SET +- prorettype = 'intbig_gkey'::pg_catalog.regtype +-WHERE oid = pg_catalog.to_regprocedure('g_intbig_union(internal,internal)'); ++ prorettype = (my_schema || '.intbig_gkey')::pg_catalog.regtype ++WHERE oid = pg_catalog.to_regprocedure(my_schema || '.g_intbig_union(internal,internal)'); ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; + + ALTER FUNCTION bqarr_in(cstring) PARALLEL SAFE; + ALTER FUNCTION bqarr_out(query_int) PARALLEL SAFE; +diff --git a/contrib/ltree/ltree--1.0--1.1.sql b/contrib/ltree/ltree--1.0--1.1.sql +index 155751a..2ce6f5a 100644 +--- a/contrib/ltree/ltree--1.0--1.1.sql ++++ b/contrib/ltree/ltree--1.0--1.1.sql +@@ -7,26 +7,41 @@ + -- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, + -- wherein the signatures have been updated already. In that case to_regprocedure() will + -- return NULL and no updates will happen. ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) + FROM (VALUES +-(NULL::pg_catalog.text, NULL::pg_catalog.regtype[]), -- establish column types +-('ltree_consistent(internal,internal,int2,oid,internal)', '{internal,ltree,int2,oid,internal}'), +-('ltree_same(internal,internal,internal)', '{ltree_gist,ltree_gist,internal}'), +-('_ltree_consistent(internal,internal,int2,oid,internal)', '{internal,_ltree,int2,oid,internal}'), +-('_ltree_same(internal,internal,internal)', '{ltree_gist,ltree_gist,internal}') +-) AS update_data (oldproc, newtypes) +-WHERE oid = pg_catalog.to_regprocedure(oldproc); ++(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types ++('ltree_consistent(internal,internal,int2,oid,internal)', '{internal,SCH.ltree,int2,oid,internal}'), ++('ltree_same(internal,internal,internal)', '{SCH.ltree_gist,SCH.ltree_gist,internal}'), ++('_ltree_consistent(internal,internal,int2,oid,internal)', '{internal,SCH._ltree,int2,oid,internal}'), ++('_ltree_same(internal,internal,internal)', '{SCH.ltree_gist,SCH.ltree_gist,internal}') ++) AS update_data (oldproc, newtypestext), ++LATERAL ( ++ SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ ++) ls ++WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + + UPDATE pg_catalog.pg_proc SET +- prorettype = 'ltree_gist'::pg_catalog.regtype +-WHERE oid = pg_catalog.to_regprocedure('ltree_union(internal,internal)'); ++ prorettype = (my_schema || '.ltree_gist')::pg_catalog.regtype ++WHERE oid = pg_catalog.to_regprocedure(my_schema || '.ltree_union(internal,internal)'); + + UPDATE pg_catalog.pg_proc SET +- prorettype = 'ltree_gist'::pg_catalog.regtype +-WHERE oid = pg_catalog.to_regprocedure('_ltree_union(internal,internal)'); ++ prorettype = (my_schema || '.ltree_gist')::pg_catalog.regtype ++WHERE oid = pg_catalog.to_regprocedure(my_schema || '._ltree_union(internal,internal)'); ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; + + ALTER FUNCTION ltree_in(cstring) PARALLEL SAFE; + ALTER FUNCTION ltree_out(ltree) PARALLEL SAFE; +diff --git a/contrib/pg_trgm/pg_trgm--1.2--1.3.sql b/contrib/pg_trgm/pg_trgm--1.2--1.3.sql +index b082dcd..8dc772c 100644 +--- a/contrib/pg_trgm/pg_trgm--1.2--1.3.sql ++++ b/contrib/pg_trgm/pg_trgm--1.2--1.3.sql +@@ -7,21 +7,36 @@ + -- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, + -- wherein the signatures have been updated already. In that case to_regprocedure() will + -- return NULL and no updates will happen. ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) + FROM (VALUES +-(NULL::pg_catalog.text, NULL::pg_catalog.regtype[]), -- establish column types ++(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types + ('gtrgm_consistent(internal,text,int4,oid,internal)', '{internal,text,int2,oid,internal}'), + ('gtrgm_distance(internal,text,int4,oid)', '{internal,text,int2,oid,internal}'), + ('gtrgm_union(bytea,internal)', '{internal,internal}') +-) AS update_data (oldproc, newtypes) +-WHERE oid = pg_catalog.to_regprocedure(oldproc); ++) AS update_data (oldproc, newtypestext), ++LATERAL ( ++ SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ ++) ls ++WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + + UPDATE pg_catalog.pg_proc SET +- prorettype = 'gtrgm'::pg_catalog.regtype +-WHERE oid = pg_catalog.to_regprocedure('gtrgm_union(internal,internal)'); ++ prorettype = (my_schema || '.gtrgm')::pg_catalog.regtype ++WHERE oid = pg_catalog.to_regprocedure(my_schema || '.gtrgm_union(internal,internal)'); ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; + + ALTER FUNCTION set_limit(float4) PARALLEL UNSAFE; + ALTER FUNCTION show_limit() PARALLEL SAFE; +diff --git a/contrib/seg/seg--1.0--1.1.sql b/contrib/seg/seg--1.0--1.1.sql +index 2dcd4d4..ae6cb2f 100644 +--- a/contrib/seg/seg--1.0--1.1.sql ++++ b/contrib/seg/seg--1.0--1.1.sql +@@ -7,15 +7,30 @@ + -- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, + -- wherein the signatures have been updated already. In that case to_regprocedure() will + -- return NULL and no updates will happen. ++DO LANGUAGE plpgsql ++$$ ++DECLARE ++ my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); ++ old_path pg_catalog.text := pg_catalog.current_setting('search_path'); ++BEGIN ++-- for safety, transiently set search_path to just pg_catalog+pg_temp ++PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) + FROM (VALUES +-(NULL::pg_catalog.text, NULL::pg_catalog.regtype[]), -- establish column types +-('gseg_consistent(internal,seg,int4,oid,internal)', '{internal,seg,int2,oid,internal}') +-) AS update_data (oldproc, newtypes) +-WHERE oid = pg_catalog.to_regprocedure(oldproc); ++(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types ++('gseg_consistent(internal,SCH.seg,int4,oid,internal)', '{internal,SCH.seg,int2,oid,internal}') ++) AS update_data (oldproc, newtypestext), ++LATERAL ( ++ SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ ++) ls ++WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); ++ ++PERFORM pg_catalog.set_config('search_path', old_path, true); ++END ++$$; + + ALTER FUNCTION seg_in(cstring) PARALLEL SAFE; + ALTER FUNCTION seg_out(seg) PARALLEL SAFE; + +diff --git a/doc/src/sgml/earthdistance.sgml b/doc/src/sgml/earthdistance.sgml +index 6dedc4a..a58c5a1 100644 +--- a/doc/src/sgml/earthdistance.sgml ++++ b/doc/src/sgml/earthdistance.sgml +@@ -10,9 +10,8 @@ + + The earthdistance module provides two different approaches to + calculating great circle distances on the surface of the Earth. The one +- described first depends on the cube module (which +- must be installed before earthdistance can be +- installed). The second one is based on the built-in point data type, ++ described first depends on the cube module. ++ The second one is based on the built-in point data type, + using longitude and latitude for the coordinates. + + +@@ -23,6 +22,28 @@ + project.) + + ++ ++ The cube module must be installed ++ before earthdistance can be installed ++ (although you can use the CASCADE option ++ of CREATE EXTENSION to install both in one command). ++ ++ ++ ++ ++ It is strongly recommended that earthdistance ++ and cube be installed in the same schema, and that ++ that schema be one for which CREATE privilege has not been and will not ++ be granted to any untrusted users. ++ Otherwise there are installation-time security hazards ++ if earthdistance's schema contains objects defined ++ by a hostile user. ++ Furthermore, when using earthdistance's functions ++ after installation, the entire search path should contain only trusted ++ schemas. ++ ++ ++ + + Cube-based Earth Distances + +diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml +index 962515c..4901fe3 100644 +--- a/doc/src/sgml/extend.sgml ++++ b/doc/src/sgml/extend.sgml +@@ -342,7 +342,7 @@ + + + The extension script may set privileges on objects that are part of the +- extension via GRANT and REVOKE ++ extension, using GRANT and REVOKE + statements. The final set of privileges for each object (if any are set) + will be stored in the + pg_init_privs + +@@ -542,9 +516,38 @@ + + If this parameter is true (which is the default), + only superusers can create the extension or update it to a new +- version. If it is set to false, just the privileges ++ version (but see also trusted, below). ++ If it is set to false, just the privileges + required to execute the commands in the installation or update script + are required. ++ This should normally be set to true if any of the ++ script commands require superuser privileges. (Such commands would ++ fail anyway, but it's more user-friendly to give the error up front.) ++ ++ ++ ++ ++ ++ trusted (boolean) ++ ++ ++ This parameter, if set to true (which is not the ++ default), allows some non-superusers to install an extension that ++ has superuser set to true. ++ Specifically, installation will be permitted for anyone who has ++ CREATE privilege on the current database. ++ When the user executing CREATE EXTENSION is not ++ a superuser but is allowed to install by virtue of this parameter, ++ then the installation or update script is run as the bootstrap ++ superuser, not as the calling user. ++ This parameter is irrelevant if superuser is ++ false. ++ Generally, this should not be set true for extensions that could ++ allow access to otherwise-superuser-only abilities, such as ++ file system access. ++ Also, marking an extension trusted requires significant extra effort ++ to write the extension's installation and update script(s) securely; ++ see . + + + +@@ -686,7 +689,7 @@ + schema; that is, CREATE EXTENSION does the equivalent of + this: + +-SET LOCAL search_path TO @extschema@; ++SET LOCAL search_path TO @extschema@, pg_temp; + + This allows the objects created by the script file to go into the target + schema. The script file can change search_path if it wishes, +@@ -706,9 +709,15 @@ SET LOCAL search_path TO @extschema@; + + + If any prerequisite extensions are listed in requires +- in the control file, their target schemas are appended to the initial +- setting of search_path. This allows their objects to be +- visible to the new extension's script file. ++ in the control file, their target schemas are added to the initial ++ setting of search_path, following the new ++ extension's target schema. This allows their objects to be visible to ++ the new extension's script file. ++ ++ ++ ++ For security, pg_temp is automatically appended to ++ the end of search_path in all cases. + + + +@@ -962,6 +971,154 @@ SELECT * FROM pg_extension_update_paths('extension_name'); + + + ++ ++ Security Considerations for Extensions ++ ++ ++ Widely-distributed extensions should assume little about the database ++ they occupy. Therefore, it's appropriate to write functions provided ++ by an extension in a secure style that cannot be compromised by ++ search-path-based attacks. ++ ++ ++ ++ An extension that has the superuser property set to ++ true must also consider security hazards for the actions taken within ++ its installation and update scripts. It is not terribly difficult for ++ a malicious user to create trojan-horse objects that will compromise ++ later execution of a carelessly-written extension script, allowing that ++ user to acquire superuser privileges. ++ ++ ++ ++ If an extension is marked trusted, then its ++ installation schema can be selected by the installing user, who might ++ intentionally use an insecure schema in hopes of gaining superuser ++ privileges. Therefore, a trusted extension is extremely exposed from a ++ security standpoint, and all its script commands must be carefully ++ examined to ensure that no compromise is possible. ++ ++ ++ ++ Advice about writing functions securely is provided in ++ below, and advice ++ about writing installation scripts securely is provided in ++ . ++ ++ ++ ++ Security Considerations for Extension Functions ++ ++ ++ SQL-language and PL-language functions provided by extensions are at ++ risk of search-path-based attacks when they are executed, since ++ parsing of these functions occurs at execution time not creation time. ++ ++ ++ ++ The CREATE ++ FUNCTION reference page contains advice about ++ writing SECURITY DEFINER functions safely. It's ++ good practice to apply those techniques for any function provided by ++ an extension, since the function might be called by a high-privilege ++ user. ++ ++ ++ ++ ++ If you cannot set the search_path to contain only ++ secure schemas, assume that each unqualified name could resolve to an ++ object that a malicious user has defined. Beware of constructs that ++ depend on search_path implicitly; for ++ example, IN ++ and CASE expression WHEN ++ always select an operator using the search path. In their place, use ++ OPERATOR(schema.=) ANY ++ and CASE WHEN expression. ++ ++ ++ ++ A general-purpose extension usually should not assume that it's been ++ installed into a secure schema, which means that even schema-qualified ++ references to its own objects are not entirely risk-free. For ++ example, if the extension has defined a ++ function myschema.myfunc(bigint) then a call such ++ as myschema.myfunc(42) could be captured by a ++ hostile function myschema.myfunc(integer). Be ++ careful that the data types of function and operator parameters exactly ++ match the declared argument types, using explicit casts where necessary. ++ ++ ++ ++ ++ Security Considerations for Extension Scripts ++ ++ ++ An extension installation or update script should be written to guard ++ against search-path-based attacks occurring when the script executes. ++ If an object reference in the script can be made to resolve to some ++ other object than the script author intended, then a compromise might ++ occur immediately, or later when the mis-defined extension object is ++ used. ++ ++ ++ ++ DDL commands such as CREATE FUNCTION ++ and CREATE OPERATOR CLASS are generally secure, ++ but beware of any command having a general-purpose expression as a ++ component. For example, CREATE VIEW needs to be ++ vetted, as does a DEFAULT expression ++ in CREATE FUNCTION. ++ ++ ++ ++ Sometimes an extension script might need to execute general-purpose ++ SQL, for example to make catalog adjustments that aren't possible via ++ DDL. Be careful to execute such commands with a ++ secure search_path; do not ++ trust the path provided by CREATE/ALTER EXTENSION ++ to be secure. Best practice is to temporarily ++ set search_path to 'pg_catalog, ++ pg_temp' and insert references to the extension's ++ installation schema explicitly where needed. (This practice might ++ also be helpful for creating views.) Examples can be found in ++ the contrib modules in ++ the PostgreSQL source code distribution. ++ ++ ++ ++ Cross-extension references are extremely difficult to make fully ++ secure, partially because of uncertainty about which schema the other ++ extension is in. The hazards are reduced if both extensions are ++ installed in the same schema, because then a hostile object cannot be ++ placed ahead of the referenced extension in the installation-time ++ search_path. However, no mechanism currently exists ++ to require that. For now, best practice is to not mark an extension ++ trusted if it depends on another one, unless that other one is always ++ installed in pg_catalog. ++ ++ ++ ++ Do not use CREATE OR REPLACE ++ FUNCTION, except in an update script that must change the ++ definition of a function that is known to be an extension member ++ already. (Likewise for other OR REPLACE options.) ++ Using OR REPLACE unnecessarily not only has a risk ++ of accidentally overwriting someone else's function, but it creates a ++ security hazard since the overwritten function would still be owned by ++ its original owner, who could modify it. ++ ++ ++ ++ + + Extension Example + +@@ -981,18 +1138,18 @@ SELECT * FROM pg_extension_update_paths('extension_name'); + + CREATE TYPE pair AS ( k text, v text ); + +-CREATE OR REPLACE FUNCTION pair(text, text) ++CREATE FUNCTION pair(text, text) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@extschema@.pair;'; + + CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair); + + -- "SET search_path" is easy to get right, but qualified names perform better. +-CREATE OR REPLACE FUNCTION lower(pair) ++CREATE FUNCTION lower(pair) + RETURNS pair LANGUAGE SQL + AS 'SELECT ROW(lower($1.k), lower($1.v))::@extschema@.pair;' + SET search_path = pg_temp; + +-CREATE OR REPLACE FUNCTION pair_concat(pair, pair) ++CREATE FUNCTION pair_concat(pair, pair) + RETURNS pair LANGUAGE SQL + AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k, + $1.v OPERATOR(pg_catalog.||) $2.v)::@extschema@.pair;'; +@@ -1007,6 +1164,7 @@ AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k, + # pair extension + comment = 'A key/value pair data type' + default_version = '1.0' ++# cannot be relocatable because of use of @extschema@ + relocatable = false + + +diff --git a/doc/src/sgml/hstore.sgml b/doc/src/sgml/hstore.sgml +index db5d440..fee2812 100644 +--- a/doc/src/sgml/hstore.sgml ++++ b/doc/src/sgml/hstore.sgml +@@ -633,6 +633,15 @@ ALTER TABLE tablename ALTER hstorecol TYPE hstore USING hstorecol || ''; + convention). If you use them, hstore values are mapped to + Python dictionaries. + ++ ++ ++ ++ It is strongly recommended that the transform extensions be installed in ++ the same schema as hstore. Otherwise there are ++ installation-time security hazards if a transform extension's schema ++ contains objects defined by a hostile user. ++ ++ + + + +diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml +index 77599ad..16d6a9b 100644 +--- a/doc/src/sgml/ltree.sgml ++++ b/doc/src/sgml/ltree.sgml +@@ -675,6 +675,15 @@ ltreetest=> SELECT ins_label(path,2,'Space') FROM test WHERE path <@ 'Top. + creating a function, ltree values are mapped to Python lists. + (The reverse is currently not supported, however.) + ++ ++ ++ ++ It is strongly recommended that the transform extensions be installed in ++ the same schema as ltree. Otherwise there are ++ installation-time security hazards if a transform extension's schema ++ contains objects defined by a hostile user. ++ ++ + + + +diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml +index 14e9101..8016c78 100644 +--- a/doc/src/sgml/ref/create_extension.sgml ++++ b/doc/src/sgml/ref/create_extension.sgml +@@ -193,6 +193,33 @@ CREATE EXTENSION [ IF NOT EXISTS ] extension_name + system views. + + ++ ++ ++ Installing an extension as superuser requires trusting that the ++ extension's author wrote the extension installation script in a secure ++ fashion. It is not terribly difficult for a malicious user to create ++ trojan-horse objects that will compromise later execution of a ++ carelessly-written extension script, allowing that user to acquire ++ superuser privileges. However, trojan-horse objects are only hazardous ++ if they are in the search_path during script ++ execution, meaning that they are in the extension's installation target ++ schema or in the schema of some extension it depends on. Therefore, a ++ good rule of thumb when dealing with extensions whose scripts have not ++ been carefully vetted is to install them only into schemas for which ++ CREATE privilege has not been and will not be granted to any untrusted ++ users. Likewise for any extensions they depend on. ++ ++ ++ ++ The extensions supplied with PostgreSQL are ++ believed to be secure against installation-time attacks of this sort, ++ except for a few that depend on other extensions. As stated in the ++ documentation for those extensions, they should be installed into secure ++ schemas, or installed into the same schemas as the extensions they ++ depend on, or both. ++ ++ ++ + + For information about writing new extensions, see + . +@@ -204,8 +231,13 @@ CREATE EXTENSION [ IF NOT EXISTS ] extension_name + + + Install the hstore extension into the +- current database: ++ current database, placing its objects in schema addons: ++ ++CREATE EXTENSION hstore SCHEMA addons; ++ ++ Another way to accomplish the same thing: + ++SET search_path = addons; + CREATE EXTENSION hstore; + + +diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c +index e4340ee..068c463 100644 +--- a/src/backend/commands/extension.c ++++ b/src/backend/commands/extension.c +@@ -838,9 +838,21 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, + GUC_ACTION_SAVE, true, 0, false); + + /* +- * Set up the search path to contain the target schema, then the schemas +- * of any prerequisite extensions, and nothing else. In particular this +- * makes the target schema be the default creation target namespace. ++ * Similarly disable check_function_bodies, to ensure that SQL functions ++ * won't be parsed during creation. ++ */ ++ if (check_function_bodies) ++ (void) set_config_option("check_function_bodies", "off", ++ PGC_USERSET, PGC_S_SESSION, ++ GUC_ACTION_SAVE, true, 0, false); ++ ++ /* ++ * Set up the search path to have the target schema first, making it be ++ * the default creation target namespace. Then add the schemas of any ++ * prerequisite extensions, unless they are in pg_catalog which would be ++ * searched anyway. (Listing pg_catalog explicitly in a non-first ++ * position would be bad for security.) Finally add pg_temp to ensure ++ * that temp objects can't take precedence over others. + * + * Note: it might look tempting to use PushOverrideSearchPath for this, + * but we cannot do that. We have to actually set the search_path GUC in +@@ -854,9 +866,10 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, + Oid reqschema = lfirst_oid(lc); + char *reqname = get_namespace_name(reqschema); + +- if (reqname) ++ if (reqname && strcmp(reqname, "pg_catalog") != 0) + appendStringInfo(&pathbuf, ", %s", quote_identifier(reqname)); + } ++ appendStringInfoString(&pathbuf, ", pg_temp"); + + (void) set_config_option("search_path", pathbuf.data, + PGC_USERSET, PGC_S_SESSION, +diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c +index 6674b41..b637cd4 100644 +--- a/src/backend/commands/operatorcmds.c ++++ b/src/backend/commands/operatorcmds.c +@@ -297,6 +297,7 @@ ValidateJoinEstimator(List *joinName) + { + Oid typeId[5]; + Oid joinOid; ++ Oid joinOid2; + AclResult aclresult; + + typeId[0] = INTERNALOID; /* PlannerInfo */ +@@ -307,15 +308,26 @@ ValidateJoinEstimator(List *joinName) + + /* + * As of Postgres 8.4, the preferred signature for join estimators has 5 +- * arguments, but we still allow the old 4-argument form. Try the +- * preferred form first. ++ * arguments, but we still allow the old 4-argument form. Whine about ++ * ambiguity if both forms exist. + */ + joinOid = LookupFuncName(joinName, 5, typeId, true); +- if (!OidIsValid(joinOid)) +- joinOid = LookupFuncName(joinName, 4, typeId, true); +- /* If not found, reference the 5-argument signature in error msg */ +- if (!OidIsValid(joinOid)) +- joinOid = LookupFuncName(joinName, 5, typeId, false); ++ joinOid2 = LookupFuncName(joinName, 4, typeId, true); ++ if (OidIsValid(joinOid)) ++ { ++ if (OidIsValid(joinOid2)) ++ ereport(ERROR, ++ (errcode(ERRCODE_AMBIGUOUS_FUNCTION), ++ errmsg("join estimator function %s has multiple matches", ++ NameListToString(joinName)))); ++ } ++ else ++ { ++ joinOid = joinOid2; ++ /* If not found, reference the 5-argument signature in error msg */ ++ if (!OidIsValid(joinOid)) ++ joinOid = LookupFuncName(joinName, 5, typeId, false); ++ } + + /* estimators must return float8 */ + if (get_func_rettype(joinOid) != FLOAT8OID) + +diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c +index 8e5134b..6172d47 100644 +--- a/src/backend/commands/typecmds.c ++++ b/src/backend/commands/typecmds.c +@@ -1659,7 +1659,11 @@ static Oid + findTypeInputFunction(List *procname, Oid typeOid) + { + Oid argList[3]; ++ int nmatches = 0; + Oid procOid; ++ Oid procOid2; ++ Oid procOid3; ++ Oid procOid4; + + /* + * Input functions can take a single argument of type CSTRING, or three +@@ -1670,29 +1674,42 @@ findTypeInputFunction(List *procname, Oid typeOid) + */ + argList[0] = CSTRINGOID; + +- procOid = LookupFuncName(procname, 1, argList, true); +- if (OidIsValid(procOid)) +- return procOid; +- + argList[1] = OIDOID; + argList[2] = INT4OID; + +- procOid = LookupFuncName(procname, 3, argList, true); ++ procOid = LookupFuncName(procname, 1, argList, true); + if (OidIsValid(procOid)) +- return procOid; ++ nmatches++; ++ procOid2 = LookupFuncName(procname, 3, argList, true); ++ if (OidIsValid(procOid2)) ++ nmatches++; + + /* No luck, try it with OPAQUE */ + argList[0] = OPAQUEOID; + +- procOid = LookupFuncName(procname, 1, argList, true); ++ procOid3 = LookupFuncName(procname, 1, argList, true); ++ if (OidIsValid(procOid3)) ++ nmatches++; ++ procOid4 = LookupFuncName(procname, 3, argList, true); ++ if (OidIsValid(procOid4)) ++ nmatches++; + +- if (!OidIsValid(procOid)) +- { +- argList[1] = OIDOID; +- argList[2] = INT4OID; ++ if (nmatches > 1) ++ ereport(ERROR, ++ (errcode(ERRCODE_AMBIGUOUS_FUNCTION), ++ errmsg("type input function %s has multiple matches", ++ NameListToString(procname)))); + +- procOid = LookupFuncName(procname, 3, argList, true); +- } ++ if (OidIsValid(procOid)) ++ return procOid; ++ if (OidIsValid(procOid2)) ++ return procOid2; ++ ++ /* Cases with OPAQUE need adjustment */ ++ if (OidIsValid(procOid3)) ++ procOid = procOid3; ++ else ++ procOid = procOid4; + + if (OidIsValid(procOid)) + { +@@ -1778,6 +1795,7 @@ findTypeReceiveFunction(List *procname, Oid typeOid) + { + Oid argList[3]; + Oid procOid; ++ Oid procOid2; + + /* + * Receive functions can take a single argument of type INTERNAL, or three +@@ -1785,17 +1803,24 @@ findTypeReceiveFunction(List *procname, Oid typeOid) + */ + argList[0] = INTERNALOID; + +- procOid = LookupFuncName(procname, 1, argList, true); +- if (OidIsValid(procOid)) +- return procOid; +- + argList[1] = OIDOID; + argList[2] = INT4OID; + +- procOid = LookupFuncName(procname, 3, argList, true); ++ procOid = LookupFuncName(procname, 1, argList, true); ++ procOid2 = LookupFuncName(procname, 3, argList, true); + if (OidIsValid(procOid)) ++ { ++ if (OidIsValid(procOid2)) ++ ereport(ERROR, ++ (errcode(ERRCODE_AMBIGUOUS_FUNCTION), ++ errmsg("type receive function %s has multiple matches", ++ NameListToString(procname)))); + return procOid; ++ } ++ else if (OidIsValid(procOid2)) ++ return procOid2; + ++ /* If not found, reference the 1-argument signature in error msg */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", +-- +2.23.0 + diff --git a/CVE-2020-25694-1.patch b/CVE-2020-25694-1.patch new file mode 100644 index 0000000000000000000000000000000000000000..36d5d8fa7b46345bc48f55e62c49fe3ead9e25ec --- /dev/null +++ b/CVE-2020-25694-1.patch @@ -0,0 +1,492 @@ +From a062fb822382398c7282810029016400feecf644 Mon Sep 17 00:00:00 2001 +From: Tom Lane +Date: Wed, 21 Oct 2020 16:18:41 -0400 +Subject: [PATCH] Fix connection string handling in psql's \connect command. + +psql's \connect claims to be able to re-use previous connection +parameters, but in fact it only re-uses the database name, user name, +host name (and possibly hostaddr, depending on version), and port. +This is problematic for assorted use cases. Notably, pg_dump[all] +emits "\connect databasename" commands which we would like to have +re-use all other parameters. If such a script is loaded in a psql run +that initially had "-d connstring" with some non-default parameters, +those other parameters would be lost, potentially causing connection +failure. (Thus, this is the same kind of bug addressed in commits +a45bc8a4f and 8e5793ab6, although the details are much different.) + +To fix, redesign do_connect() so that it pulls out all properties +of the old PGconn using PQconninfo(), and then replaces individual +properties in that array. In the case where we don't wish to re-use +anything, get libpq's default settings using PQconndefaults() and +replace entries in that, so that we don't need different code paths +for the two cases. + +This does result in an additional behavioral change for cases where +the original connection parameters allowed multiple hosts, say +"psql -h host1,host2", and the \connect request allows re-use of the +host setting. Because the previous coding relied on PQhost(), it +would only permit reconnection to the same host originally selected. +Although one can think of scenarios where that's a good thing, there +are others where it is not. Moreover, that behavior doesn't seem to +meet the principle of least surprise, nor was it documented; nor is +it even clear it was intended, since that coding long pre-dates the +addition of multi-host support to libpq. Hence, this patch is content +to drop it and re-use the host list as given. + +Per Peter Eisentraut's comments on bug #16604. Back-patch to all +supported branches. + +Discussion: https://postgr.es/m/16604-933f4b8791227b15@postgresql.org +--- + doc/src/sgml/ref/psql-ref.sgml | 44 ++++-- + src/bin/psql/command.c | 272 ++++++++++++++++++++++++--------- + 2 files changed, 230 insertions(+), 86 deletions(-) + +diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml +index c8388513b8..7fda949f3b 100644 +--- a/doc/src/sgml/ref/psql-ref.sgml ++++ b/doc/src/sgml/ref/psql-ref.sgml +@@ -875,21 +875,17 @@ testdb=> + \c or \connect [ -reuse-previous=on|off ] [ dbname [ username ] [ host ] [ port ] | conninfo ] + + +- Establishes a new connection to a PostgreSQL ++ Establishes a new connection to a PostgreSQL + server. The connection parameters to use can be specified either +- using a positional syntax, or using conninfo connection +- strings as detailed in . ++ using a positional syntax (one or more of database name, user, ++ host, and port), or using a conninfo ++ connection string as detailed in ++ . If no arguments are given, a ++ new connection is made using the same parameters as before. + + + +- Where the command omits database name, user, host, or port, the new +- connection can reuse values from the previous connection. By default, +- values from the previous connection are reused except when processing +- a conninfo string. Passing a first argument +- of -reuse-previous=on +- or -reuse-previous=off overrides that default. +- When the command neither specifies nor reuses a particular parameter, +- the libpq default is used. Specifying any ++ Specifying any + of dbname, + username, + host or +@@ -897,12 +893,31 @@ testdb=> + as - is equivalent to omitting that parameter. + + ++ ++ The new connection can re-use connection parameters from the previous ++ connection; not only database name, user, host, and port, but other ++ settings such as sslmode. By default, ++ parameters are re-used in the positional syntax, but not when ++ a conninfo string is given. Passing a ++ first argument of -reuse-previous=on ++ or -reuse-previous=off overrides that default. If ++ parameters are re-used, then any parameter not explicitly specified as ++ a positional parameter or in the conninfo ++ string is taken from the existing connection's parameters. An ++ exception is that if the host setting ++ is changed from its previous value using the positional syntax, ++ any hostaddr setting present in the ++ existing connection's parameters is dropped. ++ When the command neither specifies nor reuses a particular parameter, ++ the libpq default is used. ++ ++ + + If the new connection is successfully made, the previous + connection is closed. +- If the connection attempt failed (wrong user name, access +- denied, etc.), the previous connection will only be kept if +- psql is in interactive mode. When ++ If the connection attempt fails (wrong user name, access ++ denied, etc.), the previous connection will be kept if ++ psql is in interactive mode. But when + executing a non-interactive script, processing will + immediately stop with an error. This distinction was chosen as + a user convenience against typos on the one hand, and a safety +@@ -917,6 +932,7 @@ testdb=> + => \c mydb myuser host.dom 6432 + => \c service=foo + => \c "host=localhost port=5432 dbname=mydb connect_timeout=10 sslmode=disable" ++=> \c -reuse-previous=on sslmode=require -- changes only sslmode + => \c postgresql://tom@localhost/mydb?application_name=myapp + + +diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c +index a30a5f8ea0..91f7f0adc3 100644 +--- a/src/bin/psql/command.c ++++ b/src/bin/psql/command.c +@@ -2979,12 +2979,15 @@ do_connect(enum trivalue reuse_previous_specification, + char *dbname, char *user, char *host, char *port) + { + PGconn *o_conn = pset.db, +- *n_conn; ++ *n_conn = NULL; ++ PQconninfoOption *cinfo; ++ int nconnopts = 0; ++ bool same_host = false; + char *password = NULL; +- bool keep_password; ++ bool success = true; ++ bool keep_password = true; + bool has_connection_string; + bool reuse_previous; +- PQExpBufferData connstr; + + if (!o_conn && (!dbname || !user || !host || !port)) + { +@@ -3012,6 +3015,11 @@ do_connect(enum trivalue reuse_previous_specification, + reuse_previous = !has_connection_string; + break; + } ++ ++ /* If the old connection does not exist, there is nothing to reuse. */ ++ if (!o_conn) ++ reuse_previous = false; ++ + /* Silently ignore arguments subsequent to a connection string. */ + if (has_connection_string) + { +@@ -3020,41 +3028,126 @@ do_connect(enum trivalue reuse_previous_specification, + port = NULL; + } + +- /* grab missing values from the old connection */ +- if (!user && reuse_previous) +- user = PQuser(o_conn); +- if (!host && reuse_previous) +- host = PQhost(o_conn); +- if (!port && reuse_previous) +- port = PQport(o_conn); +- + /* +- * Any change in the parameters read above makes us discard the password. +- * We also discard it if we're to use a conninfo rather than the +- * positional syntax. ++ * If we intend to re-use connection parameters, collect them out of the ++ * old connection, then replace individual values as necessary. Otherwise, ++ * obtain a PQconninfoOption array containing libpq's defaults, and modify ++ * that. Note this function assumes that PQconninfo, PQconndefaults, and ++ * PQconninfoParse will all produce arrays containing the same options in ++ * the same order. + */ +- if (has_connection_string) +- keep_password = false; ++ if (reuse_previous) ++ cinfo = PQconninfo(o_conn); + else +- keep_password = +- (user && PQuser(o_conn) && strcmp(user, PQuser(o_conn)) == 0) && +- (host && PQhost(o_conn) && strcmp(host, PQhost(o_conn)) == 0) && +- (port && PQport(o_conn) && strcmp(port, PQport(o_conn)) == 0); ++ cinfo = PQconndefaults(); + +- /* +- * Grab missing dbname from old connection. No password discard if this +- * changes: passwords aren't (usually) database-specific. +- */ +- if (!dbname && reuse_previous) ++ if (cinfo) + { +- initPQExpBuffer(&connstr); +- appendPQExpBuffer(&connstr, "dbname="); +- appendConnStrVal(&connstr, PQdb(o_conn)); +- dbname = connstr.data; +- /* has_connection_string=true would be a dead store */ ++ if (has_connection_string) ++ { ++ /* Parse the connstring and insert values into cinfo */ ++ PQconninfoOption *replcinfo; ++ char *errmsg; ++ ++ replcinfo = PQconninfoParse(dbname, &errmsg); ++ if (replcinfo) ++ { ++ PQconninfoOption *ci; ++ PQconninfoOption *replci; ++ ++ for (ci = cinfo, replci = replcinfo; ++ ci->keyword && replci->keyword; ++ ci++, replci++) ++ { ++ Assert(strcmp(ci->keyword, replci->keyword) == 0); ++ /* Insert value from connstring if one was provided */ ++ if (replci->val) ++ { ++ /* ++ * We know that both val strings were allocated by ++ * libpq, so the least messy way to avoid memory leaks ++ * is to swap them. ++ */ ++ char *swap = replci->val; ++ ++ replci->val = ci->val; ++ ci->val = swap; ++ } ++ } ++ Assert(ci->keyword == NULL && replci->keyword == NULL); ++ ++ /* While here, determine how many option slots there are */ ++ nconnopts = ci - cinfo; ++ ++ PQconninfoFree(replcinfo); ++ ++ /* We never re-use a password with a conninfo string. */ ++ keep_password = false; ++ ++ /* Don't let code below try to inject dbname into params. */ ++ dbname = NULL; ++ } ++ else ++ { ++ /* PQconninfoParse failed */ ++ if (errmsg) ++ { ++ psql_error("%s", errmsg); ++ PQfreemem(errmsg); ++ } ++ else ++ psql_error("out of memory\n"); ++ success = false; ++ } ++ } ++ else ++ { ++ /* ++ * If dbname isn't a connection string, then we'll inject it and ++ * the other parameters into the keyword array below. (We can't ++ * easily insert them into the cinfo array because of memory ++ * management issues: PQconninfoFree would misbehave on Windows.) ++ * However, to avoid dependencies on the order in which parameters ++ * appear in the array, make a preliminary scan to set ++ * keep_password and same_host correctly. ++ * ++ * While any change in user, host, or port causes us to ignore the ++ * old connection's password, we don't force that for dbname, ++ * since passwords aren't database-specific. ++ */ ++ PQconninfoOption *ci; ++ ++ for (ci = cinfo; ci->keyword; ci++) ++ { ++ if (user && strcmp(ci->keyword, "user") == 0) ++ { ++ if (!(ci->val && strcmp(user, ci->val) == 0)) ++ keep_password = false; ++ } ++ else if (host && strcmp(ci->keyword, "host") == 0) ++ { ++ if (ci->val && strcmp(host, ci->val) == 0) ++ same_host = true; ++ else ++ keep_password = false; ++ } ++ else if (port && strcmp(ci->keyword, "port") == 0) ++ { ++ if (!(ci->val && strcmp(port, ci->val) == 0)) ++ keep_password = false; ++ } ++ } ++ ++ /* While here, determine how many option slots there are */ ++ nconnopts = ci - cinfo; ++ } + } + else +- connstr.data = NULL; ++ { ++ /* We failed to create the cinfo structure */ ++ psql_error("out of memory\n"); ++ success = false; ++ } + + /* + * If the user asked to be prompted for a password, ask for one now. If +@@ -3066,7 +3159,7 @@ do_connect(enum trivalue reuse_previous_specification, + * the postmaster's log. But libpq offers no API that would let us obtain + * a password and then continue with the first connection attempt. + */ +- if (pset.getPassword == TRI_YES) ++ if (pset.getPassword == TRI_YES && success) + { + password = prompt_for_password(user); + } +@@ -3079,52 +3172,60 @@ do_connect(enum trivalue reuse_previous_specification, + password = NULL; + } + +- while (true) ++ /* Loop till we have a connection or fail, which we might've already */ ++ while (success) + { +-#define PARAMS_ARRAY_SIZE 8 +- const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); +- const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); +- int paramnum = -1; +- +- keywords[++paramnum] = "host"; +- values[paramnum] = host; +- keywords[++paramnum] = "port"; +- values[paramnum] = port; +- keywords[++paramnum] = "user"; +- values[paramnum] = user; ++ const char **keywords = pg_malloc((nconnopts + 1) * sizeof(*keywords)); ++ const char **values = pg_malloc((nconnopts + 1) * sizeof(*values)); ++ int paramnum = 0; ++ PQconninfoOption *ci; + + /* +- * Position in the array matters when the dbname is a connection +- * string, because settings in a connection string override earlier +- * array entries only. Thus, user= in the connection string always +- * takes effect, but client_encoding= often will not. ++ * Copy non-default settings into the PQconnectdbParams parameter ++ * arrays; but override any values specified old-style, as well as the ++ * password and a couple of fields we want to set forcibly. + * +- * If you change this code, also change the initial-connection code in ++ * If you change this code, see also the initial-connection code in + * main(). For no good reason, a connection string password= takes + * precedence in main() but not here. + */ +- keywords[++paramnum] = "dbname"; +- values[paramnum] = dbname; +- keywords[++paramnum] = "password"; +- values[paramnum] = password; +- keywords[++paramnum] = "fallback_application_name"; +- values[paramnum] = pset.progname; +- keywords[++paramnum] = "client_encoding"; +- values[paramnum] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; +- ++ for (ci = cinfo; ci->keyword; ci++) ++ { ++ keywords[paramnum] = ci->keyword; ++ ++ if (dbname && strcmp(ci->keyword, "dbname") == 0) ++ values[paramnum++] = dbname; ++ else if (user && strcmp(ci->keyword, "user") == 0) ++ values[paramnum++] = user; ++ else if (host && strcmp(ci->keyword, "host") == 0) ++ values[paramnum++] = host; ++ else if (host && !same_host && strcmp(ci->keyword, "hostaddr") == 0) ++ { ++ /* If we're changing the host value, drop any old hostaddr */ ++ values[paramnum++] = NULL; ++ } ++ else if (port && strcmp(ci->keyword, "port") == 0) ++ values[paramnum++] = port; ++ else if (strcmp(ci->keyword, "password") == 0) ++ values[paramnum++] = password; ++ else if (strcmp(ci->keyword, "fallback_application_name") == 0) ++ values[paramnum++] = pset.progname; ++ else if (strcmp(ci->keyword, "client_encoding") == 0) ++ values[paramnum++] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; ++ else if (ci->val) ++ values[paramnum++] = ci->val; ++ /* else, don't bother making libpq parse this keyword */ ++ } + /* add array terminator */ +- keywords[++paramnum] = NULL; ++ keywords[paramnum] = NULL; + values[paramnum] = NULL; + +- n_conn = PQconnectdbParams(keywords, values, true); ++ /* Note we do not want libpq to re-expand the dbname parameter */ ++ n_conn = PQconnectdbParams(keywords, values, false); + + pg_free(keywords); + pg_free(values); + +- /* We can immediately discard the password -- no longer needed */ +- if (password) +- pg_free(password); +- + if (PQstatus(n_conn) == CONNECTION_OK) + break; + +@@ -3134,11 +3235,34 @@ do_connect(enum trivalue reuse_previous_specification, + */ + if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO) + { ++ /* ++ * Prompt for password using the username we actually connected ++ * with --- it might've come out of "dbname" rather than "user". ++ */ ++ password = prompt_for_password(PQuser(n_conn)); + PQfinish(n_conn); +- password = prompt_for_password(user); ++ n_conn = NULL; + continue; + } + ++ /* ++ * We'll report the error below ... unless n_conn is NULL, indicating ++ * that libpq didn't have enough memory to make a PGconn. ++ */ ++ if (n_conn == NULL) ++ psql_error("out of memory\n"); ++ ++ success = false; ++ } /* end retry loop */ ++ ++ /* Release locally allocated data, whether we succeeded or not */ ++ if (password) ++ pg_free(password); ++ if (cinfo) ++ PQconninfoFree(cinfo); ++ ++ if (!success) ++ { + /* + * Failed to connect to the database. In interactive mode, keep the + * previous connection to the DB; in scripting mode, close our +@@ -3146,7 +3270,11 @@ do_connect(enum trivalue reuse_previous_specification, + */ + if (pset.cur_cmd_interactive) + { +- psql_error("%s", PQerrorMessage(n_conn)); ++ if (n_conn) ++ { ++ psql_error("%s", PQerrorMessage(n_conn)); ++ PQfinish(n_conn); ++ } + + /* pset.db is left unmodified */ + if (o_conn) +@@ -3154,7 +3282,12 @@ do_connect(enum trivalue reuse_previous_specification, + } + else + { +- psql_error("\\connect: %s", PQerrorMessage(n_conn)); ++ if (n_conn) ++ { ++ psql_error("\\connect: %s", PQerrorMessage(n_conn)); ++ PQfinish(n_conn); ++ } ++ + if (o_conn) + { + PQfinish(o_conn); +@@ -3162,13 +3295,8 @@ do_connect(enum trivalue reuse_previous_specification, + } + } + +- PQfinish(n_conn); +- if (connstr.data) +- termPQExpBuffer(&connstr); + return false; + } +- if (connstr.data) +- termPQExpBuffer(&connstr); + + /* + * Replace the old connection with the new one, and update +-- +2.23.0 + diff --git a/CVE-2020-25694-2.patch b/CVE-2020-25694-2.patch new file mode 100644 index 0000000000000000000000000000000000000000..76c621aa182baf6250c0b4861868a88546186e47 --- /dev/null +++ b/CVE-2020-25694-2.patch @@ -0,0 +1,1076 @@ +From 27d64ec8da4c0813c5cb0956e74b332d1db67bc7 Mon Sep 17 00:00:00 2001 +From: Tom Lane +Date: Mon, 19 Oct 2020 19:03:47 -0400 +Subject: [PATCH 2/2] Fix connection string handling in src/bin/scripts/ + programs. + +When told to process all databases, clusterdb, reindexdb, and vacuumdb +would reconnect by replacing their --maintenance-db parameter with the +name of the target database. If that parameter is a connstring (which +has been allowed for a long time, though we failed to document that +before this patch), we'd lose any other options it might specify, for +example SSL or GSS parameters, possibly resulting in failure to connect. +Thus, this is the same bug as commit a45bc8a4f fixed in pg_dump and +pg_restore. We can fix it in the same way, by using libpq's rules for +handling multiple "dbname" parameters to add the target database name +separately. I chose to apply the same refactoring approach as in that +patch, with a struct to handle the command line parameters that need to +be passed through to connectDatabase. (Maybe someday we can unify the +very similar functions here and in pg_dump/pg_restore.) + +Per Peter Eisentraut's comments on bug #16604. Back-patch to all +supported branches. + +Discussion: https://postgr.es/m/16604-933f4b8791227b15@postgresql.org +--- + doc/src/sgml/ref/clusterdb.sgml | 20 +++++--- + doc/src/sgml/ref/createdb.sgml | 3 ++ + doc/src/sgml/ref/dropdb.sgml | 3 ++ + doc/src/sgml/ref/reindexdb.sgml | 20 +++++--- + doc/src/sgml/ref/vacuumdb.sgml | 20 +++++--- + src/bin/scripts/clusterdb.c | 67 +++++++++++-------------- + src/bin/scripts/common.c | 89 ++++++++++++++++++++------------- + src/bin/scripts/common.h | 26 +++++++--- + src/bin/scripts/createdb.c | 11 +++- + src/bin/scripts/createuser.c | 11 +++- + src/bin/scripts/dropdb.c | 12 +++-- + src/bin/scripts/dropuser.c | 13 +++-- + src/bin/scripts/reindexdb.c | 85 +++++++++++++++---------------- + src/bin/scripts/vacuumdb.c | 70 +++++++++++--------------- + 14 files changed, 254 insertions(+), 196 deletions(-) + +diff --git a/doc/src/sgml/ref/clusterdb.sgml b/doc/src/sgml/ref/clusterdb.sgml +index 67582fd6e6..e3e2c0cff7 100644 +--- a/doc/src/sgml/ref/clusterdb.sgml ++++ b/doc/src/sgml/ref/clusterdb.sgml +@@ -90,9 +90,9 @@ PostgreSQL documentation + + + +- Specifies the name of the database to be clustered. +- If this is not specified and (or +- ) is not used, the database name is read ++ Specifies the name of the database to be clustered, ++ when / is not used. ++ If this is not specified, the database name is read + from the environment variable PGDATABASE. If + that is not set, the user name specified for the connection is + used. +@@ -246,10 +246,16 @@ PostgreSQL documentation + + + +- Specifies the name of the database to connect to discover what other +- databases should be clustered. If not specified, the +- postgres database will be used, +- and if that does not exist, template1 will be used. ++ Specifies the name of the database to connect to to discover which ++ databases should be clustered, ++ when / is used. ++ If not specified, the postgres database will be used, ++ or if that does not exist, template1 will be used. ++ This can be a connection ++ string. If so, connection string parameters will override any ++ conflicting command line options. Also, connection string parameters ++ other than the database name itself will be re-used when connecting ++ to other databases. + + + +diff --git a/doc/src/sgml/ref/createdb.sgml b/doc/src/sgml/ref/createdb.sgml +index 9fc4c16a81..7bb46a0fb2 100644 +--- a/doc/src/sgml/ref/createdb.sgml ++++ b/doc/src/sgml/ref/createdb.sgml +@@ -286,6 +286,9 @@ PostgreSQL documentation + database will be used; if that does not exist (or if it is the name + of the new database being created), template1 will + be used. ++ This can be a connection ++ string. If so, connection string parameters will override any ++ conflicting command line options. + + + +diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml +index 16c49e7928..0d7170d665 100644 +--- a/doc/src/sgml/ref/dropdb.sgml ++++ b/doc/src/sgml/ref/dropdb.sgml +@@ -205,6 +205,9 @@ PostgreSQL documentation + target database. If not specified, the postgres + database will be used; if that does not exist (or is the database + being dropped), template1 will be used. ++ This can be a connection ++ string. If so, connection string parameters will override any ++ conflicting command line options. + + + +diff --git a/doc/src/sgml/ref/reindexdb.sgml b/doc/src/sgml/ref/reindexdb.sgml +index e4721d8113..202e45e6ae 100644 +--- a/doc/src/sgml/ref/reindexdb.sgml ++++ b/doc/src/sgml/ref/reindexdb.sgml +@@ -123,9 +123,9 @@ PostgreSQL documentation + + + +- Specifies the name of the database to be reindexed. +- If this is not specified and (or +- ) is not used, the database name is read ++ Specifies the name of the database to be reindexed, ++ when / is not used. ++ If this is not specified, the database name is read + from the environment variable PGDATABASE. If + that is not set, the user name specified for the connection is + used. +@@ -314,10 +314,16 @@ PostgreSQL documentation + + + +- Specifies the name of the database to connect to discover what other +- databases should be reindexed. If not specified, the +- postgres database will be used, +- and if that does not exist, template1 will be used. ++ Specifies the name of the database to connect to to discover which ++ databases should be reindexed, ++ when / is used. ++ If not specified, the postgres database will be used, ++ or if that does not exist, template1 will be used. ++ This can be a connection ++ string. If so, connection string parameters will override any ++ conflicting command line options. Also, connection string parameters ++ other than the database name itself will be re-used when connecting ++ to other databases. + + + +diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml +index 4f6fa0d708..64a9f2dba3 100644 +--- a/doc/src/sgml/ref/vacuumdb.sgml ++++ b/doc/src/sgml/ref/vacuumdb.sgml +@@ -92,9 +92,9 @@ PostgreSQL documentation + + + +- Specifies the name of the database to be cleaned or analyzed. +- If this is not specified and (or +- ) is not used, the database name is read ++ Specifies the name of the database to be cleaned or analyzed, ++ when / is not used. ++ If this is not specified, the database name is read + from the environment variable PGDATABASE. If + that is not set, the user name specified for the connection is + used. +@@ -339,10 +339,16 @@ PostgreSQL documentation + + + +- Specifies the name of the database to connect to discover what other +- databases should be vacuumed. If not specified, the +- postgres database will be used, +- and if that does not exist, template1 will be used. ++ Specifies the name of the database to connect to to discover which ++ databases should be vacuumed, ++ when / is used. ++ If not specified, the postgres database will be used, ++ or if that does not exist, template1 will be used. ++ This can be a connection ++ string. If so, connection string parameters will override any ++ conflicting command line options. Also, connection string parameters ++ other than the database name itself will be re-used when connecting ++ to other databases. + + + +diff --git a/src/bin/scripts/clusterdb.c b/src/bin/scripts/clusterdb.c +index c48b0cb08f..e53cc92405 100644 +--- a/src/bin/scripts/clusterdb.c ++++ b/src/bin/scripts/clusterdb.c +@@ -15,15 +15,10 @@ + #include "fe_utils/string_utils.h" + + +-static void cluster_one_database(const char *dbname, bool verbose, const char *table, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, +- const char *progname, bool echo); +-static void cluster_all_databases(bool verbose, const char *maintenance_db, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, +- const char *progname, bool echo, bool quiet); +- ++static void cluster_one_database(const ConnParams *cparams, const char *table, ++ const char *progname, bool verbose, bool echo); ++static void cluster_all_databases(ConnParams *cparams, const char *progname, ++ bool verbose, bool echo, bool quiet); + static void help(const char *progname); + + +@@ -56,6 +51,7 @@ main(int argc, char *argv[]) + char *port = NULL; + char *username = NULL; + enum trivalue prompt_password = TRI_DEFAULT; ++ ConnParams cparams; + bool echo = false; + bool quiet = false; + bool alldb = false; +@@ -131,6 +127,13 @@ main(int argc, char *argv[]) + exit(1); + } + ++ /* fill cparams except for dbname, which is set below */ ++ cparams.pghost = host; ++ cparams.pgport = port; ++ cparams.pguser = username; ++ cparams.prompt_password = prompt_password; ++ cparams.override_dbname = NULL; ++ + setup_cancel_handler(); + + if (alldb) +@@ -149,8 +152,9 @@ main(int argc, char *argv[]) + exit(1); + } + +- cluster_all_databases(verbose, maintenance_db, host, port, username, prompt_password, +- progname, echo, quiet); ++ cparams.dbname = maintenance_db; ++ ++ cluster_all_databases(&cparams, progname, verbose, echo, quiet); + } + else + { +@@ -164,21 +168,21 @@ main(int argc, char *argv[]) + dbname = get_user_name_or_exit(progname); + } + ++ cparams.dbname = dbname; ++ + if (tables.head != NULL) + { + SimpleStringListCell *cell; + + for (cell = tables.head; cell; cell = cell->next) + { +- cluster_one_database(dbname, verbose, cell->val, +- host, port, username, prompt_password, +- progname, echo); ++ cluster_one_database(&cparams, cell->val, ++ progname, verbose, echo); + } + } + else +- cluster_one_database(dbname, verbose, NULL, +- host, port, username, prompt_password, +- progname, echo); ++ cluster_one_database(&cparams, NULL, ++ progname, verbose, echo); + } + + exit(0); +@@ -186,17 +190,14 @@ main(int argc, char *argv[]) + + + static void +-cluster_one_database(const char *dbname, bool verbose, const char *table, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, +- const char *progname, bool echo) ++cluster_one_database(const ConnParams *cparams, const char *table, ++ const char *progname, bool verbose, bool echo) + { + PQExpBufferData sql; + + PGconn *conn; + +- conn = connectDatabase(dbname, host, port, username, prompt_password, +- progname, echo, false, false); ++ conn = connectDatabase(cparams, progname, echo, false, false); + + initPQExpBuffer(&sql); + +@@ -227,22 +228,17 @@ cluster_one_database(const char *dbname, bool verbose, const char *table, + + + static void +-cluster_all_databases(bool verbose, const char *maintenance_db, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, +- const char *progname, bool echo, bool quiet) ++cluster_all_databases(ConnParams *cparams, const char *progname, ++ bool verbose, bool echo, bool quiet) + { + PGconn *conn; + PGresult *result; +- PQExpBufferData connstr; + int i; + +- conn = connectMaintenanceDatabase(maintenance_db, host, port, username, +- prompt_password, progname, echo); ++ conn = connectMaintenanceDatabase(cparams, progname, echo); + result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo); + PQfinish(conn); + +- initPQExpBuffer(&connstr); + for (i = 0; i < PQntuples(result); i++) + { + char *dbname = PQgetvalue(result, i, 0); +@@ -253,15 +249,10 @@ cluster_all_databases(bool verbose, const char *maintenance_db, + fflush(stdout); + } + +- resetPQExpBuffer(&connstr); +- appendPQExpBuffer(&connstr, "dbname="); +- appendConnStrVal(&connstr, dbname); ++ cparams->override_dbname = dbname; + +- cluster_one_database(connstr.data, verbose, NULL, +- host, port, username, prompt_password, +- progname, echo); ++ cluster_one_database(cparams, NULL, progname, verbose, echo); + } +- termPQExpBuffer(&connstr); + + PQclear(result); + } +diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c +index 5088c4c48e..ad70ca4ff9 100644 +--- a/src/bin/scripts/common.c ++++ b/src/bin/scripts/common.c +@@ -57,7 +57,7 @@ handle_help_version_opts(int argc, char *argv[], + * Make a database connection with the given parameters. + * + * An interactive password prompt is automatically issued if needed and +- * allowed by prompt_password. ++ * allowed by cparams->prompt_password. + * + * If allow_password_reuse is true, we will try to re-use any password + * given during previous calls to this routine. (Callers should not pass +@@ -65,9 +65,7 @@ handle_help_version_opts(int argc, char *argv[], + * as before, else we might create password exposure hazards.) + */ + PGconn * +-connectDatabase(const char *dbname, const char *pghost, +- const char *pgport, const char *pguser, +- enum trivalue prompt_password, const char *progname, ++connectDatabase(const ConnParams *cparams, const char *progname, + bool echo, bool fail_ok, bool allow_password_reuse) + { + PGconn *conn; +@@ -75,10 +73,13 @@ connectDatabase(const char *dbname, const char *pghost, + static bool have_password = false; + static char password[100]; + ++ /* Callers must supply at least dbname; other params can be NULL */ ++ Assert(cparams->dbname); ++ + if (!allow_password_reuse) + have_password = false; + +- if (!have_password && prompt_password == TRI_YES) ++ if (cparams->prompt_password == TRI_YES && !have_password) + { + simple_prompt("Password: ", password, sizeof(password), false); + have_password = true; +@@ -90,23 +91,35 @@ connectDatabase(const char *dbname, const char *pghost, + */ + do + { +- const char *keywords[7]; +- const char *values[7]; +- +- keywords[0] = "host"; +- values[0] = pghost; +- keywords[1] = "port"; +- values[1] = pgport; +- keywords[2] = "user"; +- values[2] = pguser; +- keywords[3] = "password"; +- values[3] = have_password ? password : NULL; +- keywords[4] = "dbname"; +- values[4] = dbname; +- keywords[5] = "fallback_application_name"; +- values[5] = progname; +- keywords[6] = NULL; +- values[6] = NULL; ++ const char *keywords[8]; ++ const char *values[8]; ++ int i = 0; ++ ++ /* ++ * If dbname is a connstring, its entries can override the other ++ * values obtained from cparams; but in turn, override_dbname can ++ * override the dbname component of it. ++ */ ++ keywords[i] = "host"; ++ values[i++] = cparams->pghost; ++ keywords[i] = "port"; ++ values[i++] = cparams->pgport; ++ keywords[i] = "user"; ++ values[i++] = cparams->pguser; ++ keywords[i] = "password"; ++ values[i++] = have_password ? password : NULL; ++ keywords[i] = "dbname"; ++ values[i++] = cparams->dbname; ++ if (cparams->override_dbname) ++ { ++ keywords[i] = "dbname"; ++ values[i++] = cparams->override_dbname; ++ } ++ keywords[i] = "fallback_application_name"; ++ values[i++] = progname; ++ keywords[i] = NULL; ++ values[i++] = NULL; ++ Assert(i <= lengthof(keywords)); + + new_pass = false; + conn = PQconnectdbParams(keywords, values, true); +@@ -114,7 +127,7 @@ connectDatabase(const char *dbname, const char *pghost, + if (!conn) + { + fprintf(stderr, _("%s: could not connect to database %s: out of memory\n"), +- progname, dbname); ++ progname, cparams->dbname); + exit(1); + } + +@@ -123,7 +136,7 @@ connectDatabase(const char *dbname, const char *pghost, + */ + if (PQstatus(conn) == CONNECTION_BAD && + PQconnectionNeedsPassword(conn) && +- prompt_password != TRI_NO) ++ cparams->prompt_password != TRI_NO) + { + PQfinish(conn); + simple_prompt("Password: ", password, sizeof(password), false); +@@ -141,10 +154,11 @@ connectDatabase(const char *dbname, const char *pghost, + return NULL; + } + fprintf(stderr, _("%s: could not connect to database %s: %s"), +- progname, dbname, PQerrorMessage(conn)); ++ progname, cparams->dbname, PQerrorMessage(conn)); + exit(1); + } + ++ /* Start strict; callers may override this. */ + PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, + progname, echo)); + +@@ -153,27 +167,30 @@ connectDatabase(const char *dbname, const char *pghost, + + /* + * Try to connect to the appropriate maintenance database. ++ * ++ * This differs from connectDatabase only in that it has a rule for ++ * inserting a default "dbname" if none was given (which is why cparams ++ * is not const). Note that cparams->dbname should typically come from ++ * a --maintenance-db command line parameter. + */ + PGconn * +-connectMaintenanceDatabase(const char *maintenance_db, +- const char *pghost, const char *pgport, +- const char *pguser, enum trivalue prompt_password, ++connectMaintenanceDatabase(ConnParams *cparams, + const char *progname, bool echo) + { + PGconn *conn; + + /* If a maintenance database name was specified, just connect to it. */ +- if (maintenance_db) +- return connectDatabase(maintenance_db, pghost, pgport, pguser, +- prompt_password, progname, echo, false, false); ++ if (cparams->dbname) ++ return connectDatabase(cparams, progname, echo, false, false); + + /* Otherwise, try postgres first and then template1. */ +- conn = connectDatabase("postgres", pghost, pgport, pguser, prompt_password, +- progname, echo, true, false); ++ cparams->dbname = "postgres"; ++ conn = connectDatabase(cparams, progname, echo, true, false); + if (!conn) +- conn = connectDatabase("template1", pghost, pgport, pguser, +- prompt_password, progname, echo, false, false); +- ++ { ++ cparams->dbname = "template1"; ++ conn = connectDatabase(cparams, progname, echo, false, false); ++ } + return conn; + } + +diff --git a/src/bin/scripts/common.h b/src/bin/scripts/common.h +index 085e3b7cf6..b94c3e907f 100644 +--- a/src/bin/scripts/common.h ++++ b/src/bin/scripts/common.h +@@ -23,20 +23,32 @@ enum trivalue + + extern bool CancelRequested; + ++/* Parameters needed by connectDatabase/connectMaintenanceDatabase */ ++typedef struct _connParams ++{ ++ /* These fields record the actual command line parameters */ ++ const char *dbname; /* this may be a connstring! */ ++ const char *pghost; ++ const char *pgport; ++ const char *pguser; ++ enum trivalue prompt_password; ++ /* If not NULL, this overrides the dbname obtained from command line */ ++ /* (but *only* the DB name, not anything else in the connstring) */ ++ const char *override_dbname; ++} ConnParams; ++ + typedef void (*help_handler) (const char *progname); + + extern void handle_help_version_opts(int argc, char *argv[], + const char *fixed_progname, + help_handler hlp); + +-extern PGconn *connectDatabase(const char *dbname, const char *pghost, +- const char *pgport, const char *pguser, +- enum trivalue prompt_password, const char *progname, +- bool echo, bool fail_ok, bool allow_password_reuse); ++extern PGconn *connectDatabase(const ConnParams *cparams, ++ const char *progname, ++ bool echo, bool fail_ok, ++ bool allow_password_reuse); + +-extern PGconn *connectMaintenanceDatabase(const char *maintenance_db, +- const char *pghost, const char *pgport, +- const char *pguser, enum trivalue prompt_password, ++extern PGconn *connectMaintenanceDatabase(ConnParams *cparams, + const char *progname, bool echo); + + extern PGresult *executeQuery(PGconn *conn, const char *query, +diff --git a/src/bin/scripts/createdb.c b/src/bin/scripts/createdb.c +index 45d26ecb8c..f1be5745fb 100644 +--- a/src/bin/scripts/createdb.c ++++ b/src/bin/scripts/createdb.c +@@ -50,6 +50,7 @@ main(int argc, char *argv[]) + char *port = NULL; + char *username = NULL; + enum trivalue prompt_password = TRI_DEFAULT; ++ ConnParams cparams; + bool echo = false; + char *owner = NULL; + char *tablespace = NULL; +@@ -181,8 +182,14 @@ main(int argc, char *argv[]) + if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) + maintenance_db = "template1"; + +- conn = connectMaintenanceDatabase(maintenance_db, host, port, username, +- prompt_password, progname, echo); ++ cparams.dbname = maintenance_db; ++ cparams.pghost = host; ++ cparams.pgport = port; ++ cparams.pguser = username; ++ cparams.prompt_password = prompt_password; ++ cparams.override_dbname = NULL; ++ ++ conn = connectMaintenanceDatabase(&cparams, progname, echo); + + initPQExpBuffer(&sql); + +diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c +index edadd136a6..b1585d7e56 100644 +--- a/src/bin/scripts/createuser.c ++++ b/src/bin/scripts/createuser.c +@@ -60,6 +60,7 @@ main(int argc, char *argv[]) + char *username = NULL; + SimpleStringList roles = {NULL, NULL}; + enum trivalue prompt_password = TRI_DEFAULT; ++ ConnParams cparams; + bool echo = false; + bool interactive = false; + char *conn_limit = NULL; +@@ -251,8 +252,14 @@ main(int argc, char *argv[]) + if (login == 0) + login = TRI_YES; + +- conn = connectDatabase("postgres", host, port, username, prompt_password, +- progname, echo, false, false); ++ cparams.dbname = NULL; /* this program lacks any dbname option... */ ++ cparams.pghost = host; ++ cparams.pgport = port; ++ cparams.pguser = username; ++ cparams.prompt_password = prompt_password; ++ cparams.override_dbname = NULL; ++ ++ conn = connectMaintenanceDatabase(&cparams, progname, echo); + + initPQExpBuffer(&sql); + +diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c +index 614110179e..3f14a365ee 100644 +--- a/src/bin/scripts/dropdb.c ++++ b/src/bin/scripts/dropdb.c +@@ -46,6 +46,7 @@ main(int argc, char *argv[]) + char *port = NULL; + char *username = NULL; + enum trivalue prompt_password = TRI_DEFAULT; ++ ConnParams cparams; + bool echo = false; + bool interactive = false; + +@@ -128,9 +129,14 @@ main(int argc, char *argv[]) + if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) + maintenance_db = "template1"; + +- conn = connectMaintenanceDatabase(maintenance_db, +- host, port, username, prompt_password, +- progname, echo); ++ cparams.dbname = maintenance_db; ++ cparams.pghost = host; ++ cparams.pgport = port; ++ cparams.pguser = username; ++ cparams.prompt_password = prompt_password; ++ cparams.override_dbname = NULL; ++ ++ conn = connectMaintenanceDatabase(&cparams, progname, echo); + + if (echo) + printf("%s\n", sql.data); +diff --git a/src/bin/scripts/dropuser.c b/src/bin/scripts/dropuser.c +index ad400c3518..1dd5ad20fd 100644 +--- a/src/bin/scripts/dropuser.c ++++ b/src/bin/scripts/dropuser.c +@@ -44,6 +44,7 @@ main(int argc, char *argv[]) + char *port = NULL; + char *username = NULL; + enum trivalue prompt_password = TRI_DEFAULT; ++ ConnParams cparams; + bool echo = false; + bool interactive = false; + char dropuser_buf[128]; +@@ -129,13 +130,19 @@ main(int argc, char *argv[]) + exit(0); + } + ++ cparams.dbname = NULL; /* this program lacks any dbname option... */ ++ cparams.pghost = host; ++ cparams.pgport = port; ++ cparams.pguser = username; ++ cparams.prompt_password = prompt_password; ++ cparams.override_dbname = NULL; ++ ++ conn = connectMaintenanceDatabase(&cparams, progname, echo); ++ + initPQExpBuffer(&sql); + appendPQExpBuffer(&sql, "DROP ROLE %s%s;", + (if_exists ? "IF EXISTS " : ""), fmtId(dropuser)); + +- conn = connectDatabase("postgres", host, port, username, prompt_password, +- progname, echo, false, false); +- + if (echo) + printf("%s\n", sql.data); + result = PQexec(conn, sql.data); +diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c +index cdfb115641..48074afe79 100644 +--- a/src/bin/scripts/reindexdb.c ++++ b/src/bin/scripts/reindexdb.c +@@ -15,19 +15,14 @@ + #include "fe_utils/string_utils.h" + + +-static void reindex_one_database(const char *name, const char *dbname, +- const char *type, const char *host, +- const char *port, const char *username, +- enum trivalue prompt_password, const char *progname, ++static void reindex_one_database(const ConnParams *cparams, ++ const char *type, const char *name, ++ const char *progname, + bool echo, bool verbose); +-static void reindex_all_databases(const char *maintenance_db, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, ++static void reindex_all_databases(ConnParams *cparams, + const char *progname, bool echo, + bool quiet, bool verbose); +-static void reindex_system_catalogs(const char *dbname, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, ++static void reindex_system_catalogs(const ConnParams *cparams, + const char *progname, bool echo, bool verbose); + static void help(const char *progname); + +@@ -63,6 +58,7 @@ main(int argc, char *argv[]) + const char *port = NULL; + const char *username = NULL; + enum trivalue prompt_password = TRI_DEFAULT; ++ ConnParams cparams; + bool syscatalog = false; + bool alldb = false; + bool echo = false; +@@ -151,6 +147,13 @@ main(int argc, char *argv[]) + exit(1); + } + ++ /* fill cparams except for dbname, which is set below */ ++ cparams.pghost = host; ++ cparams.pgport = port; ++ cparams.pguser = username; ++ cparams.prompt_password = prompt_password; ++ cparams.override_dbname = NULL; ++ + setup_cancel_handler(); + + if (alldb) +@@ -181,8 +184,10 @@ main(int argc, char *argv[]) + exit(1); + } + +- reindex_all_databases(maintenance_db, host, port, username, +- prompt_password, progname, echo, quiet, verbose); ++ cparams.dbname = maintenance_db; ++ ++ reindex_all_databases(&cparams, ++ progname, echo, quiet, verbose); + } + else if (syscatalog) + { +@@ -212,7 +217,9 @@ main(int argc, char *argv[]) + dbname = get_user_name_or_exit(progname); + } + +- reindex_system_catalogs(dbname, host, port, username, prompt_password, ++ cparams.dbname = dbname; ++ ++ reindex_system_catalogs(&cparams, + progname, echo, verbose); + } + else +@@ -227,14 +234,16 @@ main(int argc, char *argv[]) + dbname = get_user_name_or_exit(progname); + } + ++ cparams.dbname = dbname; ++ + if (schemas.head != NULL) + { + SimpleStringListCell *cell; + + for (cell = schemas.head; cell; cell = cell->next) + { +- reindex_one_database(cell->val, dbname, "SCHEMA", host, port, +- username, prompt_password, progname, echo, verbose); ++ reindex_one_database(&cparams, "SCHEMA", cell->val, ++ progname, echo, verbose); + } + } + +@@ -244,8 +253,8 @@ main(int argc, char *argv[]) + + for (cell = indexes.head; cell; cell = cell->next) + { +- reindex_one_database(cell->val, dbname, "INDEX", host, port, +- username, prompt_password, progname, echo, verbose); ++ reindex_one_database(&cparams, "INDEX", cell->val, ++ progname, echo, verbose); + } + } + if (tables.head != NULL) +@@ -254,8 +263,8 @@ main(int argc, char *argv[]) + + for (cell = tables.head; cell; cell = cell->next) + { +- reindex_one_database(cell->val, dbname, "TABLE", host, port, +- username, prompt_password, progname, echo, verbose); ++ reindex_one_database(&cparams, "TABLE", cell->val, ++ progname, echo, verbose); + } + } + +@@ -264,25 +273,24 @@ main(int argc, char *argv[]) + * specified + */ + if (indexes.head == NULL && tables.head == NULL && schemas.head == NULL) +- reindex_one_database(NULL, dbname, "DATABASE", host, port, +- username, prompt_password, progname, echo, verbose); ++ reindex_one_database(&cparams, "DATABASE", NULL, ++ progname, echo, verbose); + } + + exit(0); + } + + static void +-reindex_one_database(const char *name, const char *dbname, const char *type, +- const char *host, const char *port, const char *username, +- enum trivalue prompt_password, const char *progname, bool echo, +- bool verbose) ++reindex_one_database(const ConnParams *cparams, ++ const char *type, const char *name, ++ const char *progname, ++ bool echo, bool verbose) + { + PQExpBufferData sql; + + PGconn *conn; + +- conn = connectDatabase(dbname, host, port, username, prompt_password, +- progname, echo, false, false); ++ conn = connectDatabase(cparams, progname, echo, false, false); + + initPQExpBuffer(&sql); + +@@ -325,22 +333,17 @@ reindex_one_database(const char *name, const char *dbname, const char *type, + } + + static void +-reindex_all_databases(const char *maintenance_db, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, ++reindex_all_databases(ConnParams *cparams, + const char *progname, bool echo, bool quiet, bool verbose) + { + PGconn *conn; + PGresult *result; +- PQExpBufferData connstr; + int i; + +- conn = connectMaintenanceDatabase(maintenance_db, host, port, username, +- prompt_password, progname, echo); ++ conn = connectMaintenanceDatabase(cparams, progname, echo); + result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo); + PQfinish(conn); + +- initPQExpBuffer(&connstr); + for (i = 0; i < PQntuples(result); i++) + { + char *dbname = PQgetvalue(result, i, 0); +@@ -351,29 +354,23 @@ reindex_all_databases(const char *maintenance_db, + fflush(stdout); + } + +- resetPQExpBuffer(&connstr); +- appendPQExpBuffer(&connstr, "dbname="); +- appendConnStrVal(&connstr, dbname); ++ cparams->override_dbname = dbname; + +- reindex_one_database(NULL, connstr.data, "DATABASE", host, +- port, username, prompt_password, ++ reindex_one_database(cparams, "DATABASE", NULL, + progname, echo, verbose); + } +- termPQExpBuffer(&connstr); + + PQclear(result); + } + + static void +-reindex_system_catalogs(const char *dbname, const char *host, const char *port, +- const char *username, enum trivalue prompt_password, ++reindex_system_catalogs(const ConnParams *cparams, + const char *progname, bool echo, bool verbose) + { + PGconn *conn; + PQExpBufferData sql; + +- conn = connectDatabase(dbname, host, port, username, prompt_password, +- progname, echo, false, false); ++ conn = connectDatabase(cparams, progname, echo, false, false); + + initPQExpBuffer(&sql); + +diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c +index 4ac765244a..40b1fbc5a3 100644 +--- a/src/bin/scripts/vacuumdb.c ++++ b/src/bin/scripts/vacuumdb.c +@@ -43,19 +43,16 @@ typedef struct vacuumingOptions + } vacuumingOptions; + + +-static void vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, ++static void vacuum_one_database(const ConnParams *cparams, ++ vacuumingOptions *vacopts, + int stage, + SimpleStringList *tables, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, + int concurrentCons, + const char *progname, bool echo, bool quiet); + +-static void vacuum_all_databases(vacuumingOptions *vacopts, ++static void vacuum_all_databases(ConnParams *cparams, ++ vacuumingOptions *vacopts, + bool analyze_in_stages, +- const char *maintenance_db, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, + int concurrentCons, + const char *progname, bool echo, bool quiet); + +@@ -122,6 +119,7 @@ main(int argc, char *argv[]) + char *port = NULL; + char *username = NULL; + enum trivalue prompt_password = TRI_DEFAULT; ++ ConnParams cparams; + bool echo = false; + bool quiet = false; + vacuumingOptions vacopts; +@@ -248,6 +246,13 @@ main(int argc, char *argv[]) + /* allow 'and_analyze' with 'analyze_only' */ + } + ++ /* fill cparams except for dbname, which is set below */ ++ cparams.pghost = host; ++ cparams.pgport = port; ++ cparams.pguser = username; ++ cparams.prompt_password = prompt_password; ++ cparams.override_dbname = NULL; ++ + setup_cancel_handler(); + + /* Avoid opening extra connections. */ +@@ -269,10 +274,10 @@ main(int argc, char *argv[]) + exit(1); + } + +- vacuum_all_databases(&vacopts, ++ cparams.dbname = maintenance_db; ++ ++ vacuum_all_databases(&cparams, &vacopts, + analyze_in_stages, +- maintenance_db, +- host, port, username, prompt_password, + concurrentCons, + progname, echo, quiet); + } +@@ -288,25 +293,25 @@ main(int argc, char *argv[]) + dbname = get_user_name_or_exit(progname); + } + ++ cparams.dbname = dbname; ++ + if (analyze_in_stages) + { + int stage; + + for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++) + { +- vacuum_one_database(dbname, &vacopts, ++ vacuum_one_database(&cparams, &vacopts, + stage, + &tables, +- host, port, username, prompt_password, + concurrentCons, + progname, echo, quiet); + } + } + else +- vacuum_one_database(dbname, &vacopts, ++ vacuum_one_database(&cparams, &vacopts, + ANALYZE_NO_STAGE, + &tables, +- host, port, username, prompt_password, + concurrentCons, + progname, echo, quiet); + } +@@ -328,11 +333,10 @@ main(int argc, char *argv[]) + * a list of tables from the database. + */ + static void +-vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, ++vacuum_one_database(const ConnParams *cparams, ++ vacuumingOptions *vacopts, + int stage, + SimpleStringList *tables, +- const char *host, const char *port, +- const char *username, enum trivalue prompt_password, + int concurrentCons, + const char *progname, bool echo, bool quiet) + { +@@ -358,8 +362,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, + Assert(stage == ANALYZE_NO_STAGE || + (stage >= 0 && stage < ANALYZE_NUM_STAGES)); + +- conn = connectDatabase(dbname, host, port, username, prompt_password, +- progname, echo, false, true); ++ conn = connectDatabase(cparams, progname, echo, false, true); + + if (!quiet) + { +@@ -435,8 +438,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, + { + for (i = 1; i < concurrentCons; i++) + { +- conn = connectDatabase(dbname, host, port, username, prompt_password, +- progname, echo, false, true); ++ conn = connectDatabase(cparams, progname, echo, false, true); + + /* + * Fail and exit immediately if trying to use a socket in an +@@ -556,28 +558,23 @@ finish: + * quickly everywhere before generating more detailed ones. + */ + static void +-vacuum_all_databases(vacuumingOptions *vacopts, ++vacuum_all_databases(ConnParams *cparams, ++ vacuumingOptions *vacopts, + bool analyze_in_stages, +- const char *maintenance_db, const char *host, +- const char *port, const char *username, +- enum trivalue prompt_password, + int concurrentCons, + const char *progname, bool echo, bool quiet) + { + PGconn *conn; + PGresult *result; +- PQExpBufferData connstr; + int stage; + int i; + +- conn = connectMaintenanceDatabase(maintenance_db, host, port, username, +- prompt_password, progname, echo); ++ conn = connectMaintenanceDatabase(cparams, progname, echo); + result = executeQuery(conn, + "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", + progname, echo); + PQfinish(conn); + +- initPQExpBuffer(&connstr); + if (analyze_in_stages) + { + /* +@@ -592,14 +589,11 @@ vacuum_all_databases(vacuumingOptions *vacopts, + { + for (i = 0; i < PQntuples(result); i++) + { +- resetPQExpBuffer(&connstr); +- appendPQExpBuffer(&connstr, "dbname="); +- appendConnStrVal(&connstr, PQgetvalue(result, i, 0)); ++ cparams->override_dbname = PQgetvalue(result, i, 0); + +- vacuum_one_database(connstr.data, vacopts, ++ vacuum_one_database(cparams, vacopts, + stage, + NULL, +- host, port, username, prompt_password, + concurrentCons, + progname, echo, quiet); + } +@@ -609,19 +603,15 @@ vacuum_all_databases(vacuumingOptions *vacopts, + { + for (i = 0; i < PQntuples(result); i++) + { +- resetPQExpBuffer(&connstr); +- appendPQExpBuffer(&connstr, "dbname="); +- appendConnStrVal(&connstr, PQgetvalue(result, i, 0)); ++ cparams->override_dbname = PQgetvalue(result, i, 0); + +- vacuum_one_database(connstr.data, vacopts, ++ vacuum_one_database(cparams, vacopts, + ANALYZE_NO_STAGE, + NULL, +- host, port, username, prompt_password, + concurrentCons, + progname, echo, quiet); + } + } +- termPQExpBuffer(&connstr); + + PQclear(result); + } +-- +2.23.0 + diff --git a/CVE-2020-25694-3.patch b/CVE-2020-25694-3.patch new file mode 100644 index 0000000000000000000000000000000000000000..e1687e319fe57e8263dcff3caf70a1509b7ff261 --- /dev/null +++ b/CVE-2020-25694-3.patch @@ -0,0 +1,782 @@ +From bb3ab8bfb690721923e987d2dfab683b462c1e88 Mon Sep 17 00:00:00 2001 +From: Tom Lane +Date: Thu, 24 Sep 2020 18:19:39 -0400 +Subject: [PATCH] Fix handling of -d "connection string" in pg_dump/pg_restore. + +Parallel pg_dump failed if its -d parameter was a connection string +containing any essential information other than host, port, or username. +The same was true for pg_restore with --create. + +The reason is that these scenarios failed to preserve the connection +string from the command line; the code felt free to replace that with +just the database name when reconnecting from a pg_dump parallel worker +or after creating the target database. By chance, parallel pg_restore +did not suffer this defect, as long as you didn't say --create. + +In practice it seems that the error would be obvious only if the +connstring included essential, non-default SSL or GSS parameters. +This may explain why it took us so long to notice. (It also makes +it very difficult to craft a regression test case illustrating the +problem, since the test would fail in builds without those options.) + +Fix by refactoring so that ConnectDatabase always receives all the +relevant options directly from the command line, rather than +reconstructed values. Inject a different database name, when necessary, +by relying on libpq's rules for handling multiple "dbname" parameters. + +While here, let's get rid of the essentially duplicate _connectDB +function, as well as some obsolete nearby cruft. + +Per bug #16604 from Zsolt Ero. Back-patch to all supported branches. + +Discussion: https://postgr.es/m/16604-933f4b8791227b15@postgresql.org +--- + src/bin/pg_dump/pg_backup.h | 36 ++-- + src/bin/pg_dump/pg_backup_archiver.c | 96 +++-------- + src/bin/pg_dump/pg_backup_archiver.h | 3 +- + src/bin/pg_dump/pg_backup_db.c | 249 +++++++-------------------- + src/bin/pg_dump/pg_dump.c | 24 +-- + src/bin/pg_dump/pg_restore.c | 14 +- + 6 files changed, 131 insertions(+), 291 deletions(-) + +diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h +index 560de01884..72c6a8db6c 100644 +--- a/src/bin/pg_dump/pg_backup.h ++++ b/src/bin/pg_dump/pg_backup.h +@@ -58,6 +58,20 @@ typedef enum _teSection + SECTION_POST_DATA /* stuff to be processed after data */ + } teSection; + ++/* Parameters needed by ConnectDatabase; same for dump and restore */ ++typedef struct _connParams ++{ ++ /* These fields record the actual command line parameters */ ++ char *dbname; /* this may be a connstring! */ ++ char *pgport; ++ char *pghost; ++ char *username; ++ trivalue promptPassword; ++ /* If not NULL, this overrides the dbname obtained from command line */ ++ /* (but *only* the DB name, not anything else in the connstring) */ ++ char *override_dbname; ++} ConnParams; ++ + typedef struct _restoreOptions + { + int createDB; /* Issue commands to create the database */ +@@ -106,12 +120,9 @@ typedef struct _restoreOptions + SimpleStringList tableNames; + + int useDB; +- char *dbname; /* subject to expand_dbname */ +- char *pgport; +- char *pghost; +- char *username; ++ ConnParams cparams; /* parameters to use if useDB */ ++ + int noDataForFailedTables; +- trivalue promptPassword; + int exit_on_error; + int compression; + int suppressDumpWarnings; /* Suppress output of WARNING entries +@@ -126,10 +137,8 @@ typedef struct _restoreOptions + + typedef struct _dumpOptions + { +- const char *dbname; /* subject to expand_dbname */ +- const char *pghost; +- const char *pgport; +- const char *username; ++ ConnParams cparams; ++ + bool oids; + + int binary_upgrade; +@@ -239,12 +248,9 @@ typedef void (*SetupWorkerPtrType) (Archive *AH); + * Main archiver interface. + */ + +-extern void ConnectDatabase(Archive *AH, +- const char *dbname, +- const char *pghost, +- const char *pgport, +- const char *username, +- trivalue prompt_password); ++extern void ConnectDatabase(Archive *AHX, ++ const ConnParams *cparams, ++ bool isReconnect); + extern void DisconnectDatabase(Archive *AHX); + extern PGconn *GetConnection(Archive *AHX); + +diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c +index 8892b17790..4003ab3b13 100644 +--- a/src/bin/pg_dump/pg_backup_archiver.c ++++ b/src/bin/pg_dump/pg_backup_archiver.c +@@ -144,6 +144,7 @@ InitDumpOptions(DumpOptions *opts) + memset(opts, 0, sizeof(DumpOptions)); + /* set any fields that shouldn't default to zeroes */ + opts->include_everything = true; ++ opts->cparams.promptPassword = TRI_DEFAULT; + opts->dumpSections = DUMP_UNSECTIONED; + } + +@@ -157,6 +158,11 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) + DumpOptions *dopt = NewDumpOptions(); + + /* this is the inverse of what's at the end of pg_dump.c's main() */ ++ dopt->cparams.dbname = ropt->cparams.dbname ? pg_strdup(ropt->cparams.dbname) : NULL; ++ dopt->cparams.pgport = ropt->cparams.pgport ? pg_strdup(ropt->cparams.pgport) : NULL; ++ dopt->cparams.pghost = ropt->cparams.pghost ? pg_strdup(ropt->cparams.pghost) : NULL; ++ dopt->cparams.username = ropt->cparams.username ? pg_strdup(ropt->cparams.username) : NULL; ++ dopt->cparams.promptPassword = ropt->cparams.promptPassword; + dopt->outputClean = ropt->dropSchema; + dopt->dataOnly = ropt->dataOnly; + dopt->schemaOnly = ropt->schemaOnly; +@@ -401,9 +407,7 @@ RestoreArchive(Archive *AHX) + AHX->minRemoteVersion = 0; + AHX->maxRemoteVersion = 9999999; + +- ConnectDatabase(AHX, ropt->dbname, +- ropt->pghost, ropt->pgport, ropt->username, +- ropt->promptPassword); ++ ConnectDatabase(AHX, &ropt->cparams, false); + + /* + * If we're talking to the DB directly, don't send comments since they +@@ -823,16 +827,8 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel) + /* If we created a DB, connect to it... */ + if (strcmp(te->desc, "DATABASE") == 0) + { +- PQExpBufferData connstr; +- +- initPQExpBuffer(&connstr); +- appendPQExpBufferStr(&connstr, "dbname="); +- appendConnStrVal(&connstr, te->tag); +- /* Abandon struct, but keep its buffer until process exit. */ +- + ahlog(AH, 1, "connecting to new database \"%s\"\n", te->tag); + _reconnectToDB(AH, te->tag); +- ropt->dbname = connstr.data; + } + } + +@@ -966,7 +962,7 @@ NewRestoreOptions(void) + + /* set any fields that shouldn't default to zeroes */ + opts->format = archUnknown; +- opts->promptPassword = TRI_DEFAULT; ++ opts->cparams.promptPassword = TRI_DEFAULT; + opts->dumpSections = DUMP_UNSECTIONED; + + return opts; +@@ -2388,8 +2384,6 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, + else + AH->format = fmt; + +- AH->promptPassword = TRI_DEFAULT; +- + switch (AH->format) + { + case archCustom: +@@ -3157,27 +3151,20 @@ _doSetWithOids(ArchiveHandle *AH, const bool withOids) + * If we're currently restoring right into a database, this will + * actually establish a connection. Otherwise it puts a \connect into + * the script output. +- * +- * NULL dbname implies reconnecting to the current DB (pretty useless). + */ + static void + _reconnectToDB(ArchiveHandle *AH, const char *dbname) + { + if (RestoringToDB(AH)) +- ReconnectToServer(AH, dbname, NULL); ++ ReconnectToServer(AH, dbname); + else + { +- if (dbname) +- { +- PQExpBufferData connectbuf; ++ PQExpBufferData connectbuf; + +- initPQExpBuffer(&connectbuf); +- appendPsqlMetaConnect(&connectbuf, dbname); +- ahprintf(AH, "%s\n", connectbuf.data); +- termPQExpBuffer(&connectbuf); +- } +- else +- ahprintf(AH, "%s\n", "\\connect -\n"); ++ initPQExpBuffer(&connectbuf); ++ appendPsqlMetaConnect(&connectbuf, dbname); ++ ahprintf(AH, "%s\n", connectbuf.data); ++ termPQExpBuffer(&connectbuf); + } + + /* +@@ -4107,9 +4094,7 @@ restore_toc_entries_postfork(ArchiveHandle *AH, TocEntry *pending_list) + /* + * Now reconnect the single parent connection. + */ +- ConnectDatabase((Archive *) AH, ropt->dbname, +- ropt->pghost, ropt->pgport, ropt->username, +- ropt->promptPassword); ++ ConnectDatabase((Archive *) AH, &ropt->cparams, true); + + /* re-establish fixed state */ + _doSetFixedOutputState(AH); +@@ -4690,54 +4675,15 @@ CloneArchive(ArchiveHandle *AH) + clone->public.n_errors = 0; + + /* +- * Connect our new clone object to the database: In parallel restore the +- * parent is already disconnected, because we can connect the worker +- * processes independently to the database (no snapshot sync required). In +- * parallel backup we clone the parent's existing connection. ++ * Connect our new clone object to the database, using the same connection ++ * parameters used for the original connection. + */ +- if (AH->mode == archModeRead) +- { +- RestoreOptions *ropt = AH->public.ropt; +- +- Assert(AH->connection == NULL); +- +- /* this also sets clone->connection */ +- ConnectDatabase((Archive *) clone, ropt->dbname, +- ropt->pghost, ropt->pgport, ropt->username, +- ropt->promptPassword); ++ ConnectDatabase((Archive *) clone, &clone->public.ropt->cparams, true); + +- /* re-establish fixed state */ ++ /* re-establish fixed state */ ++ if (AH->mode == archModeRead) + _doSetFixedOutputState(clone); +- } +- else +- { +- PQExpBufferData connstr; +- char *pghost; +- char *pgport; +- char *username; +- +- Assert(AH->connection != NULL); +- +- /* +- * Even though we are technically accessing the parent's database +- * object here, these functions are fine to be called like that +- * because all just return a pointer and do not actually send/receive +- * any data to/from the database. +- */ +- initPQExpBuffer(&connstr); +- appendPQExpBuffer(&connstr, "dbname="); +- appendConnStrVal(&connstr, PQdb(AH->connection)); +- pghost = PQhost(AH->connection); +- pgport = PQport(AH->connection); +- username = PQuser(AH->connection); +- +- /* this also sets clone->connection */ +- ConnectDatabase((Archive *) clone, connstr.data, +- pghost, pgport, username, TRI_NO); +- +- termPQExpBuffer(&connstr); +- /* setupDumpWorker will fix up connection state */ +- } ++ /* in write case, setupDumpWorker will fix up connection state */ + + /* Let the format-specific code have a chance too */ + (clone->ClonePtr) (clone); +diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h +index 3b69868359..50829c4b2e 100644 +--- a/src/bin/pg_dump/pg_backup_archiver.h ++++ b/src/bin/pg_dump/pg_backup_archiver.h +@@ -304,7 +304,6 @@ struct _archiveHandle + + /* Stuff for direct DB connection */ + char *archdbname; /* DB name *read* from archive */ +- trivalue promptPassword; + char *savedPassword; /* password for ropt->username, if known */ + char *use_role; + PGconn *connection; +@@ -450,7 +449,7 @@ extern void InitArchiveFmt_Tar(ArchiveHandle *AH); + + extern bool isValidTarHeader(char *header); + +-extern int ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *newUser); ++extern void ReconnectToServer(ArchiveHandle *AH, const char *dbname); + extern void DropBlobIfExists(ArchiveHandle *AH, Oid oid); + + void ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH); +diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c +index 43f5941f93..4eaea4af4f 100644 +--- a/src/bin/pg_dump/pg_backup_db.c ++++ b/src/bin/pg_dump/pg_backup_db.c +@@ -30,7 +30,6 @@ + static const char *modulename = gettext_noop("archiver (db)"); + + static void _check_database_version(ArchiveHandle *AH); +-static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser); + static void notice_processor(void *arg, const char *message); + + static void +@@ -76,186 +75,51 @@ _check_database_version(ArchiveHandle *AH) + + /* + * Reconnect to the server. If dbname is not NULL, use that database, +- * else the one associated with the archive handle. If username is +- * not NULL, use that user name, else the one from the handle. If +- * both the database and the user match the existing connection already, +- * nothing will be done. +- * +- * Returns 1 in any case. ++ * else the one associated with the archive handle. + */ +-int +-ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username) +-{ +- PGconn *newConn; +- const char *newdbname; +- const char *newusername; +- +- if (!dbname) +- newdbname = PQdb(AH->connection); +- else +- newdbname = dbname; +- +- if (!username) +- newusername = PQuser(AH->connection); +- else +- newusername = username; +- +- /* Let's see if the request is already satisfied */ +- if (strcmp(newdbname, PQdb(AH->connection)) == 0 && +- strcmp(newusername, PQuser(AH->connection)) == 0) +- return 1; +- +- newConn = _connectDB(AH, newdbname, newusername); +- +- /* Update ArchiveHandle's connCancel before closing old connection */ +- set_archive_cancel_info(AH, newConn); +- +- PQfinish(AH->connection); +- AH->connection = newConn; +- +- /* Start strict; later phases may override this. */ +- PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH, +- ALWAYS_SECURE_SEARCH_PATH_SQL)); +- +- return 1; +-} +- +-/* +- * Connect to the db again. +- * +- * Note: it's not really all that sensible to use a single-entry password +- * cache if the username keeps changing. In current usage, however, the +- * username never does change, so one savedPassword is sufficient. We do +- * update the cache on the off chance that the password has changed since the +- * start of the run. +- */ +-static PGconn * +-_connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) ++void ++ReconnectToServer(ArchiveHandle *AH, const char *dbname) + { +- PQExpBufferData connstr; +- PGconn *newConn; +- const char *newdb; +- const char *newuser; +- char *password; +- char passbuf[100]; +- bool new_pass; +- +- if (!reqdb) +- newdb = PQdb(AH->connection); +- else +- newdb = reqdb; +- +- if (!requser || strlen(requser) == 0) +- newuser = PQuser(AH->connection); +- else +- newuser = requser; +- +- ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n", +- newdb, newuser); +- +- password = AH->savedPassword; +- +- if (AH->promptPassword == TRI_YES && password == NULL) +- { +- simple_prompt("Password: ", passbuf, sizeof(passbuf), false); +- password = passbuf; +- } +- +- initPQExpBuffer(&connstr); +- appendPQExpBuffer(&connstr, "dbname="); +- appendConnStrVal(&connstr, newdb); +- +- do +- { +- const char *keywords[7]; +- const char *values[7]; +- +- keywords[0] = "host"; +- values[0] = PQhost(AH->connection); +- keywords[1] = "port"; +- values[1] = PQport(AH->connection); +- keywords[2] = "user"; +- values[2] = newuser; +- keywords[3] = "password"; +- values[3] = password; +- keywords[4] = "dbname"; +- values[4] = connstr.data; +- keywords[5] = "fallback_application_name"; +- values[5] = progname; +- keywords[6] = NULL; +- values[6] = NULL; +- +- new_pass = false; +- newConn = PQconnectdbParams(keywords, values, true); +- +- if (!newConn) +- exit_horribly(modulename, "failed to reconnect to database\n"); +- +- if (PQstatus(newConn) == CONNECTION_BAD) +- { +- if (!PQconnectionNeedsPassword(newConn)) +- exit_horribly(modulename, "could not reconnect to database: %s", +- PQerrorMessage(newConn)); +- PQfinish(newConn); +- +- if (password) +- fprintf(stderr, "Password incorrect\n"); +- +- fprintf(stderr, "Connecting to %s as %s\n", +- newdb, newuser); +- +- if (AH->promptPassword != TRI_NO) +- { +- simple_prompt("Password: ", passbuf, sizeof(passbuf), false); +- password = passbuf; +- } +- else +- exit_horribly(modulename, "connection needs password\n"); +- +- new_pass = true; +- } +- } while (new_pass); ++ PGconn *oldConn = AH->connection; ++ RestoreOptions *ropt = AH->public.ropt; + + /* +- * We want to remember connection's actual password, whether or not we got +- * it by prompting. So we don't just store the password variable. ++ * Save the dbname, if given, in override_dbname so that it will also ++ * affect any later reconnection attempt. + */ +- if (PQconnectionUsedPassword(newConn)) +- { +- if (AH->savedPassword) +- free(AH->savedPassword); +- AH->savedPassword = pg_strdup(PQpass(newConn)); +- } ++ if (dbname) ++ ropt->cparams.override_dbname = pg_strdup(dbname); + +- termPQExpBuffer(&connstr); +- +- /* check for version mismatch */ +- _check_database_version(AH); ++ /* ++ * Note: we want to establish the new connection, and in particular update ++ * ArchiveHandle's connCancel, before closing old connection. Otherwise ++ * an ill-timed SIGINT could try to access a dead connection. ++ */ ++ AH->connection = NULL; /* dodge error check in ConnectDatabase */ + +- PQsetNoticeProcessor(newConn, notice_processor, NULL); ++ ConnectDatabase((Archive *) AH, &ropt->cparams, true); + +- return newConn; ++ PQfinish(oldConn); + } + +- + /* +- * Make a database connection with the given parameters. The +- * connection handle is returned, the parameters are stored in AHX. +- * An interactive password prompt is automatically issued if required. ++ * Make, or remake, a database connection with the given parameters. ++ * ++ * The resulting connection handle is stored in AHX->connection. + * ++ * An interactive password prompt is automatically issued if required. ++ * We store the results of that in AHX->savedPassword. + * Note: it's not really all that sensible to use a single-entry password + * cache if the username keeps changing. In current usage, however, the + * username never does change, so one savedPassword is sufficient. + */ + void + ConnectDatabase(Archive *AHX, +- const char *dbname, +- const char *pghost, +- const char *pgport, +- const char *username, +- trivalue prompt_password) ++ const ConnParams *cparams, ++ bool isReconnect) + { + ArchiveHandle *AH = (ArchiveHandle *) AHX; ++ trivalue prompt_password; + char *password; + char passbuf[100]; + bool new_pass; +@@ -263,6 +127,9 @@ ConnectDatabase(Archive *AHX, + if (AH->connection) + exit_horribly(modulename, "already connected to a database\n"); + ++ /* Never prompt for a password during a reconnection */ ++ prompt_password = isReconnect ? TRI_NO : cparams->promptPassword; ++ + password = AH->savedPassword; + + if (prompt_password == TRI_YES && password == NULL) +@@ -270,7 +137,6 @@ ConnectDatabase(Archive *AHX, + simple_prompt("Password: ", passbuf, sizeof(passbuf), false); + password = passbuf; + } +- AH->promptPassword = prompt_password; + + /* + * Start the connection. Loop until we have a password if requested by +@@ -278,23 +144,35 @@ ConnectDatabase(Archive *AHX, + */ + do + { +- const char *keywords[7]; +- const char *values[7]; +- +- keywords[0] = "host"; +- values[0] = pghost; +- keywords[1] = "port"; +- values[1] = pgport; +- keywords[2] = "user"; +- values[2] = username; +- keywords[3] = "password"; +- values[3] = password; +- keywords[4] = "dbname"; +- values[4] = dbname; +- keywords[5] = "fallback_application_name"; +- values[5] = progname; +- keywords[6] = NULL; +- values[6] = NULL; ++ const char *keywords[8]; ++ const char *values[8]; ++ int i = 0; ++ ++ /* ++ * If dbname is a connstring, its entries can override the other ++ * values obtained from cparams; but in turn, override_dbname can ++ * override the dbname component of it. ++ */ ++ keywords[i] = "host"; ++ values[i++] = cparams->pghost; ++ keywords[i] = "port"; ++ values[i++] = cparams->pgport; ++ keywords[i] = "user"; ++ values[i++] = cparams->username; ++ keywords[i] = "password"; ++ values[i++] = password; ++ keywords[i] = "dbname"; ++ values[i++] = cparams->dbname; ++ if (cparams->override_dbname) ++ { ++ keywords[i] = "dbname"; ++ values[i++] = cparams->override_dbname; ++ } ++ keywords[i] = "fallback_application_name"; ++ values[i++] = progname; ++ keywords[i] = NULL; ++ values[i++] = NULL; ++ Assert(i <= lengthof(keywords)); + + new_pass = false; + AH->connection = PQconnectdbParams(keywords, values, true); +@@ -316,9 +194,16 @@ ConnectDatabase(Archive *AHX, + + /* check to see that the backend connection was successfully made */ + if (PQstatus(AH->connection) == CONNECTION_BAD) +- exit_horribly(modulename, "connection to database \"%s\" failed: %s", +- PQdb(AH->connection) ? PQdb(AH->connection) : "", +- PQerrorMessage(AH->connection)); ++ { ++ if (isReconnect) ++ exit_horribly(modulename, "reconnection to database \"%s\" failed: %s", ++ PQdb(AH->connection) ? PQdb(AH->connection) : "", ++ PQerrorMessage(AH->connection)); ++ else ++ exit_horribly(modulename, "connection to database \"%s\" failed: %s", ++ PQdb(AH->connection) ? PQdb(AH->connection) : "", ++ PQerrorMessage(AH->connection)); ++ } + + /* Start strict; later phases may override this. */ + PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH, +diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c +index 8080155a95..abcee89d10 100644 +--- a/src/bin/pg_dump/pg_dump.c ++++ b/src/bin/pg_dump/pg_dump.c +@@ -302,7 +302,6 @@ main(int argc, char **argv) + const char *dumpsnapshot = NULL; + char *use_role = NULL; + int numWorkers = 1; +- trivalue prompt_password = TRI_DEFAULT; + int compressLevel = -1; + int plainText = 0; + ArchiveFormat archiveFormat = archUnknown; +@@ -431,7 +430,7 @@ main(int argc, char **argv) + break; + + case 'd': /* database name */ +- dopt.dbname = pg_strdup(optarg); ++ dopt.cparams.dbname = pg_strdup(optarg); + break; + + case 'E': /* Dump encoding */ +@@ -447,7 +446,7 @@ main(int argc, char **argv) + break; + + case 'h': /* server host */ +- dopt.pghost = pg_strdup(optarg); ++ dopt.cparams.pghost = pg_strdup(optarg); + break; + + case 'j': /* number of dump jobs */ +@@ -472,7 +471,7 @@ main(int argc, char **argv) + break; + + case 'p': /* server port */ +- dopt.pgport = pg_strdup(optarg); ++ dopt.cparams.pgport = pg_strdup(optarg); + break; + + case 'R': +@@ -497,7 +496,7 @@ main(int argc, char **argv) + break; + + case 'U': +- dopt.username = pg_strdup(optarg); ++ dopt.cparams.username = pg_strdup(optarg); + break; + + case 'v': /* verbose */ +@@ -505,11 +504,11 @@ main(int argc, char **argv) + break; + + case 'w': +- prompt_password = TRI_NO; ++ dopt.cparams.promptPassword = TRI_NO; + break; + + case 'W': +- prompt_password = TRI_YES; ++ dopt.cparams.promptPassword = TRI_YES; + break; + + case 'x': /* skip ACL dump */ +@@ -563,8 +562,8 @@ main(int argc, char **argv) + * Non-option argument specifies database name as long as it wasn't + * already specified with -d / --dbname + */ +- if (optind < argc && dopt.dbname == NULL) +- dopt.dbname = argv[optind++]; ++ if (optind < argc && dopt.cparams.dbname == NULL) ++ dopt.cparams.dbname = argv[optind++]; + + /* Complain if any arguments remain */ + if (optind < argc) +@@ -677,7 +676,7 @@ main(int argc, char **argv) + * Open the database using the Archiver, so it knows about it. Errors mean + * death. + */ +- ConnectDatabase(fout, dopt.dbname, dopt.pghost, dopt.pgport, dopt.username, prompt_password); ++ ConnectDatabase(fout, &dopt.cparams, false); + setup_connection(fout, dumpencoding, dumpsnapshot, use_role); + + /* +@@ -860,6 +859,11 @@ main(int argc, char **argv) + ropt->filename = filename; + + /* if you change this list, see dumpOptionsFromRestoreOptions */ ++ ropt->cparams.dbname = dopt.cparams.dbname ? pg_strdup(dopt.cparams.dbname) : NULL; ++ ropt->cparams.pgport = dopt.cparams.pgport ? pg_strdup(dopt.cparams.pgport) : NULL; ++ ropt->cparams.pghost = dopt.cparams.pghost ? pg_strdup(dopt.cparams.pghost) : NULL; ++ ropt->cparams.username = dopt.cparams.username ? pg_strdup(dopt.cparams.username) : NULL; ++ ropt->cparams.promptPassword = dopt.cparams.promptPassword; + ropt->dropSchema = dopt.outputClean; + ropt->dataOnly = dopt.dataOnly; + ropt->schemaOnly = dopt.schemaOnly; +diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c +index 860a211a3c..ce03ded8ec 100644 +--- a/src/bin/pg_dump/pg_restore.c ++++ b/src/bin/pg_dump/pg_restore.c +@@ -163,7 +163,7 @@ main(int argc, char **argv) + opts->createDB = 1; + break; + case 'd': +- opts->dbname = pg_strdup(optarg); ++ opts->cparams.dbname = pg_strdup(optarg); + break; + case 'e': + opts->exit_on_error = true; +@@ -177,7 +177,7 @@ main(int argc, char **argv) + break; + case 'h': + if (strlen(optarg) != 0) +- opts->pghost = pg_strdup(optarg); ++ opts->cparams.pghost = pg_strdup(optarg); + break; + + case 'j': /* number of restore jobs */ +@@ -206,7 +206,7 @@ main(int argc, char **argv) + + case 'p': + if (strlen(optarg) != 0) +- opts->pgport = pg_strdup(optarg); ++ opts->cparams.pgport = pg_strdup(optarg); + break; + case 'R': + /* no-op, still accepted for backwards compatibility */ +@@ -240,7 +240,7 @@ main(int argc, char **argv) + break; + + case 'U': +- opts->username = pg_strdup(optarg); ++ opts->cparams.username = pg_strdup(optarg); + break; + + case 'v': /* verbose */ +@@ -248,11 +248,11 @@ main(int argc, char **argv) + break; + + case 'w': +- opts->promptPassword = TRI_NO; ++ opts->cparams.promptPassword = TRI_NO; + break; + + case 'W': +- opts->promptPassword = TRI_YES; ++ opts->cparams.promptPassword = TRI_YES; + break; + + case 'x': /* skip ACL dump */ +@@ -302,7 +302,7 @@ main(int argc, char **argv) + } + + /* Should get at most one of -d and -f, else user is confused */ +- if (opts->dbname) ++ if (opts->cparams.dbname) + { + if (opts->filename) + { +-- +2.23.0 + diff --git a/CVE-2020-25695.patch b/CVE-2020-25695.patch new file mode 100644 index 0000000000000000000000000000000000000000..59e6196b5616f139e16fc7f0006eb20c9ce03d3b --- /dev/null +++ b/CVE-2020-25695.patch @@ -0,0 +1,244 @@ +From 0c3185e963d9f9dd0608214f7d732b84aa0888fe Mon Sep 17 00:00:00 2001 +From: Noah Misch +Date: Mon, 9 Nov 2020 07:32:09 -0800 +Subject: [PATCH] In security-restricted operations, block enqueue of at-commit + user code. + +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). + +Reviewed by Robert Haas. Reported by Etienne Stalmans. + +Security: CVE-2020-25695 +--- + contrib/postgres_fdw/connection.c | 4 +++ + src/backend/access/transam/xact.c | 13 ++++---- + src/backend/commands/portalcmds.c | 5 +++ + src/backend/commands/trigger.c | 12 +++++++ + src/test/regress/expected/privileges.out | 42 ++++++++++++++++++++++++ + src/test/regress/sql/privileges.sql | 34 +++++++++++++++++++ + 6 files changed, 104 insertions(+), 6 deletions(-) + +diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c +index be4ec07cf9..09ffb21d48 100644 +--- a/contrib/postgres_fdw/connection.c ++++ b/contrib/postgres_fdw/connection.c +@@ -645,6 +645,10 @@ pgfdw_report_error(int elevel, PGresult *res, PGconn *conn, + + /* + * pgfdw_xact_callback --- cleanup at main-transaction end. ++ * ++ * This runs just late enough that it must not enter user-defined code ++ * locally. (Entering such code on the remote side is fine. Its remote ++ * COMMIT TRANSACTION may run deferred triggers.) + */ + static void + pgfdw_xact_callback(XactEvent event, void *arg) +diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c +index 9004e38e6d..e2ca8a5d2e 100644 +--- a/src/backend/access/transam/xact.c ++++ b/src/backend/access/transam/xact.c +@@ -1961,9 +1961,10 @@ CommitTransaction(void) + + /* + * Do pre-commit processing that involves calling user-defined code, such +- * as triggers. Since closing cursors could queue trigger actions, +- * triggers could open cursors, etc, we have to keep looping until there's +- * nothing left to do. ++ * as triggers. SECURITY_RESTRICTED_OPERATION contexts must not queue an ++ * action that would run here, because that would bypass the sandbox. ++ * Since closing cursors could queue trigger actions, triggers could open ++ * cursors, etc, we have to keep looping until there's nothing left to do. + */ + for (;;) + { +@@ -1981,9 +1982,6 @@ CommitTransaction(void) + break; + } + +- CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT +- : XACT_EVENT_PRE_COMMIT); +- + /* + * The remaining actions cannot call any user-defined code, so it's safe + * to start shutting down within-transaction services. But note that most +@@ -1991,6 +1989,9 @@ CommitTransaction(void) + * the transaction-abort path. + */ + ++ CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT ++ : XACT_EVENT_PRE_COMMIT); ++ + /* If we might have parallel workers, clean them up now. */ + if (IsInParallelMode()) + AtEOXact_Parallel(true); +diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c +index 46369cf3db..3d01a782da 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 "rewrite/rewriteHandler.h" + #include "tcop/pquery.h" + #include "tcop/tcopprot.h" +@@ -64,6 +65,10 @@ PerformCursorOpen(DeclareCursorStmt *cstmt, 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"))); + + /* + * Parse analysis was done already, but we still have to run the rule +diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c +index 2886aebef4..896cb20051 100644 +--- a/src/backend/commands/trigger.c ++++ b/src/backend/commands/trigger.c +@@ -4142,6 +4142,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events, + bool immediate_only) + { + bool found = false; ++ bool deferred_found = false; + AfterTriggerEvent event; + AfterTriggerEventChunk *chunk; + +@@ -4177,6 +4178,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 */ +@@ -4184,6 +4186,16 @@ 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; + } + +diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out +index 65d950f15b..f7f9252a05 100644 +--- a/src/test/regress/expected/privileges.out ++++ b/src/test/regress/expected/privileges.out +@@ -1138,6 +1138,48 @@ SELECT has_table_privilege('regress_user1', 'atest4', 'SELECT WITH GRANT OPTION' + t + (1 row) + ++-- security-restricted operations ++\c - ++CREATE ROLE regress_sro_user; ++SET SESSION AUTHORIZATION regress_sro_user; ++CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS ++ 'GRANT regress_priv_group2 TO regress_sro_user'; ++CREATE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS ++ 'DECLARE c CURSOR WITH HOLD FOR SELECT unwanted_grant(); SELECT true'; ++-- REFRESH of this MV will queue a GRANT at end of transaction ++CREATE MATERIALIZED VIEW sro_mv AS SELECT mv_action() WITH NO DATA; ++REFRESH MATERIALIZED VIEW sro_mv; ++ERROR: cannot create a cursor WITH HOLD within security-restricted operation ++CONTEXT: SQL function "mv_action" statement 1 ++\c - ++REFRESH MATERIALIZED VIEW sro_mv; ++ERROR: cannot create a cursor WITH HOLD within security-restricted operation ++CONTEXT: SQL function "mv_action" statement 1 ++SET SESSION AUTHORIZATION regress_sro_user; ++-- INSERT to this table will queue a GRANT at end of transaction ++CREATE TABLE sro_trojan_table (); ++CREATE FUNCTION sro_trojan() RETURNS trigger LANGUAGE plpgsql AS ++ 'BEGIN PERFORM unwanted_grant(); RETURN NULL; END'; ++CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table ++ INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE sro_trojan(); ++-- Now, REFRESH will issue such an INSERT, queueing the GRANT ++CREATE OR REPLACE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS ++ 'INSERT INTO sro_trojan_table DEFAULT VALUES; SELECT true'; ++REFRESH MATERIALIZED VIEW sro_mv; ++ERROR: cannot fire deferred trigger within security-restricted operation ++CONTEXT: SQL function "mv_action" statement 1 ++\c - ++REFRESH MATERIALIZED VIEW sro_mv; ++ERROR: cannot fire deferred trigger within security-restricted operation ++CONTEXT: SQL function "mv_action" statement 1 ++BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT; ++ERROR: must have admin option on role "regress_priv_group2" ++CONTEXT: SQL function "unwanted_grant" statement 1 ++SQL statement "SELECT unwanted_grant()" ++PL/pgSQL function sro_trojan() line 1 at PERFORM ++SQL function "mv_action" statement 1 ++DROP OWNED BY regress_sro_user; ++DROP ROLE regress_sro_user; + -- Admin options + SET SESSION AUTHORIZATION regress_user4; + CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS +diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql +index 902f64c747..baa521bcaf 100644 +--- a/src/test/regress/sql/privileges.sql ++++ b/src/test/regress/sql/privileges.sql +@@ -726,6 +726,40 @@ SELECT has_table_privilege('regress_user3', 'atest4', 'SELECT'); -- false + SELECT has_table_privilege('regress_user1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true + + ++-- security-restricted operations ++\c - ++CREATE ROLE regress_sro_user; ++ ++SET SESSION AUTHORIZATION regress_sro_user; ++CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS ++ 'GRANT regress_priv_group2 TO regress_sro_user'; ++CREATE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS ++ 'DECLARE c CURSOR WITH HOLD FOR SELECT unwanted_grant(); SELECT true'; ++-- REFRESH of this MV will queue a GRANT at end of transaction ++CREATE MATERIALIZED VIEW sro_mv AS SELECT mv_action() WITH NO DATA; ++REFRESH MATERIALIZED VIEW sro_mv; ++\c - ++REFRESH MATERIALIZED VIEW sro_mv; ++ ++SET SESSION AUTHORIZATION regress_sro_user; ++-- INSERT to this table will queue a GRANT at end of transaction ++CREATE TABLE sro_trojan_table (); ++CREATE FUNCTION sro_trojan() RETURNS trigger LANGUAGE plpgsql AS ++ 'BEGIN PERFORM unwanted_grant(); RETURN NULL; END'; ++CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table ++ INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE sro_trojan(); ++-- Now, REFRESH will issue such an INSERT, queueing the GRANT ++CREATE OR REPLACE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS ++ 'INSERT INTO sro_trojan_table DEFAULT VALUES; SELECT true'; ++REFRESH MATERIALIZED VIEW sro_mv; ++\c - ++REFRESH MATERIALIZED VIEW sro_mv; ++BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT; ++ ++DROP OWNED BY regress_sro_user; ++DROP ROLE regress_sro_user; ++ ++ + -- Admin options + + SET SESSION AUTHORIZATION regress_user4; +-- +2.23.0 + diff --git a/CVE-2020-25696.patch b/CVE-2020-25696.patch new file mode 100644 index 0000000000000000000000000000000000000000..1b56845f01992e693e8d1e69363a87ad1b96b923 --- /dev/null +++ b/CVE-2020-25696.patch @@ -0,0 +1,124 @@ +From 098fb00799ffb026ff12c64bd21635f963cfc609 Mon Sep 17 00:00:00 2001 +From: Noah Misch +Date: Mon, 9 Nov 2020 07:32:09 -0800 +Subject: [PATCH] Ignore attempts to \gset into specially treated variables. + +If an interactive psql session used \gset when querying a compromised +server, the attacker could execute arbitrary code as the operating +system account running psql. Using a prefix not found among specially +treated variables, e.g. every lowercase string, precluded the attack. +Fix by issuing a warning and setting no variable for the column in +question. Users wanting the old behavior can use a prefix and then a +meta-command like "\set HISTSIZE :prefix_HISTSIZE". Back-patch to 9.5 +(all supported versions). + +Reviewed by Robert Haas. Reported by Nick Cleaton. + +Security: CVE-2020-25696 +--- + src/bin/psql/common.c | 7 +++++++ + src/bin/psql/variables.c | 26 ++++++++++++++++++++++++++ + src/bin/psql/variables.h | 1 + + src/test/regress/expected/psql.out | 4 ++++ + src/test/regress/sql/psql.sql | 3 +++ + 5 files changed, 41 insertions(+) + +diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c +index a41932ff27..f3d966d7cf 100644 +--- a/src/bin/psql/common.c ++++ b/src/bin/psql/common.c +@@ -878,6 +878,13 @@ StoreQueryTuple(const PGresult *result) + /* concatenate prefix and column name */ + varname = psprintf("%s%s", pset.gset_prefix, colname); + ++ if (VariableHasHook(pset.vars, varname)) ++ { ++ pg_log_warning("attempt to \\gset into specially treated variable \"%s\" ignored", ++ varname); ++ continue; ++ } ++ + if (!PQgetisnull(result, 0, i)) + value = PQgetvalue(result, 0, i); + else +diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c +index 120b25c696..0d28ba9c92 100644 +--- a/src/bin/psql/variables.c ++++ b/src/bin/psql/variables.c +@@ -360,6 +360,32 @@ SetVariableHooks(VariableSpace space, const char *name, + (void) (*ahook) (current->value); + } + ++/* ++ * Return true iff the named variable has substitute and/or assign hook ++ * functions. ++ */ ++bool ++VariableHasHook(VariableSpace space, const char *name) ++{ ++ struct _variable *current; ++ ++ Assert(space); ++ Assert(name); ++ ++ for (current = space->next; current; current = current->next) ++ { ++ int cmp = strcmp(current->name, name); ++ ++ if (cmp == 0) ++ return (current->substitute_hook != NULL || ++ current->assign_hook != NULL); ++ if (cmp > 0) ++ break; /* it's not there */ ++ } ++ ++ return false; ++} ++ + /* + * Convenience function to set a variable's value to "on". + */ +diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h +index 02d85b1bc2..8dc5c20ee8 100644 +--- a/src/bin/psql/variables.h ++++ b/src/bin/psql/variables.h +@@ -90,6 +90,7 @@ bool DeleteVariable(VariableSpace space, const char *name); + void SetVariableHooks(VariableSpace space, const char *name, + VariableSubstituteHook shook, + VariableAssignHook ahook); ++bool VariableHasHook(VariableSpace space, const char *name); + + void PsqlVarEnumError(const char *name, const char *value, const char *suggestions); + +diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out +index 0c94144575..1ae81912c7 100644 +--- a/src/test/regress/expected/psql.out ++++ b/src/test/regress/expected/psql.out +@@ -84,6 +84,10 @@ select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_ + select 10 as "bad name" + \gset + invalid variable name: "bad name" ++select 97 as "EOF", 'ok' as _foo \gset IGNORE ++attempt to \gset into specially treated variable "IGNOREEOF" ignored ++\echo :IGNORE_foo :IGNOREEOF ++ok 0 + -- multiple backslash commands in one line + select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x + 1 +diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql +index 4a676c3119..7f8ab2e5c2 100644 +--- a/src/test/regress/sql/psql.sql ++++ b/src/test/regress/sql/psql.sql +@@ -48,6 +48,9 @@ select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_ + select 10 as "bad name" + \gset + ++select 97 as "EOF", 'ok' as _foo \gset IGNORE ++\echo :IGNORE_foo :IGNOREEOF ++ + -- multiple backslash commands in one line + select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x + select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y +-- +2.23.0 + diff --git a/Fix-error-handling-of-vacuumdb-when-running-out-of-f.patch b/Fix-error-handling-of-vacuumdb-when-running-out-of-f.patch new file mode 100644 index 0000000000000000000000000000000000000000..23dda246d01833ddf527fb91a7af9319ad81a16c --- /dev/null +++ b/Fix-error-handling-of-vacuumdb-when-running-out-of-f.patch @@ -0,0 +1,70 @@ +From eec462367ee2b41e02c6e29135c857ad6f2da66a Mon Sep 17 00:00:00 2001 +From: Michael Paquier +Date: Mon, 26 Aug 2019 11:14:33 +0900 +Subject: [PATCH] Fix error handling of vacuumdb when running out of fds +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When trying to use a high number of jobs, vacuumdb has only checked for +a maximum number of jobs used, causing confusing failures when running +out of file descriptors when the jobs open connections to Postgres. +This commit changes the error handling so as we do not check anymore for +a maximum number of allowed jobs when parsing the option value with +FD_SETSIZE, but check instead if a file descriptor is within the +supported range when opening the connections for the jobs so as this is +detected at the earliest time possible. + +Also, improve the error message to give a hint about the number of jobs +recommended, using a wording given by the reviewers of the patch. + +Reported-by: Andres Freund +Author: Michael Paquier +Reviewed-by: Andres Freund, Álvaro Herrera, Tom Lane +Discussion: https://postgr.es/m/20190818001858.ho3ev4z57fqhs7a5@alap3.anarazel.de +Backpatch-through: 9.5 +--- + src/bin/scripts/vacuumdb.c | 20 ++++++++++++++------ + 1 file changed, 14 insertions(+), 6 deletions(-) + +diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c +index f888bf73bc..4ac765244a 100644 +--- a/src/bin/scripts/vacuumdb.c ++++ b/src/bin/scripts/vacuumdb.c +@@ -200,12 +200,6 @@ main(int argc, char *argv[]) + progname); + exit(1); + } +- if (concurrentCons > FD_SETSIZE - 1) +- { +- fprintf(stderr, _("%s: too many parallel jobs requested (maximum: %d)\n"), +- progname, FD_SETSIZE - 1); +- exit(1); +- } + break; + case 2: + maintenance_db = pg_strdup(optarg); +@@ -443,6 +437,20 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, + { + conn = connectDatabase(dbname, host, port, username, prompt_password, + progname, echo, false, true); ++ ++ /* ++ * Fail and exit immediately if trying to use a socket in an ++ * unsupported range. POSIX requires open(2) to use the lowest ++ * unused file descriptor and the hint given relies on that. ++ */ ++ if (PQsocket(conn) >= FD_SETSIZE) ++ { ++ fprintf(stderr, ++ _("%s: too many jobs for this platform -- try %d"), ++ progname, i); ++ exit(1); ++ } ++ + init_slot(slots + i, conn); + } + } +-- +2.23.0 + diff --git a/Remove-some-code-related-to-7.3-and-older-servers-fr.patch b/Remove-some-code-related-to-7.3-and-older-servers-fr.patch new file mode 100644 index 0000000000000000000000000000000000000000..6f7cdd562fa2bc931724ae40454592013a6f187f --- /dev/null +++ b/Remove-some-code-related-to-7.3-and-older-servers-fr.patch @@ -0,0 +1,50 @@ +From 57ba539775f1dd0b0460f1dfe673da00eeef3a2f Mon Sep 17 00:00:00 2001 +From: Michael Paquier +Date: Tue, 7 May 2019 14:20:01 +0900 +Subject: [PATCH] Remove some code related to 7.3 and older servers from tools + of src/bin/ + +This code was broken as of 582edc3, and is most likely not used anymore. +Note that pg_dump supports servers down to 8.0, and psql has code to +support servers down to 7.4. + +Author: Julien Rouhaud +Reviewed-by: Tom Lane +Discussion: https://postgr.es/m/CAOBaU_Y5y=zo3+2gf+2NJC1pvMYPcbRXoQaPXx=U7+C8Qh4CzQ@mail.gmail.com +--- + src/bin/scripts/common.c | 12 ++---------- + 1 file changed, 2 insertions(+), 10 deletions(-) + +diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c +index 8073ee0d0e..5088c4c48e 100644 +--- a/src/bin/scripts/common.c ++++ b/src/bin/scripts/common.c +@@ -145,9 +145,8 @@ connectDatabase(const char *dbname, const char *pghost, + exit(1); + } + +- if (PQserverVersion(conn) >= 70300) +- PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, +- progname, echo)); ++ PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, ++ progname, echo)); + + return conn; + } +@@ -311,13 +310,6 @@ appendQualifiedRelation(PQExpBuffer buf, const char *spec, + PGresult *res; + int ntups; + +- /* Before 7.3, the concept of qualifying a name did not exist. */ +- if (PQserverVersion(conn) < 70300) +- { +- appendPQExpBufferStr(&sql, spec); +- return; +- } +- + split_table_columns_spec(spec, PQclientEncoding(conn), &table, &columns); + + /* +-- +2.23.0 + diff --git a/createdb-Fix-quoting-of-encoding-lc-ctype-and-lc-col.patch b/createdb-Fix-quoting-of-encoding-lc-ctype-and-lc-col.patch new file mode 100644 index 0000000000000000000000000000000000000000..cd03be23f8ea5993c10e3376e380ec5acc234e30 --- /dev/null +++ b/createdb-Fix-quoting-of-encoding-lc-ctype-and-lc-col.patch @@ -0,0 +1,76 @@ +From a1413123f80f470da1ec422592f228aebe4a8866 Mon Sep 17 00:00:00 2001 +From: Michael Paquier +Date: Thu, 27 Feb 2020 11:21:14 +0900 +Subject: [PATCH 1/2] createdb: Fix quoting of --encoding, --lc-ctype and + --lc-collate + +The original coding failed to properly quote those arguments, leading to +failures when using quotes in the values used. As the quoting can be +encoding-sensitive, the connection to the backend needs to be taken +before applying the correct quoting. + +Author: Michael Paquier +Reviewed-by: Daniel Gustafsson +Discussion: https://postgr.es/m/20200214041004.GB1998@paquier.xyz +Backpatch-through: 9.5 +--- + src/bin/scripts/createdb.c | 29 +++++++++++++++++++---------- + 1 file changed, 19 insertions(+), 10 deletions(-) + +diff --git a/src/bin/scripts/createdb.c b/src/bin/scripts/createdb.c +index 8116b084ff..45d26ecb8c 100644 +--- a/src/bin/scripts/createdb.c ++++ b/src/bin/scripts/createdb.c +@@ -177,6 +177,13 @@ main(int argc, char *argv[]) + dbname = get_user_name_or_exit(progname); + } + ++ /* No point in trying to use postgres db when creating postgres db. */ ++ if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) ++ maintenance_db = "template1"; ++ ++ conn = connectMaintenanceDatabase(maintenance_db, host, port, username, ++ prompt_password, progname, echo); ++ + initPQExpBuffer(&sql); + + appendPQExpBuffer(&sql, "CREATE DATABASE %s", +@@ -187,23 +194,25 @@ main(int argc, char *argv[]) + if (tablespace) + appendPQExpBuffer(&sql, " TABLESPACE %s", fmtId(tablespace)); + if (encoding) +- appendPQExpBuffer(&sql, " ENCODING '%s'", encoding); ++ { ++ appendPQExpBufferStr(&sql, " ENCODING "); ++ appendStringLiteralConn(&sql, encoding, conn); ++ } + if (template) + appendPQExpBuffer(&sql, " TEMPLATE %s", fmtId(template)); + if (lc_collate) +- appendPQExpBuffer(&sql, " LC_COLLATE '%s'", lc_collate); ++ { ++ appendPQExpBufferStr(&sql, " LC_COLLATE "); ++ appendStringLiteralConn(&sql, lc_collate, conn); ++ } + if (lc_ctype) +- appendPQExpBuffer(&sql, " LC_CTYPE '%s'", lc_ctype); ++ { ++ appendPQExpBufferStr(&sql, " LC_CTYPE "); ++ appendStringLiteralConn(&sql, lc_ctype, conn); ++ } + + appendPQExpBufferChar(&sql, ';'); + +- /* No point in trying to use postgres db when creating postgres db. */ +- if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) +- maintenance_db = "template1"; +- +- conn = connectMaintenanceDatabase(maintenance_db, host, port, username, +- prompt_password, progname, echo); +- + if (echo) + printf("%s\n", sql.data); + result = PQexec(conn, sql.data); +-- +2.23.0 + diff --git a/postgresql.spec b/postgresql.spec index 3aa69de2312fb779ddc3299662a719323c38c2d8..b0f14621f044d780baa36e65bc47dc7c1d520d9f 100644 --- a/postgresql.spec +++ b/postgresql.spec @@ -4,7 +4,7 @@ Name: postgresql Version: 10.5 -Release: 16 +Release: 18 Summary: PostgreSQL client programs License: PostgreSQL URL: http://www.postgresql.org/ @@ -23,19 +23,29 @@ Source11: macros.postgresql Source12: macros.postgresql-test Source13: postgresql_pkg_tests.sh -Patch0000: 0000-postgresql-var-run-socket.patch -Patch0001: 0000-rpm-pgsql.patch - -Patch6000: 6000-CVE-2019-10164-1.patch -Patch6001: 6001-CVE-2019-10164-2.patch -Patch6002: CVE-2019-10208.patch -Patch6003: CVE-2018-16850.patch -Patch6004: CVE-2019-10130.patch -Patch6005: CVE-2020-1720.patch +Patch1: 0001-postgresql-var-run-socket.patch +Patch2: 0002-rpm-pgsql.patch +Patch3: 0003-CVE-2019-10164-1.patch +Patch4: 0004-CVE-2019-10164-2.patch +Patch5: 0005-CVE-2019-10208.patch +Patch6: 0006-CVE-2018-16850.patch +Patch7: 0007-CVE-2019-10130.patch +Patch8: 0008-CVE-2020-1720.patch +Patch9: 0009-CVE-2020-14349-1.patch +Patch10: 0010-CVE-2020-14349-2.patch +Patch11: 0011-CVE-2020-14350.patch +Patch12: Fix-error-handling-of-vacuumdb-when-running-out-of-f.patch +Patch13: Remove-some-code-related-to-7.3-and-older-servers-fr.patch +Patch14: createdb-Fix-quoting-of-encoding-lc-ctype-and-lc-col.patch +Patch15: CVE-2020-25694-1.patch +Patch16: CVE-2020-25694-2.patch +Patch17: CVE-2020-25694-3.patch +Patch18: CVE-2020-25695.patch +Patch19: CVE-2020-25696.patch BuildRequires: gcc perl(ExtUtils::MakeMaker) glibc-devel bison flex gawk perl(ExtUtils::Embed) BuildRequires: perl-devel perl-generators readline-devel zlib-devel systemd systemd-devel -BuildRequires: util-linux m4 elinks docbook-utils help2man +BuildRequires: util-linux m4 elinks docbook-utils help2man docbook-style-xsl BuildRequires: python3 python3-devel tcl-devel openssl-devel krb5-devel openldap-devel gettext >= 0.10.35 BuildRequires: uuid-devel libxml2-devel libxslt-devel pam-devel systemtap-sdt-devel libselinux-devel Requires: %{name}-libs = %{version}-%{release} @@ -158,14 +168,17 @@ that want to run build-time testsuite against running PostgreSQL server. sha256sum -c %{SOURCE3} ) %setup -q -%patch0000 -p1 -%patch0001 -p1 -%patch6000 -p1 -%patch6001 -p1 -%patch6002 -p1 -%patch6003 -p1 -%patch6004 -p1 -%patch6005 -p1 +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 +%patch9 -p1 +%patch10 -p1 +%patch11 -p1 %build if [ x"`id -u`" = x0 ]; then @@ -430,6 +443,12 @@ find_lang_bins pltcl.lst pltcl %attr(-,postgres,postgres) %{_libdir}/pgsql/test %changelog +* Tue Dec 8 2020 wangxiao - 10.5-18 +- Fix CVE-2020-25694 CVE-2020-25695 CVE-2020-25696 + +* Web Sep 9 2020 yanglongkang - 10.5-17 +- Fix CVE-2020-14349 CVE-2020-14350 + * Fri Jun 19 2020 cuibaobao - 10.5-16 - Type: enhancement - DESC: delete all about residual parse_upgrade_setup in postgresql-setup