diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out
index a75548a3887..ae1c57552b6 100644
--- a/contrib/amcheck/expected/check_btree.out
+++ b/contrib/amcheck/expected/check_btree.out
@@ -114,7 +114,8 @@ WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx
COMMIT;
--
--- Check that index expressions and predicates are run as the table's owner
+-- Check that index expressions and predicates are run as the table's owner,
+-- with empty search_path
--
TRUNCATE bttest_a;
INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
@@ -122,15 +123,48 @@ ALTER TABLE bttest_a OWNER TO regress_bttest_role;
-- A dummy index function checking current_user
CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
BEGIN
- IF current_user <> 'regress_bttest_role'
- THEN RAISE EXCEPTION 'ifun(%s) called by %s', $1, current_user;
- END IF;
+ ASSERT current_setting('search_path') NOT LIKE '%preempt%',
+ format('ifun(%s) called with current_schemas %s, search_path %s',
+ $1, current_schemas(true), current_setting('search_path'));
+ ASSERT "current_user"() = 'regress_bttest_role',
+ format('ifun(%s) called by %s', $1, current_user);
RETURN $1;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
WHERE ifun(id + 10) > ifun(10);
-SELECT bt_index_check('bttest_a_expr_idx');
+BEGIN;
+SET LOCAL check_function_bodies = off;
+CREATE SCHEMA preempt;
+GRANT USAGE ON SCHEMA preempt TO regress_bttest_role;
+SET LOCAL search_path = preempt, pg_catalog, public;
+CREATE FUNCTION "current_user"() RETURNS name AS $$
+ broken
+$$ LANGUAGE sql STABLE PARALLEL SAFE STRICT;
+SELECT bt_index_check('bttest_a_expr_idx', true);
+ bt_index_check
+----------------
+
+(1 row)
+
+ROLLBACK;
+-- Check support of both 1B and 4B header sizes of short varlena datum
+CREATE TABLE varlena_bug (v text);
+ALTER TABLE varlena_bug ALTER column v SET storage plain;
+INSERT INTO varlena_bug VALUES ('x');
+COPY varlena_bug from stdin;
+CREATE INDEX varlena_bug_idx on varlena_bug(v);
+SELECT bt_index_check('varlena_bug_idx', true);
+ bt_index_check
+----------------
+
+(1 row)
+
+-- Also check that we compress varlena values, which were previously stored
+-- uncompressed in index.
+INSERT INTO varlena_bug VALUES (repeat('Test', 250));
+ALTER TABLE varlena_bug ALTER COLUMN v SET STORAGE extended;
+SELECT bt_index_check('varlena_bug_idx', true);
bt_index_check
----------------
diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql
index 6a037169006..2ee91d952ed 100644
--- a/contrib/amcheck/sql/check_btree.sql
+++ b/contrib/amcheck/sql/check_btree.sql
@@ -61,7 +61,8 @@ WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx
COMMIT;
--
--- Check that index expressions and predicates are run as the table's owner
+-- Check that index expressions and predicates are run as the table's owner,
+-- with empty search_path
--
TRUNCATE bttest_a;
INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
@@ -69,17 +70,43 @@ ALTER TABLE bttest_a OWNER TO regress_bttest_role;
-- A dummy index function checking current_user
CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
BEGIN
- IF current_user <> 'regress_bttest_role'
- THEN RAISE EXCEPTION 'ifun(%s) called by %s', $1, current_user;
- END IF;
+ ASSERT current_setting('search_path') NOT LIKE '%preempt%',
+ format('ifun(%s) called with current_schemas %s, search_path %s',
+ $1, current_schemas(true), current_setting('search_path'));
+ ASSERT "current_user"() = 'regress_bttest_role',
+ format('ifun(%s) called by %s', $1, current_user);
RETURN $1;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
WHERE ifun(id + 10) > ifun(10);
+BEGIN;
+SET LOCAL check_function_bodies = off;
+CREATE SCHEMA preempt;
+GRANT USAGE ON SCHEMA preempt TO regress_bttest_role;
+SET LOCAL search_path = preempt, pg_catalog, public;
+CREATE FUNCTION "current_user"() RETURNS name AS $$
+ broken
+$$ LANGUAGE sql STABLE PARALLEL SAFE STRICT;
+SELECT bt_index_check('bttest_a_expr_idx', true);
+ROLLBACK;
-SELECT bt_index_check('bttest_a_expr_idx');
+-- Check support of both 1B and 4B header sizes of short varlena datum
+CREATE TABLE varlena_bug (v text);
+ALTER TABLE varlena_bug ALTER column v SET storage plain;
+INSERT INTO varlena_bug VALUES ('x');
+COPY varlena_bug from stdin;
+x
+\.
+CREATE INDEX varlena_bug_idx on varlena_bug(v);
+SELECT bt_index_check('varlena_bug_idx', true);
+
+-- Also check that we compress varlena values, which were previously stored
+-- uncompressed in index.
+INSERT INTO varlena_bug VALUES (repeat('Test', 250));
+ALTER TABLE varlena_bug ALTER COLUMN v SET STORAGE extended;
+SELECT bt_index_check('varlena_bug_idx', true);
-- cleanup
DROP TABLE bttest_a;
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 900ed20eeb2..7de0d3a6684 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -232,6 +232,8 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed)
SetUserIdAndSecContext(heaprel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ set_config_option("search_path", "pg_catalog, pg_temp", PGC_USERSET,
+ PGC_S_SESSION, GUC_ACTION_SAVE, true, 0);
}
else
{
diff --git a/contrib/fuzzystrmatch/fuzzystrmatch.c b/contrib/fuzzystrmatch/fuzzystrmatch.c
index 7a53d8a008e..8979694e029 100644
--- a/contrib/fuzzystrmatch/fuzzystrmatch.c
+++ b/contrib/fuzzystrmatch/fuzzystrmatch.c
@@ -167,6 +167,20 @@ rest_of_char_same(const char *s1, const char *s2, int len)
return true;
}
+/*
+ * Helper function for checking return value of Levenshtein distance functions.
+ * We calculate it as an int64, but the distance functions return an int32.
+ */
+static inline int
+levenshtein_result(int64 res)
+{
+ if (unlikely(res < PG_INT32_MIN || res > PG_INT32_MAX))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("levenshtein distance out of range")));
+ return res;
+}
+
#include "levenshtein.c"
#define LEVENSHTEIN_LESS_EQUAL
#include "levenshtein.c"
diff --git a/contrib/fuzzystrmatch/levenshtein.c b/contrib/fuzzystrmatch/levenshtein.c
index bf741c16b35..e65e68f6040 100644
--- a/contrib/fuzzystrmatch/levenshtein.c
+++ b/contrib/fuzzystrmatch/levenshtein.c
@@ -76,14 +76,17 @@ levenshtein_internal(text *s, text *t,
n,
s_bytes,
t_bytes;
- int *prev;
- int *curr;
+ int64 *prev;
+ int64 *curr;
int *s_char_len = NULL;
int i,
j;
const char *s_data;
const char *t_data;
const char *y;
+ int64 ins_c_64 = ins_c;
+ int64 del_c_64 = del_c;
+ int64 sub_c_64 = sub_c;
/*
* For levenshtein_less_equal_internal, we have real variables called
@@ -120,9 +123,9 @@ levenshtein_internal(text *s, text *t,
* into an empty s with m deletions.
*/
if (!m)
- return n * ins_c;
+ return levenshtein_result(n * ins_c_64);
if (!n)
- return m * del_c;
+ return levenshtein_result(m * del_c_64);
/*
* For security concerns, restrict excessive CPU+RAM usage. (This
@@ -148,20 +151,20 @@ levenshtein_internal(text *s, text *t,
*/
if (max_d >= 0)
{
- int min_theo_d; /* Theoretical minimum distance. */
- int max_theo_d; /* Theoretical maximum distance. */
+ int64 min_theo_d; /* Theoretical minimum distance. */
+ int64 max_theo_d; /* Theoretical maximum distance. */
int net_inserts = n - m;
min_theo_d = net_inserts < 0 ?
- -net_inserts * del_c : net_inserts * ins_c;
+ -net_inserts * del_c_64 : net_inserts * ins_c_64;
if (min_theo_d > max_d)
- return max_d + 1;
- if (ins_c + del_c < sub_c)
- sub_c = ins_c + del_c;
- max_theo_d = min_theo_d + sub_c * Min(m, n);
+ return levenshtein_result((int64) max_d + 1);
+ if (ins_c_64 + del_c_64 < sub_c_64)
+ sub_c_64 = ins_c_64 + del_c_64;
+ max_theo_d = min_theo_d + sub_c_64 * Min(m, n);
if (max_d >= max_theo_d)
max_d = -1;
- else if (ins_c + del_c > 0)
+ else if (ins_c_64 + del_c_64 > 0)
{
/*
* Figure out how much of the first row of the notional matrix we
@@ -175,12 +178,12 @@ levenshtein_internal(text *s, text *t,
* column n - m. If we do start further right, the best-case
* total cost increases by ins_c + del_c for each move right.
*/
- int slack_d = max_d - min_theo_d;
+ int64 slack_d = max_d - min_theo_d;
int best_column = net_inserts < 0 ? -net_inserts : 0;
+ int64 tmp;
- stop_column = best_column + (slack_d / (ins_c + del_c)) + 1;
- if (stop_column > m)
- stop_column = m + 1;
+ tmp = best_column + (slack_d / (ins_c_64 + del_c_64)) + 1;
+ stop_column = Min(tmp, m + 1);
}
}
#endif
@@ -212,7 +215,7 @@ levenshtein_internal(text *s, text *t,
++n;
/* Previous and current rows of notional array. */
- prev = (int *) palloc(2 * m * sizeof(int));
+ prev = (int64 *) palloc(2 * m * sizeof(int64));
curr = prev + m;
/*
@@ -220,12 +223,12 @@ levenshtein_internal(text *s, text *t,
* t, we must perform i deletions.
*/
for (i = START_COLUMN; i < STOP_COLUMN; i++)
- prev[i] = i * del_c;
+ prev[i] = i * del_c_64;
/* Loop through rows of the notional array */
for (y = t_data, j = 1; j < n; j++)
{
- int *temp;
+ int64 *temp;
const char *x = s_data;
int y_char_len = n != t_bytes + 1 ? pg_mblen(y) : 1;
@@ -239,7 +242,7 @@ levenshtein_internal(text *s, text *t,
*/
if (stop_column < m)
{
- prev[stop_column] = max_d + 1;
+ prev[stop_column] = (int64) max_d + 1;
++stop_column;
}
@@ -251,13 +254,13 @@ levenshtein_internal(text *s, text *t,
*/
if (start_column == 0)
{
- curr[0] = j * ins_c;
+ curr[0] = j * ins_c_64;
i = 1;
}
else
i = start_column;
#else
- curr[0] = j * ins_c;
+ curr[0] = j * ins_c_64;
i = 1;
#endif
@@ -272,9 +275,9 @@ levenshtein_internal(text *s, text *t,
{
for (; i < STOP_COLUMN; i++)
{
- int ins;
- int del;
- int sub;
+ int64 ins;
+ int64 del;
+ int64 sub;
int x_char_len = s_char_len[i - 1];
/*
@@ -286,14 +289,14 @@ levenshtein_internal(text *s, text *t,
* get past that test, then we compare the lengths and the
* remaining bytes.
*/
- ins = prev[i] + ins_c;
- del = curr[i - 1] + del_c;
+ ins = prev[i] + ins_c_64;
+ del = curr[i - 1] + del_c_64;
if (x[x_char_len - 1] == y[y_char_len - 1]
&& x_char_len == y_char_len &&
(x_char_len == 1 || rest_of_char_same(x, y, x_char_len)))
sub = prev[i - 1];
else
- sub = prev[i - 1] + sub_c;
+ sub = prev[i - 1] + sub_c_64;
/* Take the one with minimum cost. */
curr[i] = Min(ins, del);
@@ -307,14 +310,14 @@ levenshtein_internal(text *s, text *t,
{
for (; i < STOP_COLUMN; i++)
{
- int ins;
- int del;
- int sub;
+ int64 ins;
+ int64 del;
+ int64 sub;
/* Calculate costs for insertion, deletion, and substitution. */
- ins = prev[i] + ins_c;
- del = curr[i - 1] + del_c;
- sub = prev[i - 1] + ((*x == *y) ? 0 : sub_c);
+ ins = prev[i] + ins_c_64;
+ del = curr[i - 1] + del_c_64;
+ sub = prev[i - 1] + ((*x == *y) ? 0 : sub_c_64);
/* Take the one with minimum cost. */
curr[i] = Min(ins, del);
@@ -360,8 +363,8 @@ levenshtein_internal(text *s, text *t,
int ii = stop_column - 1;
int net_inserts = ii - zp;
- if (prev[ii] + (net_inserts > 0 ? net_inserts * ins_c :
- -net_inserts * del_c) <= max_d)
+ if (prev[ii] + (net_inserts > 0 ? net_inserts * ins_c_64 :
+ -net_inserts * del_c_64) <= max_d)
break;
stop_column--;
}
@@ -372,8 +375,8 @@ levenshtein_internal(text *s, text *t,
int net_inserts = start_column - zp;
if (prev[start_column] +
- (net_inserts > 0 ? net_inserts * ins_c :
- -net_inserts * del_c) <= max_d)
+ (net_inserts > 0 ? net_inserts * ins_c_64 :
+ -net_inserts * del_c_64) <= max_d)
break;
/*
@@ -381,8 +384,8 @@ levenshtein_internal(text *s, text *t,
* there's nothing here that could confuse any future
* iteration of the outer loop.
*/
- prev[start_column] = max_d + 1;
- curr[start_column] = max_d + 1;
+ prev[start_column] = (int64) max_d + 1;
+ curr[start_column] = (int64) max_d + 1;
if (start_column != 0)
s_data += (s_char_len != NULL) ? s_char_len[start_column - 1] : 1;
start_column++;
@@ -390,7 +393,7 @@ levenshtein_internal(text *s, text *t,
/* If they cross, we're going to exceed the bound. */
if (start_column >= stop_column)
- return max_d + 1;
+ return levenshtein_result((int64) max_d + 1);
}
#endif
}
@@ -399,5 +402,5 @@ levenshtein_internal(text *s, text *t,
* Because the final value was swapped from the previous row to the
* current row, that's where we'll find it.
*/
- return prev[m - 1];
+ return levenshtein_result(prev[m - 1]);
}
diff --git a/contrib/pg_trgm/trgm_gist.c b/contrib/pg_trgm/trgm_gist.c
index 69dc7f71f07..c08ec758631 100644
--- a/contrib/pg_trgm/trgm_gist.c
+++ b/contrib/pg_trgm/trgm_gist.c
@@ -877,10 +877,10 @@ gtrgm_picksplit(PG_FUNCTION_ARGS)
{
if (ISALLTRUE(datum_l) && cache[j].allistrue)
size_alpha = 0;
- else
- size_alpha = SIGLENBIT - sizebitvec(
- (cache[j].allistrue) ? GETSIGN(datum_l) : GETSIGN(cache[j].sign)
- );
+ else
+ size_alpha = SIGLENBIT - sizebitvec(
+ (cache[j].allistrue) ? GETSIGN(datum_l) : cache[j].sign
+ );
}
else
size_alpha = hemdistsign(cache[j].sign, GETSIGN(datum_l));
@@ -889,10 +889,10 @@ gtrgm_picksplit(PG_FUNCTION_ARGS)
{
if (ISALLTRUE(datum_r) && cache[j].allistrue)
size_beta = 0;
- else
- size_beta = SIGLENBIT - sizebitvec(
- (cache[j].allistrue) ? GETSIGN(datum_r) : GETSIGN(cache[j].sign)
- );
+ else
+ size_beta = SIGLENBIT - sizebitvec(
+ (cache[j].allistrue) ? GETSIGN(datum_r) : cache[j].sign
+ );
}
else
size_beta = hemdistsign(cache[j].sign, GETSIGN(datum_r));
diff --git a/contrib/pgcrypto/expected/pgp-decrypt.out b/contrib/pgcrypto/expected/pgp-decrypt.out
index 2dabfaf7b0e..9e15a523c4c 100755
--- a/contrib/pgcrypto/expected/pgp-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt.out
@@ -423,3 +423,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
'), 'key', 'debug=1');
NOTICE: dbg: parse_compressed_data: bzip2 unsupported
ERROR: Unsupported compression algorithm
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+ERROR: Wrong key or corrupt data
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
+ERROR: Wrong key or corrupt data
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 00000000000..ad63df15707
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,200 @@
+--
+-- PGP encrypt
+--
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=aes128,
+ expect-disable-mdc=0,
+ expect-sess-key=0,
+ expect-s2k-mode=3,
+ expect-s2k-digest-algo=sha1,
+ expect-compress-algo=0
+ ');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=bf,
+ expect-disable-mdc=1,
+ expect-sess-key=1,
+ expect-s2k-mode=0,
+ expect-s2k-digest-algo=md5,
+ expect-compress-algo=1
+ ');
+NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR: Not text data
+-- text as bytea
+select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape');
+ encode
+--------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+ 'key', 'expect-cipher-algo=bf');
+ERROR: encrypt error: Cipher cannot be initialized
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+ 'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+ 'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+ 'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+ 'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+ 'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 'key', 'expect-s2k-count=65000000');
+NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
+ 'key', 'expect-s2k-digest-algo=md5');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+ 'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+ 'key', 'expect-sess-key=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+ 'key', 'expect-sess-key=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR: encrypt error: Cipher cannot be initialized
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+ 'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key');
+ pgp_sym_decrypt_bytea
+------------------------
+ \x310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select digest(pgp_sym_decrypt(
+ pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+ 'key', 'convert-crlf=1'), 'sha1') as result,
+ digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect;
+ result | expect
+--------------------------------------------+--------------------------------------------
+ \x47bde5d88d6ef8770572b9cbb4278b402aa69966 | \x47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-info.out b/contrib/pgcrypto/expected/pgp-info.out
index 90648383730..909e7f7851e 100755
--- a/contrib/pgcrypto/expected/pgp-info.out
+++ b/contrib/pgcrypto/expected/pgp-info.out
@@ -75,5 +75,6 @@ from encdata order by id;
B68504FD128E1FF9
FD0206C409B74875
FD0206C409B74875
-(5 rows)
+ D936CF64BB73F466
+(6 rows)
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
index b4b6810a3c5..d3bb5f1b06d 100755
--- a/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
@@ -585,6 +585,20 @@ blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
=PHJ1
-----END PGP MESSAGE-----
');
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
-- successful decrypt
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -600,6 +614,13 @@ from keytbl, encdata where keytbl.id=2 and encdata.id=2;
Secret msg
(1 row)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ pgp_pub_decrypt
+-----------------
+ Secret msg
+(1 row)
+
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=3 and encdata.id=3;
pgp_pub_decrypt
@@ -654,3 +675,12 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=6 and encdata.id=5;
ERROR: Wrong key or corrupt data
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR: Wrong key or corrupt data
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR: Wrong key or corrupt data
diff --git a/contrib/pgcrypto/pgp-cfb.c b/contrib/pgcrypto/pgp-cfb.c
index 1d99915f9db..af91672a22a 100644
--- a/contrib/pgcrypto/pgp-cfb.c
+++ b/contrib/pgcrypto/pgp-cfb.c
@@ -43,6 +43,7 @@ struct PGP_CFB
int pos;
int block_no;
int resync;
+ int ignore_decrypt_cipher_failure; /* for CVE-2026-14663 recovery */
uint8 fr[PGP_MAX_BLOCK];
uint8 fre[PGP_MAX_BLOCK];
uint8 encbuf[PGP_MAX_BLOCK];
@@ -50,7 +51,7 @@ struct PGP_CFB
int
pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, int key_len,
- int resync, uint8 *iv)
+ int resync, uint8 *iv, int ignore_decrypt_cipher_failure)
{
int res;
PX_Cipher *ciph;
@@ -72,6 +73,7 @@ pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, int key_len,
ctx->ciph = ciph;
ctx->block_size = px_cipher_block_size(ciph);
ctx->resync = resync;
+ ctx->ignore_decrypt_cipher_failure = ignore_decrypt_cipher_failure;
if (iv)
memcpy(ctx->fr, iv, ctx->block_size);
@@ -196,7 +198,7 @@ mix_decrypt_resync(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst)
*/
static int
cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst,
- mix_data_t mix_data)
+ mix_data_t mix_data, int ignore_cipher_failure)
{
int n;
int res;
@@ -221,7 +223,22 @@ cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst,
while (len > 0)
{
- px_cipher_encrypt(ctx->ciph, ctx->fr, ctx->block_size, ctx->fre);
+ int err;
+
+ err = px_cipher_encrypt(ctx->ciph, ctx->fr, ctx->block_size, ctx->fre);
+
+ /*
+ * XXX Ignoring cipher failures is dangerous, but we allow it during
+ * decryption to return to the behavior prior to the fix for
+ * CVE-2026-14663. This lets users recover data from a badly-encrypted
+ * message.
+ */
+ if (err && !ignore_cipher_failure)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
+ errmsg("encrypt error: %s", px_strerror(err))));
+
+
if (ctx->block_no < 5)
ctx->block_no++;
@@ -252,7 +269,8 @@ pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst)
{
mix_data_t mix = ctx->resync ? mix_encrypt_resync : mix_encrypt_normal;
- return cfb_process(ctx, data, len, dst, mix);
+ return cfb_process(ctx, data, len, dst, mix,
+ 0 /* never ignore cipher failures for encrypt */ );
}
int
@@ -260,5 +278,6 @@ pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst)
{
mix_data_t mix = ctx->resync ? mix_decrypt_resync : mix_decrypt_normal;
- return cfb_process(ctx, data, len, dst, mix);
+ return cfb_process(ctx, data, len, dst, mix,
+ ctx->ignore_decrypt_cipher_failure);
}
diff --git a/contrib/pgcrypto/pgp-decrypt.c b/contrib/pgcrypto/pgp-decrypt.c
index 30c2caead09..f8e6404d4dd 100644
--- a/contrib/pgcrypto/pgp-decrypt.c
+++ b/contrib/pgcrypto/pgp-decrypt.c
@@ -595,7 +595,8 @@ decrypt_key(PGP_Context *ctx, const uint8 *src, int len)
PGP_CFB *cfb;
res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo,
- ctx->s2k.key, ctx->s2k.key_len, 0, NULL);
+ ctx->s2k.key, ctx->s2k.key_len, 0, NULL,
+ ctx->ignore_cipher_failure);
if (res < 0)
return res;
@@ -979,7 +980,8 @@ parse_symenc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst)
PullFilter *pf_prefix = NULL;
res = pgp_cfb_create(&cfb, ctx->cipher_algo,
- ctx->sess_key, ctx->sess_key_len, 1, NULL);
+ ctx->sess_key, ctx->sess_key_len, 1, NULL,
+ ctx->ignore_cipher_failure);
if (res < 0)
goto out;
@@ -1022,7 +1024,8 @@ parse_symenc_mdc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst)
}
res = pgp_cfb_create(&cfb, ctx->cipher_algo,
- ctx->sess_key, ctx->sess_key_len, 0, NULL);
+ ctx->sess_key, ctx->sess_key_len, 0, NULL,
+ ctx->ignore_cipher_failure);
if (res < 0)
goto out;
diff --git a/contrib/pgcrypto/pgp-encrypt.c b/contrib/pgcrypto/pgp-encrypt.c
index 5556fb6c8e8..f31c5da00c3 100644
--- a/contrib/pgcrypto/pgp-encrypt.c
+++ b/contrib/pgcrypto/pgp-encrypt.c
@@ -175,7 +175,8 @@ encrypt_init(PushFilter *next, void *init_arg, void **priv_p)
return res;
}
res = pgp_cfb_create(&ciph, ctx->cipher_algo,
- ctx->sess_key, ctx->sess_key_len, resync, NULL);
+ ctx->sess_key, ctx->sess_key_len, resync, NULL,
+ 0 /* never ignore cipher failures for encrypt */ );
if (res < 0)
return res;
@@ -505,7 +506,8 @@ symencrypt_sesskey(PGP_Context *ctx, uint8 *dst)
uint8 algo = ctx->cipher_algo;
res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo,
- ctx->s2k.key, ctx->s2k.key_len, 0, NULL);
+ ctx->s2k.key, ctx->s2k.key_len, 0, NULL,
+ 0 /* never ignore cipher failures for encrypt */ );
if (res < 0)
return res;
diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c
index 41f341539c5..cafa9cdc81d 100644
--- a/contrib/pgcrypto/pgp-pgsql.c
+++ b/contrib/pgcrypto/pgp-pgsql.c
@@ -182,6 +182,8 @@ set_arg(PGP_Context *ctx, char *key, char *val,
res = pgp_set_convert_crlf(ctx, atoi(val));
else if (strcmp(key, "unicode-mode") == 0)
res = pgp_set_unicode_mode(ctx, atoi(val));
+ else if (strcmp(key, "ignore-cipher-failure") == 0)
+ res = pgp_set_ignore_cipher_failure(ctx, atoi(val));
/*
* The remaining options are for debugging/testing and are therefore not
* documented in the user-facing docs.
diff --git a/contrib/pgcrypto/pgp-pubkey.c b/contrib/pgcrypto/pgp-pubkey.c
index f898d72ae99..bcf5f4d8a61 100644
--- a/contrib/pgcrypto/pgp-pubkey.c
+++ b/contrib/pgcrypto/pgp-pubkey.c
@@ -383,8 +383,15 @@ process_secret_key(PullFilter *pkt, PGP_PubKey **pk_p,
/*
* create decrypt filter
+ *
+ * ignore-cipher-failure doesn't apply here; pgcrypto didn't encrypt
+ * the secret key to begin with, and any stored encrypted data was
+ * generated using the public key, so users don't have a reason to
+ * want to incorrectly decrypt this. We'll ignore failures during
+ * decryption with the session key, instead.
*/
- res = pgp_cfb_create(&cfb, cipher_algo, s2k.key, s2k.key_len, 0, iv);
+ res = pgp_cfb_create(&cfb, cipher_algo, s2k.key, s2k.key_len, 0, iv,
+ 0 /* don't ignore cipher failures */ );
if (res < 0)
return res;
res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt);
diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c
index fb614b9c0cb..1c44287b445 100644
--- a/contrib/pgcrypto/pgp.c
+++ b/contrib/pgcrypto/pgp.c
@@ -48,6 +48,7 @@ static int def_use_sess_key = 0;
static int def_text_mode = 0;
static int def_unicode_mode = 0;
static int def_convert_crlf = 0;
+static int def_ignore_cipher_failure = 0;
struct digest_info
{
@@ -236,6 +237,7 @@ pgp_init(PGP_Context **ctx_p)
ctx->unicode_mode = def_unicode_mode;
ctx->convert_crlf = def_convert_crlf;
ctx->text_mode = def_text_mode;
+ ctx->ignore_cipher_failure = def_ignore_cipher_failure;
*ctx_p = ctx;
return 0;
@@ -370,6 +372,13 @@ pgp_set_unicode_mode(PGP_Context *ctx, int mode)
return 0;
}
+int
+pgp_set_ignore_cipher_failure(PGP_Context *ctx, int ignore)
+{
+ ctx->ignore_cipher_failure = ignore ? 1 : 0;
+ return 0;
+}
+
int
pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int len)
{
diff --git a/contrib/pgcrypto/pgp.h b/contrib/pgcrypto/pgp.h
index 762bf2e441b..d4ecf1b6aec 100644
--- a/contrib/pgcrypto/pgp.h
+++ b/contrib/pgcrypto/pgp.h
@@ -147,6 +147,9 @@ struct PGP_Context
int convert_crlf;
int unicode_mode;
+ /* DANGEROUS recovery aid for CVE-2026-14663. Applies only to decryption. */
+ int ignore_cipher_failure;
+
/*
* internal variables
*/
@@ -251,6 +254,7 @@ int pgp_set_compress_level(PGP_Context *ctx, int level);
int pgp_set_text_mode(PGP_Context *ctx, int mode);
int pgp_set_unicode_mode(PGP_Context *ctx, int mode);
int pgp_get_unicode_mode(PGP_Context *ctx);
+int pgp_set_ignore_cipher_failure(PGP_Context *ctx, int ignore);
int pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int klen);
int pgp_set_pubkey(PGP_Context *ctx, MBuf *keypkt,
@@ -270,8 +274,9 @@ int pgp_s2k_read(PullFilter *src, PGP_S2K *s2k);
int pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int klen);
typedef struct PGP_CFB PGP_CFB;
-int pgp_cfb_create(PGP_CFB **ctx_p, int algo,
- const uint8 *key, int key_len, int recync, uint8 *iv);
+int pgp_cfb_create(PGP_CFB **ctx_p, int algo,
+ const uint8 *key, int key_len, int resync, uint8 *iv,
+ int ignore_decrypt_cipher_failure);
void pgp_cfb_free(PGP_CFB *ctx);
int pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
int pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
diff --git a/contrib/pgcrypto/sql/pgp-decrypt.sql b/contrib/pgcrypto/sql/pgp-decrypt.sql
index f46a18f8cfd..e66c94a5908 100644
--- a/contrib/pgcrypto/sql/pgp-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-decrypt.sql
@@ -313,3 +313,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
=AZ9M
-----END PGP MESSAGE-----
'), 'key', 'debug=1');
+
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
diff --git a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
index 3f2bae9e40b..40a11e0b2dc 100644
--- a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
@@ -601,6 +601,21 @@ blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
-----END PGP MESSAGE-----
');
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
+
-- successful decrypt
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -608,6 +623,9 @@ from keytbl, encdata where keytbl.id=1 and encdata.id=1;
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=3 and encdata.id=3;
@@ -645,3 +663,12 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
-- test for a short read from prefix_init
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 7ab7298ae72..45c6c23e4be 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -877,6 +877,38 @@ Applies to: pgp_sym_encrypt
Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
+
+
+
+
+ ignore-cipher-failure
+
+
+ Dangerous! Instructs pgcrypto to use an incorrect decryption algorithm
+ matching the historical behavior prior to the fix for CVE-2026-14663, by
+ completely ignoring failures from the OpenSSL cipher in use. This is
+ intended only for users who need to recover incorrectly-encrypted messages
+ created when the cipher-algo was unavailable under the
+ OpenSSL configuration in use. Such faulty messages do not require the
+ correct decryption key when ignore-cipher-failure is
+ enabled, so there is no guarantee that the decrypted plaintext actually
+ originated from a holder of the key.
+
+
+ Contrast the case of a message which was correctly encrypted, but the cipher
+ that produced it is unavailable under the current OpenSSL
+ configuration. Recovering such plaintext via pgcrypto
+ requires making the actual cipher available to OpenSSL by, for example,
+ enabling the appropriate provider. ignore-cipher-failure
+ is not necessary or helpful for that scenario. If decryption
+ of a correctly encrypted message with this option happens to pass PGP
+ integrity checks, that result is coincidental and does not make the
+ recovered plaintext trustworthy.
+
+
+Values: 0, 1
+Default: 0
+Applies to: pgp_sym_decrypt, pgp_pub_decrypt
diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml
index 44da24a690e..139de3a9a3c 100755
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -176,6 +176,11 @@ CREATE TYPE name
the range type. See for more
information.
+
+
+ To be able to create a range type, you must have USAGE
+ privilege on the subtype.
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 84b18e83fcd..bbff697bd4e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -19267,12 +19267,17 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
ObjectAddress tableobj,
typeobj;
HeapTuple classtuple;
+ AclResult aclresult;
/* Validate the type. */
typetuple = typenameType(NULL, ofTypename, NULL);
check_of_type(typetuple);
typeid = HeapTupleGetOid(typetuple);
+ aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typeid);
+
/* Fail if the table has any inheritance parents. */
inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
ScanKeyInit(&key,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c67c5a62c5d..c95b5e5c2b8 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1528,6 +1528,10 @@ DefineRange(CreateRangeStmt *stmt)
errmsg("range subtype cannot be %s",
format_type_be(rangeSubtype))));
+ aclresult = pg_type_aclcheck(rangeSubtype, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, rangeSubtype);
+
/* Identify subopclass */
rangeSubOpclass = findRangeSubOpclass(rangeSubOpclassName, rangeSubtype);
diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 6d08758029f..1e7bf191b53 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -1036,23 +1036,24 @@ setup_regexp_matches(text *orig_str, text *pattern, text *flags,
if (eml > 1)
{
- int64 maxsiz = eml * (int64) maxlen;
int conv_bufsiz;
/*
* Make the conversion buffer large enough for any substring of
- * interest.
+ * interest. We can't use the original string's byte length as a
+ * tighter bound, because that assumes the input is validly encoded;
+ * but pg_mb2wchar_with_len() can accept strings that are invalid in
+ * the database encoding, and converting such a character back to
+ * multibyte form can take more bytes than it did in the input.
*
- * Worst case: assume we need the maximum size (maxlen*eml), but take
- * advantage of the fact that the original string length in bytes is an
- * upper bound on the byte length of any fetched substring (and we know
- * that len+1 is safe to allocate because the varlena header is longer
- * than 1 byte).
+ * This can't overflow, nor exceed what palloc will accept: maxlen is
+ * at most wide_len, which is at most orig_len, and we have already
+ * successfully allocated (orig_len + 1) * sizeof(pg_wchar) bytes for
+ * wide_str. That relies on eml being no more than sizeof(pg_wchar),
+ * which is true of all supported encodings.
*/
- if (maxsiz > orig_len)
- conv_bufsiz = orig_len + 1;
- else
- conv_bufsiz = maxsiz + 1; /* safe since maxsiz < 2^30 */
+ Assert(eml <= sizeof(pg_wchar));
+ conv_bufsiz = maxlen * eml + 1;
matchctx->conv_buf = palloc(conv_bufsiz);
matchctx->conv_bufsiz = conv_bufsiz;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 8468721904e..d96e6fc074e 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -807,6 +807,9 @@ CREATE TABLE test5a (a int, b testdomain1);
ERROR: permission denied for type testdomain1
CREATE TABLE test6a OF testtype1;
ERROR: permission denied for type testtype1
+CREATE TABLE test6a2 (a int, b text);
+ALTER TABLE test6a2 OF testtype1;
+ERROR: permission denied for type testtype1
CREATE TABLE test10a (a int[], b testtype1[]);
ERROR: permission denied for type testtype1
CREATE TABLE test9a (a int, b int);
@@ -838,6 +841,8 @@ CREATE FUNCTION testfunc6b(b int) RETURNS testdomain1 LANGUAGE SQL AS $$ SELECT
CREATE OPERATOR !! (PROCEDURE = testfunc5b, RIGHTARG = testdomain1);
CREATE TABLE test5b (a int, b testdomain1);
CREATE TABLE test6b OF testtype1;
+CREATE TABLE test6b2 (a int, b text);
+ALTER TABLE test6b2 OF testtype1;
CREATE TABLE test10b (a int[], b testtype1[]);
CREATE TABLE test9b (a int, b int);
ALTER TABLE test9b ADD COLUMN c testdomain1;
@@ -858,6 +863,7 @@ DROP FUNCTION testfunc5b(a testdomain1);
DROP FUNCTION testfunc6b(b int);
DROP TABLE test5b;
DROP TABLE test6b;
+DROP TABLE test6b2;
DROP TABLE test9b;
DROP TABLE test10b;
DROP TYPE test7b;
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index ac74e75e17a..5bfa0f970a2 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1258,6 +1258,21 @@ ERROR: range lower bound must be less than or equal to range upper bound
LINE 1: select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)':...
^
set timezone to default;
+-- CREATE TYPE AS RANGE checks for USAGE on subtype
+CREATE ROLE regress_subtype;
+CREATE TYPE mytype AS (a INT, b INT);
+REVOKE USAGE ON TYPE mytype FROM PUBLIC;
+SET ROLE regress_subtype;
+CREATE TYPE myrange AS RANGE (subtype = mytype);
+ERROR: permission denied for type mytype
+RESET ROLE;
+GRANT USAGE ON TYPE mytype TO regress_subtype;
+SET ROLE regress_subtype;
+CREATE TYPE myrange AS RANGE (subtype = mytype);
+RESET ROLE;
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to type myrange
+DROP ROLE regress_subtype;
--
-- Test user-defined range of floats
--
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 18a1bf9ff81..f38567c81bd 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -508,6 +508,8 @@ CREATE OPERATOR !+! (PROCEDURE = int4pl, LEFTARG = testdomain1, RIGHTARG = testd
CREATE TABLE test5a (a int, b testdomain1);
CREATE TABLE test6a OF testtype1;
+CREATE TABLE test6a2 (a int, b text);
+ALTER TABLE test6a2 OF testtype1;
CREATE TABLE test10a (a int[], b testtype1[]);
CREATE TABLE test9a (a int, b int);
@@ -543,6 +545,8 @@ CREATE OPERATOR !! (PROCEDURE = testfunc5b, RIGHTARG = testdomain1);
CREATE TABLE test5b (a int, b testdomain1);
CREATE TABLE test6b OF testtype1;
+CREATE TABLE test6b2 (a int, b text);
+ALTER TABLE test6b2 OF testtype1;
CREATE TABLE test10b (a int[], b testtype1[]);
CREATE TABLE test9b (a int, b int);
@@ -567,6 +571,7 @@ DROP FUNCTION testfunc5b(a testdomain1);
DROP FUNCTION testfunc6b(b int);
DROP TABLE test5b;
DROP TABLE test6b;
+DROP TABLE test6b2;
DROP TABLE test9b;
DROP TABLE test10b;
DROP TYPE test7b;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 6a03138faf0..fd8c14ee66c 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -377,6 +377,20 @@ select '[2010-01-01 01:00:00 -05, 2010-01-01 02:00:00 -08)'::tstzrange;
select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)'::tstzrange;
set timezone to default;
+-- CREATE TYPE AS RANGE checks for USAGE on subtype
+CREATE ROLE regress_subtype;
+CREATE TYPE mytype AS (a INT, b INT);
+REVOKE USAGE ON TYPE mytype FROM PUBLIC;
+SET ROLE regress_subtype;
+CREATE TYPE myrange AS RANGE (subtype = mytype);
+RESET ROLE;
+GRANT USAGE ON TYPE mytype TO regress_subtype;
+SET ROLE regress_subtype;
+CREATE TYPE myrange AS RANGE (subtype = mytype);
+RESET ROLE;
+DROP TYPE mytype CASCADE;
+DROP ROLE regress_subtype;
+
--
-- Test user-defined range of floats
--