From 523510f19077ff30944ef089bd0e8be053c6c156 Mon Sep 17 00:00:00 2001 From: Manuel Mendez Date: Fri, 21 Aug 2026 19:41:14 -0400 Subject: [PATCH 1/3] postgres: 15.16 -> 15.19, 17.6 -> 17.11 --- README.md | 4 ++-- migrations/schema-15.sql | 4 ++-- migrations/schema-17.sql | 4 ++-- nix/config.nix | 8 ++++---- nix/ext/tests/lib.nix | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6add67a24e..1faf0780a1 100644 --- a/README.md +++ b/README.md @@ -211,8 +211,8 @@ This is the same PostgreSQL build that powers [Supabase](https://supabase.io), b ## Primary Features -- ✅ Postgres [postgresql-15.14](https://www.postgresql.org/docs/15/index.html) -- ✅ Postgres [postgresql-17.6](https://www.postgresql.org/docs/17/index.html) +- ✅ Postgres [postgresql-15.19](https://www.postgresql.org/docs/15/index.html) +- ✅ Postgres [postgresql-17.11](https://www.postgresql.org/docs/17/index.html) - ✅ Postgres [orioledb-postgresql-17_11](https://github.com/orioledb/orioledb) - ✅ Ubuntu 24.04 (Noble Numbat). - ✅ [wal_level](https://www.postgresql.org/docs/current/runtime-config-wal.html) = logical and [max_replication_slots](https://www.postgresql.org/docs/current/runtime-config-replication.html) = 5. Ready for replication. diff --git a/migrations/schema-15.sql b/migrations/schema-15.sql index 322f47404c..fcd89ac957 100644 --- a/migrations/schema-15.sql +++ b/migrations/schema-15.sql @@ -4,8 +4,8 @@ \restrict SupabaseTestDumpKey123 --- Dumped from database version 15.14 --- Dumped by pg_dump version 15.14 +-- Dumped from database version 15.19 +-- Dumped by pg_dump version 15.19 SET statement_timeout = 0; SET lock_timeout = 0; diff --git a/migrations/schema-17.sql b/migrations/schema-17.sql index 2cbfa1b146..9df1110027 100644 --- a/migrations/schema-17.sql +++ b/migrations/schema-17.sql @@ -4,8 +4,8 @@ \restrict SupabaseTestDumpKey123 --- Dumped from database version 17.6 --- Dumped by pg_dump version 17.6 +-- Dumped from database version 17.11 +-- Dumped by pg_dump version 17.11 SET statement_timeout = 0; SET lock_timeout = 0; diff --git a/nix/config.nix b/nix/config.nix index 7ed5338e0a..a473c2bb6f 100644 --- a/nix/config.nix +++ b/nix/config.nix @@ -46,12 +46,12 @@ in supportedPostgresVersions = { postgres = { "15" = { - version = "15.14"; - hash = "sha256-Bt110wXNOHDuYrOTLmYcYkVD6vmuK6N83sCk+O3QUdI="; + version = "15.19"; + hash = "sha256-4aZKh6RrgluIwILkUYFhpHqrU8RWlJZPi6HfKPeFn4k="; }; "17" = { - version = "17.6"; - hash = "sha256-4GMKNgCuonURcVVjJZ7CERzV9DU6SwQOC+gn+UzXqLA="; + version = "17.11"; + hash = "sha256-3Sfys8Wec+0UqjMkkBJCv2mgMqY0eAXydOYmAyLUKXk="; }; }; orioledb = { diff --git a/nix/ext/tests/lib.nix b/nix/ext/tests/lib.nix index 9c268f35f0..8522848e6a 100644 --- a/nix/ext/tests/lib.nix +++ b/nix/ext/tests/lib.nix @@ -4,8 +4,8 @@ let system = pkgs.pkgsLinux.stdenv.hostPlatform.system; expectedVersions = { - "15" = "15.14"; - "17" = "17.6"; + "15" = "15.19"; + "17" = "17.11"; }; defaultPort = 5432; From df77cb35815b6b7eaf1c601d46f27290d521a5c4 Mon Sep 17 00:00:00 2001 From: Utkarash Singh Date: Mon, 24 Aug 2026 17:46:40 +0100 Subject: [PATCH 2/3] fix: Add wal2json to output_plugin_libraries PG 15.19/17.11 introduces output_plugin_libraries (CVE-2026-6471) defaulting to pgoutput and test_decoding only, which breaks wal2json slots (Realtime, CI wal2json tests). Allow wal2json in the shared config, but comment it out for orioledb builds (17.9 base, GUC absent, unknown parameter is fatal at startup). --- Dockerfile-orioledb-17 | 2 ++ ansible/files/postgresql_config/postgresql.conf.j2 | 3 +++ ansible/tasks/stage2-setup-postgres.yml | 6 ++++++ docker/pgctld/postgresql.conf.tmpl | 2 ++ nix/checks.nix | 4 ++++ nix/ext/tests/default.nix | 6 +++++- nix/ext/tests/lib.nix | 3 +++ nix/ext/wal2json.nix | 4 ++++ nix/tools/run-server.sh.in | 4 ++++ 9 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Dockerfile-orioledb-17 b/Dockerfile-orioledb-17 index 7cd70a3710..68aef054d6 100644 --- a/Dockerfile-orioledb-17 +++ b/Dockerfile-orioledb-17 @@ -150,8 +150,10 @@ RUN sed -i \ chown -R postgres:postgres /etc/postgresql-custom # Remove timescaledb, plv8, postgis, pgrouting references (not available in orioledb build) +# and output_plugin_libraries (GUC does not exist before PG 17.11; orioledb is on an older base) RUN sed -i 's/ timescaledb,//g;' "/etc/postgresql/postgresql.conf" && \ sed -i 's/db_user_namespace = off/#db_user_namespace = off/g;' "/etc/postgresql/postgresql.conf" && \ + sed -i 's/^output_plugin_libraries/#output_plugin_libraries/g;' "/etc/postgresql/postgresql.conf" && \ sed -i 's/ timescaledb,//g; s/ plv8,//g; s/ postgis,//g; s/ pgrouting,//g' "/etc/postgresql-custom/supautils.conf" # OrioleDB configuration diff --git a/ansible/files/postgresql_config/postgresql.conf.j2 b/ansible/files/postgresql_config/postgresql.conf.j2 index 154ec13416..37e50623e0 100644 --- a/ansible/files/postgresql_config/postgresql.conf.j2 +++ b/ansible/files/postgresql_config/postgresql.conf.j2 @@ -204,6 +204,9 @@ shared_buffers = 128MB # min 128kB wal_level = logical # minimal, replica, or logical # (change requires restart) +output_plugin_libraries = 'pgoutput, test_decoding, wal2json' # allowlist of logical decoding + # output plugins (PG 15.19 / 17.11+); wal2json is + # required by Realtime and shipped in the image #fsync = on # flush data to disk for crash safety # (turning this off can cause # unrecoverable data corruption) diff --git a/ansible/tasks/stage2-setup-postgres.yml b/ansible/tasks/stage2-setup-postgres.yml index d4823238a7..c77ffd8fc7 100644 --- a/ansible/tasks/stage2-setup-postgres.yml +++ b/ansible/tasks/stage2-setup-postgres.yml @@ -36,6 +36,12 @@ when: stage2 and is_psql_oriole become: true block: + - name: Comment out output_plugin_libraries if orioledb build (GUC does not exist before PG 17.11) + ansible.builtin.replace: + path: '/etc/postgresql/postgresql.conf' + regexp: '^output_plugin_libraries' + replace: '#output_plugin_libraries' + - name: Append orioledb to shared_preload_libraries append within closing quote ansible.builtin.replace: path: '/etc/postgresql/postgresql.conf' diff --git a/docker/pgctld/postgresql.conf.tmpl b/docker/pgctld/postgresql.conf.tmpl index 4067e5ab80..e8fdfbec60 100644 --- a/docker/pgctld/postgresql.conf.tmpl +++ b/docker/pgctld/postgresql.conf.tmpl @@ -83,6 +83,8 @@ max_parallel_maintenance_workers = {{.MaxParallelMaintenanceWorkers}} # taken fr wal_level = logical # minimal, replica, or logical # (change requires restart) +output_plugin_libraries = 'pgoutput, test_decoding, wal2json' # allowlist of logical decoding + # output plugins (PG 15.19 / 17.11+) wal_buffers = {{.WalBuffers}} # min 32kB, -1 sets based on shared_buffers # (change requires restart) min_wal_size = {{.MinWalSize}} diff --git a/nix/checks.nix b/nix/checks.nix index 3fdd5ba2c0..79ba151941 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -411,6 +411,10 @@ # Add orioledb to shared_preload_libraries perl -pi -e "s/(shared_preload_libraries = ')/\$1orioledb, /" "$PGTAP_CLUSTER/postgresql.conf" log info "OrioleDB added to shared_preload_libraries" + else + # PG 15.19 / 17.11+: allowlist wal2json for logical decoding (GUC does not + # exist on the orioledb 17.9 base, hence the else branch) + echo "output_plugin_libraries = 'pgoutput, test_decoding, wal2json'" >> "$PGTAP_CLUSTER"/postgresql.conf fi # Check if postgresql.conf exists diff --git a/nix/ext/tests/default.nix b/nix/ext/tests/default.nix index f0d8e37d46..e088ff6765 100644 --- a/nix/ext/tests/default.nix +++ b/nix/ext/tests/default.nix @@ -155,7 +155,11 @@ let self.packages.${pkgs.pkgsLinux.stdenv.hostPlatform.system}.postgresql_orioledb-17 ); settings = lib.mkForce ( - ((installedExtension "17").defaultSettings or { }) + # output_plugin_libraries does not exist before PG 17.11; orioledb is on an + # older base, so strip it from any extension's defaultSettings (e.g. wal2json) + (removeAttrs ((installedExtension "17").defaultSettings or { }) [ + "output_plugin_libraries" + ]) // { jit = "off"; shared_preload_libraries = [ diff --git a/nix/ext/tests/lib.nix b/nix/ext/tests/lib.nix index 8522848e6a..3c2d1ba3c0 100644 --- a/nix/ext/tests/lib.nix +++ b/nix/ext/tests/lib.nix @@ -115,6 +115,9 @@ let ${ if majorVersion == "orioledb-17" then '' + # OrioleDB: comment out output_plugin_libraries (GUC does not exist before PG 17.11; + # orioledb line is on a 17.9 base and would fail to start on an unknown parameter) + sed -i 's/^output_plugin_libraries/#output_plugin_libraries/' $out/postgresql.conf # OrioleDB: also remove pgjwt from supautils privileged_extensions sed -i 's/ pgjwt,//g;' $out/supautils.conf # OrioleDB: append orioledb to shared_preload_libraries diff --git a/nix/ext/wal2json.nix b/nix/ext/wal2json.nix index b082301c82..8ea1e44a7a 100644 --- a/nix/ext/wal2json.nix +++ b/nix/ext/wal2json.nix @@ -116,6 +116,10 @@ pkgs.buildEnv { "multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions); defaultSettings = { wal_level = "logical"; + # PG 15.19 / 17.11+ only load output plugins named here (CVE-2026-6471); + # stripped for the orioledb specialisation in nix/ext/tests/default.nix + # (GUC does not exist on orioledb's older base) + output_plugin_libraries = "pgoutput, test_decoding, wal2json"; }; }; } diff --git a/nix/tools/run-server.sh.in b/nix/tools/run-server.sh.in index 333f062bc7..19b980a67e 100644 --- a/nix/tools/run-server.sh.in +++ b/nix/tools/run-server.sh.in @@ -254,6 +254,8 @@ orioledb_config_items() { sed -i 's/ timescaledb,//g;' "$DATDIR/postgresql.conf" sed -i 's/db_user_namespace = off/#db_user_namespace = off/g;' "$DATDIR/postgresql.conf" sed -i 's/ timescaledb,//g; s/ plv8,//g; s/ pgjwt,//g;' "$DATDIR/supautils.conf" + # output_plugin_libraries does not exist before PG 17.11 (orioledb is on an older base) + sed -i 's/^output_plugin_libraries/#output_plugin_libraries/g;' "$DATDIR/postgresql.conf" sed -i 's/\(shared_preload_libraries.*\)'\''\(.*\)$/\1, orioledb'\''\2/' "$DATDIR/postgresql.conf" echo "default_table_access_method = 'orioledb'" >> "$DATDIR/postgresql.conf" elif [[ "$1" = "orioledb-17" && "$CURRENT_SYSTEM" = "aarch64-darwin" ]]; then @@ -264,6 +266,8 @@ orioledb_config_items() { # Use perl instead of sed for macOS perl -pi -e 's/ timescaledb,//g' "$DATDIR/postgresql.conf" perl -pi -e 's/db_user_namespace = off/#db_user_namespace = off/g' "$DATDIR/postgresql.conf" + # output_plugin_libraries does not exist before PG 17.11 (orioledb is on an older base) + perl -pi -e 's/^output_plugin_libraries/#output_plugin_libraries/' "$DATDIR/postgresql.conf" perl -pi -e 's/ timescaledb,//g' "$DATDIR/supautils.conf" perl -pi -e 's/ plv8,//g' "$DATDIR/supautils.conf" From 5d40f9a6328dca0a4a7855301f1efe57cf886499 Mon Sep 17 00:00:00 2001 From: Utkarash Singh Date: Tue, 25 Aug 2026 12:13:42 +0100 Subject: [PATCH 3/3] test(postgres): CVE & behavior-change regression suite for 15.19 / 17.11 Permanent pg_regress suite under nix/tests/sql pinning this release's CVE and behavior-change fixes. Existing-scope fixes (15.16-15.18 / 17.7-17.10): - operator_breaking_change (CVE-2026-2004) - pgcrypto (CVE-2026-2005) - pg_trgm (CVE-2026-2006) - intarray_ltree_query (CVE-2026-6473), - ltree_reindex, hstore_copy_binary, merge_repeatable_read, multirange_create_priv (CVE-2026-6472) - create_statistics_priv (CVE-2025-12817). - pgcrypto_cipher_matrix (CVE-2026-14663) - output_plugin_libraries (CVE-2026-6471) - btree_gist_nan (fix NaN handling for float4 & float8) - ltree_label_overflow (fix integer overflow in comparisons) All skipped on orioledb-17 (older base predates these fixes). output_plugin_libraries and btree_gist_nan skipped on the CLI variant (logical-decoding infra / btree_gist not primed there). Refs: PSQL-1110, PSQL-1234. --- nix/checks.nix | 18 +++++ nix/tests/expected/btree_gist_nan.out | 47 ++++++++++++ nix/tests/expected/create_statistics_priv.out | 26 +++++++ nix/tests/expected/hstore_copy_binary.out | 47 ++++++++++++ nix/tests/expected/intarray_ltree_query.out | 46 +++++++++++ nix/tests/expected/ltree_label_overflow.out | 28 +++++++ nix/tests/expected/ltree_reindex.out | 53 +++++++++++++ nix/tests/expected/merge_repeatable_read.out | 29 +++++++ nix/tests/expected/multirange_create_priv.out | 31 ++++++++ .../expected/operator_breaking_change.out | 50 ++++++++++++ .../expected/output_plugin_libraries.out | 35 +++++++++ nix/tests/expected/pg_trgm.out | 39 ++++++++++ nix/tests/expected/pgcrypto.out | 37 +++++++++ nix/tests/expected/pgcrypto_cipher_matrix.out | 76 +++++++++++++++++++ .../expected/replica_identity_upsert.out | 35 +++++++++ nix/tests/sql/btree_gist_nan.sql | 32 ++++++++ nix/tests/sql/create_statistics_priv.sql | 33 ++++++++ nix/tests/sql/hstore_copy_binary.sql | 38 ++++++++++ nix/tests/sql/intarray_ltree_query.sql | 28 +++++++ nix/tests/sql/ltree_label_overflow.sql | 26 +++++++ nix/tests/sql/ltree_reindex.sql | 41 ++++++++++ nix/tests/sql/merge_repeatable_read.sql | 27 +++++++ nix/tests/sql/multirange_create_priv.sql | 37 +++++++++ nix/tests/sql/operator_breaking_change.sql | 57 ++++++++++++++ nix/tests/sql/output_plugin_libraries.sql | 27 +++++++ nix/tests/sql/pg_trgm.sql | 30 ++++++++ nix/tests/sql/pgcrypto.sql | 31 ++++++++ nix/tests/sql/pgcrypto_cipher_matrix.sql | 55 ++++++++++++++ nix/tests/sql/replica_identity_upsert.sql | 32 ++++++++ 29 files changed, 1091 insertions(+) create mode 100644 nix/tests/expected/btree_gist_nan.out create mode 100644 nix/tests/expected/create_statistics_priv.out create mode 100644 nix/tests/expected/hstore_copy_binary.out create mode 100644 nix/tests/expected/intarray_ltree_query.out create mode 100644 nix/tests/expected/ltree_label_overflow.out create mode 100644 nix/tests/expected/ltree_reindex.out create mode 100644 nix/tests/expected/merge_repeatable_read.out create mode 100644 nix/tests/expected/multirange_create_priv.out create mode 100644 nix/tests/expected/operator_breaking_change.out create mode 100644 nix/tests/expected/output_plugin_libraries.out create mode 100644 nix/tests/expected/pg_trgm.out create mode 100644 nix/tests/expected/pgcrypto.out create mode 100644 nix/tests/expected/pgcrypto_cipher_matrix.out create mode 100644 nix/tests/expected/replica_identity_upsert.out create mode 100644 nix/tests/sql/btree_gist_nan.sql create mode 100644 nix/tests/sql/create_statistics_priv.sql create mode 100644 nix/tests/sql/hstore_copy_binary.sql create mode 100644 nix/tests/sql/intarray_ltree_query.sql create mode 100644 nix/tests/sql/ltree_label_overflow.sql create mode 100644 nix/tests/sql/ltree_reindex.sql create mode 100644 nix/tests/sql/merge_repeatable_read.sql create mode 100644 nix/tests/sql/multirange_create_priv.sql create mode 100644 nix/tests/sql/operator_breaking_change.sql create mode 100644 nix/tests/sql/output_plugin_libraries.sql create mode 100644 nix/tests/sql/pg_trgm.sql create mode 100644 nix/tests/sql/pgcrypto.sql create mode 100644 nix/tests/sql/pgcrypto_cipher_matrix.sql create mode 100644 nix/tests/sql/replica_identity_upsert.sql diff --git a/nix/checks.nix b/nix/checks.nix index 79ba151941..6e23ac2877 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -183,6 +183,22 @@ # Tests to skip for OrioleDB (not compatible with OrioleDB storage) orioledbSkipTests = [ "index_advisor" # index_advisor doesn't support OrioleDB tables + + # Checks CVE/behavior changes from 17.7 -> 17.11, drop when orioledb base is updated to/past these. + "operator_breaking_change" # CVE-2026-2004 (17.8) + "pgcrypto" # CVE-2026-2005 (17.8) + "pg_trgm" # CVE-2026-2006 multibyte (17.8) + "intarray_ltree_query" # CVE-2026-6473 (17.10) + "ltree_reindex" # ltree multibyte fix (17.8/17.10) + "hstore_copy_binary" # hstore recv crash fix (17.x > 17.6) + "merge_repeatable_read" # MERGE 40001 serialization fix + "multirange_create_priv" # CVE-2026-6472 (17.10) + "create_statistics_priv" # CVE-2025-12817 (17.7) + "pgcrypto_cipher_matrix" # CVE-2026-14663 (17.11) + "output_plugin_libraries" # CVE-2026-6471 (17.11) + "btree_gist_nan" # btree_gist NaN fix (17.11) + "ltree_label_overflow" # ltree comparison overflow fix (17.11) + "replica_identity_upsert" # MERGE/ON CONFLICT replica-identity check (17.7/15.15) ]; # Helper function to filter SQL files based on version @@ -256,6 +272,8 @@ "pg_cron_trigger_privileges" # needs pg_cron + the postgres role and cron-schema grants from the full migrations, not in the CLI prime file "supautils_restrict_versions" # needs the postgres role + primed hstore from the full migrations/prime, not present in the CLI variant "amcheck" # needs the postgres/anon/authenticated/service_role roles and the default privileges from the full migrations, plus amcheck primed by prime.sql + "output_plugin_libraries" # needs wal_level=logical + logical-decoding infra, not exercised in the CLI variant + "btree_gist_nan" # needs btree_gist, not in the CLI prime file # Version-specific extension tests "z_17_ext_interface" "z_17_pg_stat_monitor" diff --git a/nix/tests/expected/btree_gist_nan.out b/nix/tests/expected/btree_gist_nan.out new file mode 100644 index 0000000000..b585706847 --- /dev/null +++ b/nix/tests/expected/btree_gist_nan.out @@ -0,0 +1,47 @@ +-- btree_gist NaN handling in the float4/float8 opclasses (comparisons and the +-- GiST penalty/distance functions) previously gave wrong answers when a NaN was +-- present; upstream recommends reindexing btree_gist float indexes that may hold +-- NaN after the update. This pins the post-fix within-version correctness of +-- index scans over a float8 column containing NaN. +-- +-- Upstream commit: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- (The cross-version pre-upgrade-build / post-upgrade-REINDEX leg is A3, +-- PSQL-1235.) +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE EXTENSION IF NOT EXISTS btree_gist; +NOTICE: extension "btree_gist" already exists, skipping +CREATE TABLE bg_nan (v float8); +INSERT INTO bg_nan VALUES (1), (2), (3), ('NaN'); +CREATE INDEX bg_nan_gist ON bg_nan USING gist (v); +-- Force index scans so we exercise the opclass, not a seqscan recheck. +SET enable_seqscan = off; +-- NaN sorts as greater than every non-NaN value and equals only itself. +SELECT count(*) AS eq_nan FROM bg_nan WHERE v = 'NaN'::float8; + eq_nan +-------- + 1 +(1 row) + +SELECT count(*) AS gt_one FROM bg_nan WHERE v > 1; -- 2, 3, NaN + gt_one +-------- + 3 +(1 row) + +SELECT count(*) AS ne_two FROM bg_nan WHERE v <> 2; -- 1, 3, NaN + ne_two +-------- + 3 +(1 row) + +SELECT v FROM bg_nan WHERE v >= 3 ORDER BY v; -- 3, NaN + v +----- + 3 + NaN +(2 rows) + +RESET enable_seqscan; +ROLLBACK; diff --git a/nix/tests/expected/create_statistics_priv.out b/nix/tests/expected/create_statistics_priv.out new file mode 100644 index 0000000000..983a754f02 --- /dev/null +++ b/nix/tests/expected/create_statistics_priv.out @@ -0,0 +1,26 @@ +-- CVE-2025-12817: CREATE STATISTICS did not check CREATE privilege on the +-- schema where the statistics object is created, letting a table owner create +-- statistics objects in any schema (naming-conflict / privilege concern). +-- +-- Upstream commits: 2393d374 + d202ec1f (PG 15.15), e2fb3dfa (PG 17.7). The fix +-- adds a pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE). +-- +-- Verified as a non-superuser table owner. Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE SCHEMA owned_ns; +CREATE SCHEMA forbidden_ns; +-- postgres can create in owned_ns only; it has no rights on forbidden_ns. +GRANT CREATE, USAGE ON SCHEMA owned_ns TO postgres; +SET ROLE postgres; +-- A table postgres owns, in a schema postgres controls. +CREATE TABLE owned_ns.stat_tbl (a int, b int); +INSERT INTO owned_ns.stat_tbl SELECT g % 10, g % 5 FROM generate_series(1, 100) g; +-- Positive control: stats object in owned_ns (postgres has CREATE) is allowed. +CREATE STATISTICS owned_ns.okstat (dependencies) ON a, b FROM owned_ns.stat_tbl; +-- The fix: a stats object targeting a schema where postgres lacks CREATE is denied. +SAVEPOINT no_priv; +CREATE STATISTICS forbidden_ns.badstat (dependencies) ON a, b FROM owned_ns.stat_tbl; +ERROR: permission denied for schema forbidden_ns +ROLLBACK TO SAVEPOINT no_priv; +RESET ROLE; +ROLLBACK; diff --git a/nix/tests/expected/hstore_copy_binary.out b/nix/tests/expected/hstore_copy_binary.out new file mode 100644 index 0000000000..c96abb526d --- /dev/null +++ b/nix/tests/expected/hstore_copy_binary.out @@ -0,0 +1,47 @@ +-- Non-CVE behavior change: the hstore receive function had a NULL-pointer +-- dereference (backend crash) on COPY BINARY of an hstore whose binary form +-- contains a DUPLICATE key where the second occurrence's value is NULL. +-- +-- Upstream commits: 63c05e03 (PG 15.x), 0dfbe42d (PG 17.x). +-- +-- A normal INSERT cannot reproduce this: hstore de-duplicates on text input, so +-- a stored value never carries a duplicate key into the binary path. We instead +-- hand-craft a COPY-BINARY stream whose single hstore field contains the pair +-- sequence [ 'a' => '1', 'a' => NULL ] and feed it through hstore_recv via +-- COPY ... FROM. Pre-fix this crashed the backend; on the fixed builds the +-- duplicate is de-duplicated and the row loads cleanly. +-- +-- pg_regress runs as the superuser supabase_admin, so lo_export / server-side +-- COPY FROM a file are permitted. Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE TABLE hstore_dst (h hstore); +-- Materialise the crafted COPY-BINARY stream to a file (created and exported in +-- separate statements so the large object is visible to lo_export). +SELECT lo_from_bytea(81000, + '\x5047434f50590aff0d0a00'::bytea || -- COPY binary signature + '\x00000000'::bytea || '\x00000000'::bytea || -- flags + header-extension length + '\x0001'::bytea || '\x00000017'::bytea || -- one row, one field of length 23 + '\x00000002'::bytea || -- hstore: 2 pairs + '\x00000001'::bytea||'\x61'::bytea||'\x00000001'::bytea||'\x31'::bytea || -- 'a' => '1' + '\x00000001'::bytea||'\x61'::bytea||'\xffffffff'::bytea || -- 'a' => NULL + '\xffff'::bytea) AS loid; -- COPY trailer + loid +------- + 81000 +(1 row) + +SELECT lo_export(81000, '/tmp/pg_regress_hstore_dup.bin') AS exported; + exported +---------- + 1 +(1 row) + +-- Must not crash the backend; the duplicate key is de-duplicated on receive. +COPY hstore_dst FROM '/tmp/pg_regress_hstore_dup.bin' WITH (FORMAT binary); +SELECT h AS received, akeys(h) AS keys FROM hstore_dst; + received | keys +----------+------ + "a"=>"1" | {a} +(1 row) + +ROLLBACK; diff --git a/nix/tests/expected/intarray_ltree_query.out b/nix/tests/expected/intarray_ltree_query.out new file mode 100644 index 0000000000..30084cbd72 --- /dev/null +++ b/nix/tests/expected/intarray_ltree_query.out @@ -0,0 +1,46 @@ +-- CVE-2026-6473: memory-allocation overflow umbrella covering, among others, +-- contrib intarray query_int and contrib ltree ltxtquery / lquery parsing. +-- +-- Upstream key commits: 84a9f264 (intarray/ltree), 9c2fa5b6 (ltree lquery) on +-- PG 15.18; c4d04cc4 and siblings on PG 17.10. Full list: +-- git log REL_17_6..REL_17_10 --grep='CVE-2026-6473' +-- +-- Functional regression: well-formed queries parse and match correctly; a +-- malformed query raises a clean parse error instead of crashing. +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +-- 1) intarray query_int matching. +SELECT '{1,2,3}'::int[] @@ '2&4'::query_int AS q_and; -- expect false + q_and +------- + f +(1 row) + +SELECT '{1,2,3}'::int[] @@ '2|4'::query_int AS q_or; -- expect true + q_or +------ + t +(1 row) + +-- 2) ltree lquery and ltxtquery matching. +SELECT 'Top.Science.Astronomy'::ltree ~ 'Top.*.Astronomy'::lquery AS lquery_match; -- true + lquery_match +-------------- + t +(1 row) + +SELECT 'Top.Science.Astronomy'::ltree @ 'Astronomy & Top'::ltxtquery AS ltxtquery_match; -- true + ltxtquery_match +----------------- + t +(1 row) + +-- 3) A malformed query_int must raise a clean parse error, not crash. +SAVEPOINT bad_query; +SELECT '{1}'::int[] @@ '2&&'::query_int; +ERROR: syntax error +LINE 1: SELECT '{1}'::int[] @@ '2&&'::query_int; + ^ +ROLLBACK TO SAVEPOINT bad_query; +ROLLBACK; diff --git a/nix/tests/expected/ltree_label_overflow.out b/nix/tests/expected/ltree_label_overflow.out new file mode 100644 index 0000000000..a01c2a777e --- /dev/null +++ b/nix/tests/expected/ltree_label_overflow.out @@ -0,0 +1,28 @@ +-- contrib/ltree: an integer overflow in ltree comparisons made values with more +-- than about 14,653 labels compare incorrectly; a btree index over such values +-- could become corrupt and need reindexing. This pins the post-fix comparison +-- correctness for very deep ltree values (no index needed: a btree entry that +-- size cannot exist, so this exercises the comparator directly). +-- +-- Upstream commit: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE EXTENSION IF NOT EXISTS ltree; +NOTICE: extension "ltree" already exists, skipping +-- Build two deep ltree values (~15,000 labels) differing only in the last label. +WITH v AS ( + SELECT (repeat('a.', 15000) || 'x')::ltree AS a, + (repeat('a.', 15000) || 'y')::ltree AS b +) +SELECT nlevel(a) > 14653 AS deep_enough, + a < b AS a_lt_b, + NOT (b < a) AS b_not_lt_a, + a = a AS a_eq_a +FROM v; + deep_enough | a_lt_b | b_not_lt_a | a_eq_a +-------------+--------+------------+-------- + t | t | t | t +(1 row) + +ROLLBACK; diff --git a/nix/tests/expected/ltree_reindex.out b/nix/tests/expected/ltree_reindex.out new file mode 100644 index 0000000000..fc4e9162be --- /dev/null +++ b/nix/tests/expected/ltree_reindex.out @@ -0,0 +1,53 @@ +-- Non-CVE behavior change (highest customer blast radius this release: ltree is +-- enabled on 2,245 projects). Two upstream commits fix multibyte handling in +-- ltree's case-insensitive label matching, so GiST indexes built under the old +-- logic must be REINDEXed after upgrade on multibyte / ICU databases: +-- +-- 335b2f30 (PG 15.16) + 2b993167 (PG 15.18) "Fix multibyte issues in ltree" +-- b8cfe9dc (PG 17.8) + d1bd9a7d (PG 17.10) +-- +-- This pins WITHIN-version GiST index correctness + REINDEX idempotence. The +-- cross-version pre-upgrade-build / post-upgrade-REINDEX leg is covered by A3 +-- (pg_upgrade migration tests, PSQL-1235). +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE TABLE ltree_mb (id int, path ltree); +INSERT INTO ltree_mb VALUES + (1, 'Top.Naïve.Café'), + (2, 'Top.Science.Astronomy'), + (3, 'Top.Résumé'); +CREATE INDEX ltree_mb_gist ON ltree_mb USING gist (path); +SET enable_seqscan = off; +-- Index search before REINDEX. +SELECT id, path FROM ltree_mb WHERE path ~ 'Top.*'::lquery ORDER BY id; + id | path +----+----------------------- + 1 | Top.Naïve.Café + 2 | Top.Science.Astronomy + 3 | Top.Résumé +(3 rows) + +-- REINDEX (plain, so it runs inside the transaction) must not corrupt the index. +REINDEX INDEX ltree_mb_gist; +-- Same search after REINDEX must return the identical set. +SELECT id, path FROM ltree_mb WHERE path ~ 'Top.*'::lquery ORDER BY id; + id | path +----+----------------------- + 1 | Top.Naïve.Café + 2 | Top.Science.Astronomy + 3 | Top.Résumé +(3 rows) + +-- The '@' label modifier makes the match case-insensitive, which is what +-- invokes the multibyte ltree_strncasecmp path the upstream commits fixed. +SELECT id FROM ltree_mb WHERE path ~ 'top@.*'::lquery ORDER BY id; + id +---- + 1 + 2 + 3 +(3 rows) + +RESET enable_seqscan; +ROLLBACK; diff --git a/nix/tests/expected/merge_repeatable_read.out b/nix/tests/expected/merge_repeatable_read.out new file mode 100644 index 0000000000..12ea671bcf --- /dev/null +++ b/nix/tests/expected/merge_repeatable_read.out @@ -0,0 +1,29 @@ +-- Non-CVE behavior change: MERGE now correctly raises a serialization failure +-- (SQLSTATE 40001) under REPEATABLE READ / SERIALIZABLE when it hits a +-- concurrently-updated tuple (previously this could be silently mishandled). +-- +-- This pins the single-session HAPPY PATH only: MERGE under REPEATABLE READ +-- still produces correct results. The actual concurrent-conflict (40001) case +-- needs two concurrent sessions via the isolation tester, tracked in (PSQL-1277) +-- since pg_isolation_regress is not wired into nix/checks.nix yet. +-- +-- Refs: PSQL-1110, PSQL-1234, PSQL-1277. +BEGIN ISOLATION LEVEL REPEATABLE READ; +CREATE TABLE merge_target (id int PRIMARY KEY, v int); +CREATE TABLE merge_source (id int, v int); +INSERT INTO merge_target VALUES (1, 10), (2, 20); +INSERT INTO merge_source VALUES (1, 100), (3, 300); +MERGE INTO merge_target t +USING merge_source s ON t.id = s.id +WHEN MATCHED THEN UPDATE SET v = s.v +WHEN NOT MATCHED THEN INSERT (id, v) VALUES (s.id, s.v); +-- Expect: id 1 updated to 100, id 2 untouched (20), id 3 inserted (300). +SELECT id, v FROM merge_target ORDER BY id; + id | v +----+----- + 1 | 100 + 2 | 20 + 3 | 300 +(3 rows) + +ROLLBACK; diff --git a/nix/tests/expected/multirange_create_priv.out b/nix/tests/expected/multirange_create_priv.out new file mode 100644 index 0000000000..ecbeba42db --- /dev/null +++ b/nix/tests/expected/multirange_create_priv.out @@ -0,0 +1,31 @@ +-- CVE-2026-6472: CREATE TYPE ... AS RANGE auto-creates a companion multirange +-- type. When the multirange type name was given EXPLICITLY, the schema CREATE +-- privilege for that name was not validated (the auto-generated-name path was +-- already checked), letting a role create a multirange type in any schema. +-- +-- Upstream commits: 08c397b0 (PG 15.18), c27ba08c (PG 17.10). The fix adds a +-- pg_namespace_aclcheck(multirangeNamespace, GetUserId(), ACL_CREATE). +-- +-- Verified as a non-superuser. Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE SCHEMA allowed_ns; +CREATE SCHEMA forbidden_ns; +-- postgres gets CREATE on allowed_ns only; it has no rights on forbidden_ns. +GRANT CREATE, USAGE ON SCHEMA allowed_ns TO postgres; +SET ROLE postgres; +-- Positive control: explicit multirange name in a schema postgres CAN create in. +CREATE TYPE allowed_ns.okrange AS RANGE ( + subtype = int4, + multirange_type_name = allowed_ns.okmultirange +); +-- The fix: an explicit multirange name targeting a schema where postgres lacks +-- CREATE must now be denied. +SAVEPOINT no_priv; +CREATE TYPE allowed_ns.badrange AS RANGE ( + subtype = int4, + multirange_type_name = forbidden_ns.badmultirange +); +ERROR: permission denied for schema forbidden_ns +ROLLBACK TO SAVEPOINT no_priv; +RESET ROLE; +ROLLBACK; diff --git a/nix/tests/expected/operator_breaking_change.out b/nix/tests/expected/operator_breaking_change.out new file mode 100644 index 0000000000..93d1cb3444 --- /dev/null +++ b/nix/tests/expected/operator_breaking_change.out @@ -0,0 +1,50 @@ +-- Pin CVE-2026-2004 behaviour: attaching a non-built-in selectivity estimator +-- to an operator requires superuser. Verified against both RESTRICT and JOIN. +-- +-- Upstream commits: b764b26f (PG 15.16), bbf5bcf5 (PG 17.8). The check fires in +-- both ValidateRestrictionEstimator() and ValidateJoinEstimator() in +-- src/backend/commands/operatorcmds.c. +-- +-- We use real non-built-in estimators shipped by intarray (_int_matchsel for +-- RESTRICT, _int_overlap_joinsel for JOIN) -- these are exactly the customer- +-- reachable estimators the CVE-2026-2004 fleet-scan query targets. +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +-- A schema the non-superuser controls, so CREATE OPERATOR reaches the estimator +-- validation rather than failing an earlier schema-permission check. +CREATE SCHEMA op_ns; +GRANT CREATE, USAGE ON SCHEMA op_ns TO postgres; +-- Trivial boolean procedure for the operator (no internal args -> valid in SQL). +CREATE FUNCTION op_ns.fake_op_proc(_int4, _int4) + RETURNS bool LANGUAGE sql IMMUTABLE AS $$ SELECT true $$; +-- Switch to a non-superuser role. +SET ROLE postgres; +-- 1) RESTRICT = non-built-in estimator should be rejected. +SAVEPOINT before_restrict; +CREATE OPERATOR op_ns.@@@ ( + LEFTARG = _int4, RIGHTARG = _int4, + PROCEDURE = op_ns.fake_op_proc, + RESTRICT = _int_matchsel +); +ERROR: must be superuser to specify a non-built-in restriction estimator function +ROLLBACK TO SAVEPOINT before_restrict; +-- 2) JOIN = non-built-in estimator should be rejected. +SAVEPOINT before_join; +CREATE OPERATOR op_ns.@@@ ( + LEFTARG = _int4, RIGHTARG = _int4, + PROCEDURE = op_ns.fake_op_proc, + JOIN = _int_overlap_joinsel +); +ERROR: must be superuser to specify a non-built-in join estimator function +ROLLBACK TO SAVEPOINT before_join; +-- 3) Sanity check: built-in selectivity estimators still work for non-superusers. +CREATE OPERATOR op_ns.@@@ ( + LEFTARG = _int4, RIGHTARG = _int4, + PROCEDURE = op_ns.fake_op_proc, + RESTRICT = eqsel, + JOIN = eqjoinsel +); +DROP OPERATOR op_ns.@@@ (_int4, _int4); +RESET ROLE; +ROLLBACK; diff --git a/nix/tests/expected/output_plugin_libraries.out b/nix/tests/expected/output_plugin_libraries.out new file mode 100644 index 0000000000..30e49b874f --- /dev/null +++ b/nix/tests/expected/output_plugin_libraries.out @@ -0,0 +1,35 @@ +-- CVE-2026-6471: logical decoding could load any library named as an output +-- plugin. PG 15.19 / 17.11 add the "output_plugin_libraries" allowlist GUC; +-- only libraries named there may be used as output plugins. +-- +-- Upstream commit: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- +-- This pins the allowlist enforcement, independent of the exact allowlist value: +-- * an allowlisted, always-shipped plugin (test_decoding) can back a slot; +-- * a non-allowlisted library is rejected at slot-creation time. +-- The image's own allowlist additionally includes wal2json (Realtime); that +-- positive case is covered by wal2json.sql. Requires wal_level = logical, which +-- the image config sets. +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +-- 1) Allowlisted plugin: slot creation succeeds, then clean up. +SELECT slot_name FROM pg_create_logical_replication_slot('opl_ok', 'test_decoding'); + slot_name +----------- + opl_ok +(1 row) + +SELECT pg_drop_replication_slot('opl_ok'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +-- 2) Non-allowlisted library: slot creation must be rejected. +SAVEPOINT s_bad; +SELECT pg_create_logical_replication_slot('opl_bad', 'nonesuch_plugin'); +ERROR: library "nonesuch_plugin" may not be used as an output plugin +HINT: If it is safe for all REPLICATION users to use this library as an output plugin, add it to "output_plugin_libraries" and reload the server configuration. +ROLLBACK TO SAVEPOINT s_bad; +ROLLBACK; diff --git a/nix/tests/expected/pg_trgm.out b/nix/tests/expected/pg_trgm.out new file mode 100644 index 0000000000..0cead1ddd4 --- /dev/null +++ b/nix/tests/expected/pg_trgm.out @@ -0,0 +1,39 @@ +-- CVE-2026-2006: multibyte length validation via bounds-checked pg_mblen() +-- variants. Affects every multibyte text path, including pg_trgm. +-- +-- Upstream commits: fd82ddb6, 50863be0, b2c81ac8, 8f8b1ffa (PG 15.16); +-- 319e8a64, 7a522039, 838248b1, dc072a09 (PG 17.8). +-- +-- Functional regression: trigram generation, similarity, and GIN index search +-- all return correct results on multibyte (UTF-8) input on the fixed builds. +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +-- 1) show_trgm() on a multibyte (UTF-8) string returns well-formed trigrams. +SELECT show_trgm('café'); + show_trgm +------------------------------------- + {0xef5960," c"," ca",0x544980,caf} +(1 row) + +-- 2) similarity() of two multibyte strings is positive and symmetric. +SELECT similarity('café', 'café') AS self_sim, + similarity('café', 'cafe') = similarity('cafe', 'café') AS symmetric; + self_sim | symmetric +----------+----------- + 1 | t +(1 row) + +-- 3) A GIN trigram index on multibyte data returns the correct match set. +CREATE TABLE trgm_mb (id int, t text); +INSERT INTO trgm_mb VALUES (1, 'café'), (2, 'naïve'), (3, 'résumé'); +CREATE INDEX trgm_mb_idx ON trgm_mb USING gin (t gin_trgm_ops); +SET enable_seqscan = off; +SELECT id, t FROM trgm_mb WHERE t % 'café' ORDER BY id; + id | t +----+------ + 1 | café +(1 row) + +RESET enable_seqscan; +ROLLBACK; diff --git a/nix/tests/expected/pgcrypto.out b/nix/tests/expected/pgcrypto.out new file mode 100644 index 0000000000..40dce0f4ce --- /dev/null +++ b/nix/tests/expected/pgcrypto.out @@ -0,0 +1,37 @@ +-- CVE-2026-2005: heap buffer overflow in pgcrypto's pgp_*_decrypt_bytea() on an +-- oversized PGP session-key length. The fix hardens the PGP packet-length parser +-- shared by the symmetric and public-key bytea decrypt paths. +-- +-- Upstream commits: 9a9982ec (PG 15.16), 7a7d9693 (PG 17.8). +-- pgcrypto is default-enabled on Supabase, so this path is customer-reachable. +-- +-- This is a functional + crash-safety regression: a valid round-trip still works, +-- and a malformed PGP packet raises a clean SQL error instead of crashing the +-- backend. (The public-key variant needs externally-generated GPG keys, so we +-- exercise the shared packet parser via the symmetric bytea path.) +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +-- 1) Happy path: symmetric PGP bytea round-trip returns the original plaintext. +SELECT pgp_sym_decrypt_bytea( + pgp_sym_encrypt_bytea('\xdeadbeef'::bytea, 'test-key'), + 'test-key') = '\xdeadbeef'::bytea AS roundtrip_ok; + roundtrip_ok +-------------- + t +(1 row) + +-- 2) A malformed PGP packet must raise a clean error, not crash the backend +-- (exercises the hardened packet-length parser). +SAVEPOINT malformed; +SELECT pgp_sym_decrypt_bytea('\xdeadbeefcafebabe'::bytea, 'test-key'); +ERROR: Wrong key or corrupt data +ROLLBACK TO SAVEPOINT malformed; +-- 3) Backend is still alive and pgcrypto still works after the error. +SELECT pgp_sym_decrypt(pgp_sym_encrypt('still-here', 'k'), 'k') AS post_error_ok; + post_error_ok +--------------- + still-here +(1 row) + +ROLLBACK; diff --git a/nix/tests/expected/pgcrypto_cipher_matrix.out b/nix/tests/expected/pgcrypto_cipher_matrix.out new file mode 100644 index 0000000000..45a0628f90 --- /dev/null +++ b/nix/tests/expected/pgcrypto_cipher_matrix.out @@ -0,0 +1,76 @@ +-- CVE-2026-14663: pgcrypto's PGP functions previously did not detect when the +-- requested cipher was unavailable in the server's OpenSSL build. Encryption +-- then silently produced output that was not actually encrypted, and decryption +-- would succeed even with the wrong key. The fix makes encryption fail loudly +-- when the cipher is unavailable, and makes decryption reject such messages. +-- +-- Upstream commits: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- +-- This pins the post-fix contract for the cipher options pgcrypto exposes: +-- * ciphers unavailable in this build (bf/blowfish/cast5) -> encrypt ERRORs +-- rather than silently emitting unencrypted output; +-- * available ciphers (aes*, 3des) round-trip correctly AND reject a wrong +-- passphrase with "Wrong key or corrupt data" (integrity protection intact). +-- The exact set of unavailable ciphers is a function of the OpenSSL build; this +-- suite runs against the image's own OpenSSL, so the assertions below track that +-- build's behavior. +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE EXTENSION IF NOT EXISTS pgcrypto; +NOTICE: extension "pgcrypto" already exists, skipping +-- 1) Unavailable ciphers must fail at encrypt time, not silently pass through. +SAVEPOINT s_bf; +SELECT pgp_sym_encrypt('secret', 'k', 'cipher-algo=bf'); +ERROR: encrypt error: Cipher cannot be initialized +ROLLBACK TO SAVEPOINT s_bf; +SAVEPOINT s_blowfish; +SELECT pgp_sym_encrypt('secret', 'k', 'cipher-algo=blowfish'); +ERROR: encrypt error: Cipher cannot be initialized +ROLLBACK TO SAVEPOINT s_blowfish; +SAVEPOINT s_cast5; +SELECT pgp_sym_encrypt('secret', 'k', 'cipher-algo=cast5'); +ERROR: encrypt error: Cipher cannot be initialized +ROLLBACK TO SAVEPOINT s_cast5; +-- 2) Available ciphers round-trip correctly with the right key. +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes128'), 'k') AS aes128_rt; + aes128_rt +----------- + secret +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes192'), 'k') AS aes192_rt; + aes192_rt +----------- + secret +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes256'), 'k') AS aes256_rt; + aes256_rt +----------- + secret +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=3des'), 'k') AS des3_rt; + des3_rt +--------- + secret +(1 row) + +-- 3) The security property: a WRONG passphrase must be rejected, not accepted. +SAVEPOINT s_aes_wrong; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes256'), 'WRONG'); +ERROR: Wrong key or corrupt data +ROLLBACK TO SAVEPOINT s_aes_wrong; +SAVEPOINT s_des3_wrong; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=3des'), 'WRONG'); +ERROR: Wrong key or corrupt data +ROLLBACK TO SAVEPOINT s_des3_wrong; +-- 4) Backend still healthy after the expected errors. +SELECT pgp_sym_decrypt(pgp_sym_encrypt('still-here', 'k'), 'k') AS post_error_ok; + post_error_ok +--------------- + still-here +(1 row) + +ROLLBACK; diff --git a/nix/tests/expected/replica_identity_upsert.out b/nix/tests/expected/replica_identity_upsert.out new file mode 100644 index 0000000000..72b84c19ba --- /dev/null +++ b/nix/tests/expected/replica_identity_upsert.out @@ -0,0 +1,35 @@ +-- 17.7/15.15 tightened logical-replication checks: MERGE and +-- INSERT ... ON CONFLICT DO UPDATE now also require a REPLICA IDENTITY when the +-- target table is in a publication that publishes updates (previously these two +-- paths could slip through, unlike a plain UPDATE). Pins the post-fix behavior: +-- both error without a replica identity, and succeed once one is set. +-- +-- Refs: PSQL-1110, PSQL-1234. +BEGIN; +CREATE TABLE ri (id int UNIQUE, v int); +CREATE PUBLICATION pub_ri FOR TABLE ri; +INSERT INTO ri VALUES (1, 10); +-- No replica identity yet: both write paths must be rejected. +SAVEPOINT s_upsert; +INSERT INTO ri VALUES (1, 20) ON CONFLICT (id) DO UPDATE SET v = EXCLUDED.v; +ERROR: cannot update table "ri" because it does not have a replica identity and publishes updates +HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE. +ROLLBACK TO SAVEPOINT s_upsert; +SAVEPOINT s_merge; +MERGE INTO ri t USING (SELECT 1 AS id, 30 AS v) s ON t.id = s.id + WHEN MATCHED THEN UPDATE SET v = s.v; +ERROR: cannot update table "ri" because it does not have a replica identity and publishes updates +HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE. +ROLLBACK TO SAVEPOINT s_merge; +-- With a replica identity, both succeed. +ALTER TABLE ri REPLICA IDENTITY FULL; +INSERT INTO ri VALUES (1, 20) ON CONFLICT (id) DO UPDATE SET v = EXCLUDED.v; +MERGE INTO ri t USING (SELECT 1 AS id, 30 AS v) s ON t.id = s.id + WHEN MATCHED THEN UPDATE SET v = s.v; +SELECT v FROM ri WHERE id = 1; + v +---- + 30 +(1 row) + +ROLLBACK; diff --git a/nix/tests/sql/btree_gist_nan.sql b/nix/tests/sql/btree_gist_nan.sql new file mode 100644 index 0000000000..9f17787299 --- /dev/null +++ b/nix/tests/sql/btree_gist_nan.sql @@ -0,0 +1,32 @@ +-- btree_gist NaN handling in the float4/float8 opclasses (comparisons and the +-- GiST penalty/distance functions) previously gave wrong answers when a NaN was +-- present; upstream recommends reindexing btree_gist float indexes that may hold +-- NaN after the update. This pins the post-fix within-version correctness of +-- index scans over a float8 column containing NaN. +-- +-- Upstream commit: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- (The cross-version pre-upgrade-build / post-upgrade-REINDEX leg is A3, +-- PSQL-1235.) +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE EXTENSION IF NOT EXISTS btree_gist; + +CREATE TABLE bg_nan (v float8); +INSERT INTO bg_nan VALUES (1), (2), (3), ('NaN'); +CREATE INDEX bg_nan_gist ON bg_nan USING gist (v); + +-- Force index scans so we exercise the opclass, not a seqscan recheck. +SET enable_seqscan = off; + +-- NaN sorts as greater than every non-NaN value and equals only itself. +SELECT count(*) AS eq_nan FROM bg_nan WHERE v = 'NaN'::float8; +SELECT count(*) AS gt_one FROM bg_nan WHERE v > 1; -- 2, 3, NaN +SELECT count(*) AS ne_two FROM bg_nan WHERE v <> 2; -- 1, 3, NaN +SELECT v FROM bg_nan WHERE v >= 3 ORDER BY v; -- 3, NaN + +RESET enable_seqscan; + +ROLLBACK; diff --git a/nix/tests/sql/create_statistics_priv.sql b/nix/tests/sql/create_statistics_priv.sql new file mode 100644 index 0000000000..0db0775b16 --- /dev/null +++ b/nix/tests/sql/create_statistics_priv.sql @@ -0,0 +1,33 @@ +-- CVE-2025-12817: CREATE STATISTICS did not check CREATE privilege on the +-- schema where the statistics object is created, letting a table owner create +-- statistics objects in any schema (naming-conflict / privilege concern). +-- +-- Upstream commits: 2393d374 + d202ec1f (PG 15.15), e2fb3dfa (PG 17.7). The fix +-- adds a pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE). +-- +-- Verified as a non-superuser table owner. Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE SCHEMA owned_ns; +CREATE SCHEMA forbidden_ns; +-- postgres can create in owned_ns only; it has no rights on forbidden_ns. +GRANT CREATE, USAGE ON SCHEMA owned_ns TO postgres; + +SET ROLE postgres; + +-- A table postgres owns, in a schema postgres controls. +CREATE TABLE owned_ns.stat_tbl (a int, b int); +INSERT INTO owned_ns.stat_tbl SELECT g % 10, g % 5 FROM generate_series(1, 100) g; + +-- Positive control: stats object in owned_ns (postgres has CREATE) is allowed. +CREATE STATISTICS owned_ns.okstat (dependencies) ON a, b FROM owned_ns.stat_tbl; + +-- The fix: a stats object targeting a schema where postgres lacks CREATE is denied. +SAVEPOINT no_priv; +CREATE STATISTICS forbidden_ns.badstat (dependencies) ON a, b FROM owned_ns.stat_tbl; +ROLLBACK TO SAVEPOINT no_priv; + +RESET ROLE; + +ROLLBACK; diff --git a/nix/tests/sql/hstore_copy_binary.sql b/nix/tests/sql/hstore_copy_binary.sql new file mode 100644 index 0000000000..35da4f02cb --- /dev/null +++ b/nix/tests/sql/hstore_copy_binary.sql @@ -0,0 +1,38 @@ +-- Non-CVE behavior change: the hstore receive function had a NULL-pointer +-- dereference (backend crash) on COPY BINARY of an hstore whose binary form +-- contains a DUPLICATE key where the second occurrence's value is NULL. +-- +-- Upstream commits: 63c05e03 (PG 15.x), 0dfbe42d (PG 17.x). +-- +-- A normal INSERT cannot reproduce this: hstore de-duplicates on text input, so +-- a stored value never carries a duplicate key into the binary path. We instead +-- hand-craft a COPY-BINARY stream whose single hstore field contains the pair +-- sequence [ 'a' => '1', 'a' => NULL ] and feed it through hstore_recv via +-- COPY ... FROM. Pre-fix this crashed the backend; on the fixed builds the +-- duplicate is de-duplicated and the row loads cleanly. +-- +-- pg_regress runs as the superuser supabase_admin, so lo_export / server-side +-- COPY FROM a file are permitted. Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE TABLE hstore_dst (h hstore); + +-- Materialise the crafted COPY-BINARY stream to a file (created and exported in +-- separate statements so the large object is visible to lo_export). +SELECT lo_from_bytea(81000, + '\x5047434f50590aff0d0a00'::bytea || -- COPY binary signature + '\x00000000'::bytea || '\x00000000'::bytea || -- flags + header-extension length + '\x0001'::bytea || '\x00000017'::bytea || -- one row, one field of length 23 + '\x00000002'::bytea || -- hstore: 2 pairs + '\x00000001'::bytea||'\x61'::bytea||'\x00000001'::bytea||'\x31'::bytea || -- 'a' => '1' + '\x00000001'::bytea||'\x61'::bytea||'\xffffffff'::bytea || -- 'a' => NULL + '\xffff'::bytea) AS loid; -- COPY trailer +SELECT lo_export(81000, '/tmp/pg_regress_hstore_dup.bin') AS exported; + +-- Must not crash the backend; the duplicate key is de-duplicated on receive. +COPY hstore_dst FROM '/tmp/pg_regress_hstore_dup.bin' WITH (FORMAT binary); + +SELECT h AS received, akeys(h) AS keys FROM hstore_dst; + +ROLLBACK; diff --git a/nix/tests/sql/intarray_ltree_query.sql b/nix/tests/sql/intarray_ltree_query.sql new file mode 100644 index 0000000000..89f35027d2 --- /dev/null +++ b/nix/tests/sql/intarray_ltree_query.sql @@ -0,0 +1,28 @@ +-- CVE-2026-6473: memory-allocation overflow umbrella covering, among others, +-- contrib intarray query_int and contrib ltree ltxtquery / lquery parsing. +-- +-- Upstream key commits: 84a9f264 (intarray/ltree), 9c2fa5b6 (ltree lquery) on +-- PG 15.18; c4d04cc4 and siblings on PG 17.10. Full list: +-- git log REL_17_6..REL_17_10 --grep='CVE-2026-6473' +-- +-- Functional regression: well-formed queries parse and match correctly; a +-- malformed query raises a clean parse error instead of crashing. +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +-- 1) intarray query_int matching. +SELECT '{1,2,3}'::int[] @@ '2&4'::query_int AS q_and; -- expect false +SELECT '{1,2,3}'::int[] @@ '2|4'::query_int AS q_or; -- expect true + +-- 2) ltree lquery and ltxtquery matching. +SELECT 'Top.Science.Astronomy'::ltree ~ 'Top.*.Astronomy'::lquery AS lquery_match; -- true +SELECT 'Top.Science.Astronomy'::ltree @ 'Astronomy & Top'::ltxtquery AS ltxtquery_match; -- true + +-- 3) A malformed query_int must raise a clean parse error, not crash. +SAVEPOINT bad_query; +SELECT '{1}'::int[] @@ '2&&'::query_int; +ROLLBACK TO SAVEPOINT bad_query; + +ROLLBACK; diff --git a/nix/tests/sql/ltree_label_overflow.sql b/nix/tests/sql/ltree_label_overflow.sql new file mode 100644 index 0000000000..c631857446 --- /dev/null +++ b/nix/tests/sql/ltree_label_overflow.sql @@ -0,0 +1,26 @@ +-- contrib/ltree: an integer overflow in ltree comparisons made values with more +-- than about 14,653 labels compare incorrectly; a btree index over such values +-- could become corrupt and need reindexing. This pins the post-fix comparison +-- correctness for very deep ltree values (no index needed: a btree entry that +-- size cannot exist, so this exercises the comparator directly). +-- +-- Upstream commit: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE EXTENSION IF NOT EXISTS ltree; + +-- Build two deep ltree values (~15,000 labels) differing only in the last label. +WITH v AS ( + SELECT (repeat('a.', 15000) || 'x')::ltree AS a, + (repeat('a.', 15000) || 'y')::ltree AS b +) +SELECT nlevel(a) > 14653 AS deep_enough, + a < b AS a_lt_b, + NOT (b < a) AS b_not_lt_a, + a = a AS a_eq_a +FROM v; + +ROLLBACK; diff --git a/nix/tests/sql/ltree_reindex.sql b/nix/tests/sql/ltree_reindex.sql new file mode 100644 index 0000000000..47917670a8 --- /dev/null +++ b/nix/tests/sql/ltree_reindex.sql @@ -0,0 +1,41 @@ +-- Non-CVE behavior change (highest customer blast radius this release: ltree is +-- enabled on 2,245 projects). Two upstream commits fix multibyte handling in +-- ltree's case-insensitive label matching, so GiST indexes built under the old +-- logic must be REINDEXed after upgrade on multibyte / ICU databases: +-- +-- 335b2f30 (PG 15.16) + 2b993167 (PG 15.18) "Fix multibyte issues in ltree" +-- b8cfe9dc (PG 17.8) + d1bd9a7d (PG 17.10) +-- +-- This pins WITHIN-version GiST index correctness + REINDEX idempotence. The +-- cross-version pre-upgrade-build / post-upgrade-REINDEX leg is covered by A3 +-- (pg_upgrade migration tests, PSQL-1235). +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE TABLE ltree_mb (id int, path ltree); +INSERT INTO ltree_mb VALUES + (1, 'Top.Naïve.Café'), + (2, 'Top.Science.Astronomy'), + (3, 'Top.Résumé'); +CREATE INDEX ltree_mb_gist ON ltree_mb USING gist (path); + +SET enable_seqscan = off; + +-- Index search before REINDEX. +SELECT id, path FROM ltree_mb WHERE path ~ 'Top.*'::lquery ORDER BY id; + +-- REINDEX (plain, so it runs inside the transaction) must not corrupt the index. +REINDEX INDEX ltree_mb_gist; + +-- Same search after REINDEX must return the identical set. +SELECT id, path FROM ltree_mb WHERE path ~ 'Top.*'::lquery ORDER BY id; + +-- The '@' label modifier makes the match case-insensitive, which is what +-- invokes the multibyte ltree_strncasecmp path the upstream commits fixed. +SELECT id FROM ltree_mb WHERE path ~ 'top@.*'::lquery ORDER BY id; + +RESET enable_seqscan; + +ROLLBACK; diff --git a/nix/tests/sql/merge_repeatable_read.sql b/nix/tests/sql/merge_repeatable_read.sql new file mode 100644 index 0000000000..18314ad5b2 --- /dev/null +++ b/nix/tests/sql/merge_repeatable_read.sql @@ -0,0 +1,27 @@ +-- Non-CVE behavior change: MERGE now correctly raises a serialization failure +-- (SQLSTATE 40001) under REPEATABLE READ / SERIALIZABLE when it hits a +-- concurrently-updated tuple (previously this could be silently mishandled). +-- +-- This pins the single-session HAPPY PATH only: MERGE under REPEATABLE READ +-- still produces correct results. The actual concurrent-conflict (40001) case +-- needs two concurrent sessions via the isolation tester, tracked in (PSQL-1277) +-- since pg_isolation_regress is not wired into nix/checks.nix yet. +-- +-- Refs: PSQL-1110, PSQL-1234, PSQL-1277. + +BEGIN ISOLATION LEVEL REPEATABLE READ; + +CREATE TABLE merge_target (id int PRIMARY KEY, v int); +CREATE TABLE merge_source (id int, v int); +INSERT INTO merge_target VALUES (1, 10), (2, 20); +INSERT INTO merge_source VALUES (1, 100), (3, 300); + +MERGE INTO merge_target t +USING merge_source s ON t.id = s.id +WHEN MATCHED THEN UPDATE SET v = s.v +WHEN NOT MATCHED THEN INSERT (id, v) VALUES (s.id, s.v); + +-- Expect: id 1 updated to 100, id 2 untouched (20), id 3 inserted (300). +SELECT id, v FROM merge_target ORDER BY id; + +ROLLBACK; diff --git a/nix/tests/sql/multirange_create_priv.sql b/nix/tests/sql/multirange_create_priv.sql new file mode 100644 index 0000000000..ee7b36c5a8 --- /dev/null +++ b/nix/tests/sql/multirange_create_priv.sql @@ -0,0 +1,37 @@ +-- CVE-2026-6472: CREATE TYPE ... AS RANGE auto-creates a companion multirange +-- type. When the multirange type name was given EXPLICITLY, the schema CREATE +-- privilege for that name was not validated (the auto-generated-name path was +-- already checked), letting a role create a multirange type in any schema. +-- +-- Upstream commits: 08c397b0 (PG 15.18), c27ba08c (PG 17.10). The fix adds a +-- pg_namespace_aclcheck(multirangeNamespace, GetUserId(), ACL_CREATE). +-- +-- Verified as a non-superuser. Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE SCHEMA allowed_ns; +CREATE SCHEMA forbidden_ns; +-- postgres gets CREATE on allowed_ns only; it has no rights on forbidden_ns. +GRANT CREATE, USAGE ON SCHEMA allowed_ns TO postgres; + +SET ROLE postgres; + +-- Positive control: explicit multirange name in a schema postgres CAN create in. +CREATE TYPE allowed_ns.okrange AS RANGE ( + subtype = int4, + multirange_type_name = allowed_ns.okmultirange +); + +-- The fix: an explicit multirange name targeting a schema where postgres lacks +-- CREATE must now be denied. +SAVEPOINT no_priv; +CREATE TYPE allowed_ns.badrange AS RANGE ( + subtype = int4, + multirange_type_name = forbidden_ns.badmultirange +); +ROLLBACK TO SAVEPOINT no_priv; + +RESET ROLE; + +ROLLBACK; diff --git a/nix/tests/sql/operator_breaking_change.sql b/nix/tests/sql/operator_breaking_change.sql new file mode 100644 index 0000000000..c1abb33826 --- /dev/null +++ b/nix/tests/sql/operator_breaking_change.sql @@ -0,0 +1,57 @@ +-- Pin CVE-2026-2004 behaviour: attaching a non-built-in selectivity estimator +-- to an operator requires superuser. Verified against both RESTRICT and JOIN. +-- +-- Upstream commits: b764b26f (PG 15.16), bbf5bcf5 (PG 17.8). The check fires in +-- both ValidateRestrictionEstimator() and ValidateJoinEstimator() in +-- src/backend/commands/operatorcmds.c. +-- +-- We use real non-built-in estimators shipped by intarray (_int_matchsel for +-- RESTRICT, _int_overlap_joinsel for JOIN) -- these are exactly the customer- +-- reachable estimators the CVE-2026-2004 fleet-scan query targets. +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +-- A schema the non-superuser controls, so CREATE OPERATOR reaches the estimator +-- validation rather than failing an earlier schema-permission check. +CREATE SCHEMA op_ns; +GRANT CREATE, USAGE ON SCHEMA op_ns TO postgres; + +-- Trivial boolean procedure for the operator (no internal args -> valid in SQL). +CREATE FUNCTION op_ns.fake_op_proc(_int4, _int4) + RETURNS bool LANGUAGE sql IMMUTABLE AS $$ SELECT true $$; + +-- Switch to a non-superuser role. +SET ROLE postgres; + +-- 1) RESTRICT = non-built-in estimator should be rejected. +SAVEPOINT before_restrict; +CREATE OPERATOR op_ns.@@@ ( + LEFTARG = _int4, RIGHTARG = _int4, + PROCEDURE = op_ns.fake_op_proc, + RESTRICT = _int_matchsel +); +ROLLBACK TO SAVEPOINT before_restrict; + +-- 2) JOIN = non-built-in estimator should be rejected. +SAVEPOINT before_join; +CREATE OPERATOR op_ns.@@@ ( + LEFTARG = _int4, RIGHTARG = _int4, + PROCEDURE = op_ns.fake_op_proc, + JOIN = _int_overlap_joinsel +); +ROLLBACK TO SAVEPOINT before_join; + +-- 3) Sanity check: built-in selectivity estimators still work for non-superusers. +CREATE OPERATOR op_ns.@@@ ( + LEFTARG = _int4, RIGHTARG = _int4, + PROCEDURE = op_ns.fake_op_proc, + RESTRICT = eqsel, + JOIN = eqjoinsel +); +DROP OPERATOR op_ns.@@@ (_int4, _int4); + +RESET ROLE; + +ROLLBACK; diff --git a/nix/tests/sql/output_plugin_libraries.sql b/nix/tests/sql/output_plugin_libraries.sql new file mode 100644 index 0000000000..8042b2e413 --- /dev/null +++ b/nix/tests/sql/output_plugin_libraries.sql @@ -0,0 +1,27 @@ +-- CVE-2026-6471: logical decoding could load any library named as an output +-- plugin. PG 15.19 / 17.11 add the "output_plugin_libraries" allowlist GUC; +-- only libraries named there may be used as output plugins. +-- +-- Upstream commit: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- +-- This pins the allowlist enforcement, independent of the exact allowlist value: +-- * an allowlisted, always-shipped plugin (test_decoding) can back a slot; +-- * a non-allowlisted library is rejected at slot-creation time. +-- The image's own allowlist additionally includes wal2json (Realtime); that +-- positive case is covered by wal2json.sql. Requires wal_level = logical, which +-- the image config sets. +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +-- 1) Allowlisted plugin: slot creation succeeds, then clean up. +SELECT slot_name FROM pg_create_logical_replication_slot('opl_ok', 'test_decoding'); +SELECT pg_drop_replication_slot('opl_ok'); + +-- 2) Non-allowlisted library: slot creation must be rejected. +SAVEPOINT s_bad; +SELECT pg_create_logical_replication_slot('opl_bad', 'nonesuch_plugin'); +ROLLBACK TO SAVEPOINT s_bad; + +ROLLBACK; diff --git a/nix/tests/sql/pg_trgm.sql b/nix/tests/sql/pg_trgm.sql new file mode 100644 index 0000000000..4ca484922c --- /dev/null +++ b/nix/tests/sql/pg_trgm.sql @@ -0,0 +1,30 @@ +-- CVE-2026-2006: multibyte length validation via bounds-checked pg_mblen() +-- variants. Affects every multibyte text path, including pg_trgm. +-- +-- Upstream commits: fd82ddb6, 50863be0, b2c81ac8, 8f8b1ffa (PG 15.16); +-- 319e8a64, 7a522039, 838248b1, dc072a09 (PG 17.8). +-- +-- Functional regression: trigram generation, similarity, and GIN index search +-- all return correct results on multibyte (UTF-8) input on the fixed builds. +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +-- 1) show_trgm() on a multibyte (UTF-8) string returns well-formed trigrams. +SELECT show_trgm('café'); + +-- 2) similarity() of two multibyte strings is positive and symmetric. +SELECT similarity('café', 'café') AS self_sim, + similarity('café', 'cafe') = similarity('cafe', 'café') AS symmetric; + +-- 3) A GIN trigram index on multibyte data returns the correct match set. +CREATE TABLE trgm_mb (id int, t text); +INSERT INTO trgm_mb VALUES (1, 'café'), (2, 'naïve'), (3, 'résumé'); +CREATE INDEX trgm_mb_idx ON trgm_mb USING gin (t gin_trgm_ops); + +SET enable_seqscan = off; +SELECT id, t FROM trgm_mb WHERE t % 'café' ORDER BY id; +RESET enable_seqscan; + +ROLLBACK; diff --git a/nix/tests/sql/pgcrypto.sql b/nix/tests/sql/pgcrypto.sql new file mode 100644 index 0000000000..705cec2d6f --- /dev/null +++ b/nix/tests/sql/pgcrypto.sql @@ -0,0 +1,31 @@ +-- CVE-2026-2005: heap buffer overflow in pgcrypto's pgp_*_decrypt_bytea() on an +-- oversized PGP session-key length. The fix hardens the PGP packet-length parser +-- shared by the symmetric and public-key bytea decrypt paths. +-- +-- Upstream commits: 9a9982ec (PG 15.16), 7a7d9693 (PG 17.8). +-- pgcrypto is default-enabled on Supabase, so this path is customer-reachable. +-- +-- This is a functional + crash-safety regression: a valid round-trip still works, +-- and a malformed PGP packet raises a clean SQL error instead of crashing the +-- backend. (The public-key variant needs externally-generated GPG keys, so we +-- exercise the shared packet parser via the symmetric bytea path.) +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +-- 1) Happy path: symmetric PGP bytea round-trip returns the original plaintext. +SELECT pgp_sym_decrypt_bytea( + pgp_sym_encrypt_bytea('\xdeadbeef'::bytea, 'test-key'), + 'test-key') = '\xdeadbeef'::bytea AS roundtrip_ok; + +-- 2) A malformed PGP packet must raise a clean error, not crash the backend +-- (exercises the hardened packet-length parser). +SAVEPOINT malformed; +SELECT pgp_sym_decrypt_bytea('\xdeadbeefcafebabe'::bytea, 'test-key'); +ROLLBACK TO SAVEPOINT malformed; + +-- 3) Backend is still alive and pgcrypto still works after the error. +SELECT pgp_sym_decrypt(pgp_sym_encrypt('still-here', 'k'), 'k') AS post_error_ok; + +ROLLBACK; diff --git a/nix/tests/sql/pgcrypto_cipher_matrix.sql b/nix/tests/sql/pgcrypto_cipher_matrix.sql new file mode 100644 index 0000000000..89714e5bf6 --- /dev/null +++ b/nix/tests/sql/pgcrypto_cipher_matrix.sql @@ -0,0 +1,55 @@ +-- CVE-2026-14663: pgcrypto's PGP functions previously did not detect when the +-- requested cipher was unavailable in the server's OpenSSL build. Encryption +-- then silently produced output that was not actually encrypted, and decryption +-- would succeed even with the wrong key. The fix makes encryption fail loudly +-- when the cipher is unavailable, and makes decryption reject such messages. +-- +-- Upstream commits: (PG 15.19) / (PG 17.11), fixed 2026-08-13. +-- +-- This pins the post-fix contract for the cipher options pgcrypto exposes: +-- * ciphers unavailable in this build (bf/blowfish/cast5) -> encrypt ERRORs +-- rather than silently emitting unencrypted output; +-- * available ciphers (aes*, 3des) round-trip correctly AND reject a wrong +-- passphrase with "Wrong key or corrupt data" (integrity protection intact). +-- The exact set of unavailable ciphers is a function of the OpenSSL build; this +-- suite runs against the image's own OpenSSL, so the assertions below track that +-- build's behavior. +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +-- 1) Unavailable ciphers must fail at encrypt time, not silently pass through. +SAVEPOINT s_bf; +SELECT pgp_sym_encrypt('secret', 'k', 'cipher-algo=bf'); +ROLLBACK TO SAVEPOINT s_bf; + +SAVEPOINT s_blowfish; +SELECT pgp_sym_encrypt('secret', 'k', 'cipher-algo=blowfish'); +ROLLBACK TO SAVEPOINT s_blowfish; + +SAVEPOINT s_cast5; +SELECT pgp_sym_encrypt('secret', 'k', 'cipher-algo=cast5'); +ROLLBACK TO SAVEPOINT s_cast5; + +-- 2) Available ciphers round-trip correctly with the right key. +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes128'), 'k') AS aes128_rt; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes192'), 'k') AS aes192_rt; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes256'), 'k') AS aes256_rt; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=3des'), 'k') AS des3_rt; + +-- 3) The security property: a WRONG passphrase must be rejected, not accepted. +SAVEPOINT s_aes_wrong; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=aes256'), 'WRONG'); +ROLLBACK TO SAVEPOINT s_aes_wrong; + +SAVEPOINT s_des3_wrong; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('secret', 'k', 'cipher-algo=3des'), 'WRONG'); +ROLLBACK TO SAVEPOINT s_des3_wrong; + +-- 4) Backend still healthy after the expected errors. +SELECT pgp_sym_decrypt(pgp_sym_encrypt('still-here', 'k'), 'k') AS post_error_ok; + +ROLLBACK; diff --git a/nix/tests/sql/replica_identity_upsert.sql b/nix/tests/sql/replica_identity_upsert.sql new file mode 100644 index 0000000000..92c5df4a99 --- /dev/null +++ b/nix/tests/sql/replica_identity_upsert.sql @@ -0,0 +1,32 @@ +-- 17.7/15.15 tightened logical-replication checks: MERGE and +-- INSERT ... ON CONFLICT DO UPDATE now also require a REPLICA IDENTITY when the +-- target table is in a publication that publishes updates (previously these two +-- paths could slip through, unlike a plain UPDATE). Pins the post-fix behavior: +-- both error without a replica identity, and succeed once one is set. +-- +-- Refs: PSQL-1110, PSQL-1234. + +BEGIN; + +CREATE TABLE ri (id int UNIQUE, v int); +CREATE PUBLICATION pub_ri FOR TABLE ri; +INSERT INTO ri VALUES (1, 10); + +-- No replica identity yet: both write paths must be rejected. +SAVEPOINT s_upsert; +INSERT INTO ri VALUES (1, 20) ON CONFLICT (id) DO UPDATE SET v = EXCLUDED.v; +ROLLBACK TO SAVEPOINT s_upsert; + +SAVEPOINT s_merge; +MERGE INTO ri t USING (SELECT 1 AS id, 30 AS v) s ON t.id = s.id + WHEN MATCHED THEN UPDATE SET v = s.v; +ROLLBACK TO SAVEPOINT s_merge; + +-- With a replica identity, both succeed. +ALTER TABLE ri REPLICA IDENTITY FULL; +INSERT INTO ri VALUES (1, 20) ON CONFLICT (id) DO UPDATE SET v = EXCLUDED.v; +MERGE INTO ri t USING (SELECT 1 AS id, 30 AS v) s ON t.id = s.id + WHEN MATCHED THEN UPDATE SET v = s.v; +SELECT v FROM ri WHERE id = 1; + +ROLLBACK;