diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out
index 7c7fbcdfc7d..76b570db96b 100644
--- a/contrib/amcheck/expected/check_btree.out
+++ b/contrib/amcheck/expected/check_btree.out
@@ -186,7 +186,8 @@ SELECT bt_index_check('toasty', true);
(1 row)
--
--- 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);
@@ -194,19 +195,31 @@ ALTER TABLE bttest_a OWNER TO regress_bttest_role;
-- A dummy index function checking current_user
CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
BEGIN
- ASSERT current_user = 'regress_bttest_role',
+ 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);
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;
diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql
index bc2224ea49d..04796573c9a 100644
--- a/contrib/amcheck/sql/check_btree.sql
+++ b/contrib/amcheck/sql/check_btree.sql
@@ -117,7 +117,8 @@ INSERT INTO toast_bug SELECT repeat('a', 2200);
SELECT bt_index_check('toasty', true);
--
--- 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);
@@ -125,7 +126,10 @@ ALTER TABLE bttest_a OWNER TO regress_bttest_role;
-- A dummy index function checking current_user
CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
BEGIN
- ASSERT current_user = 'regress_bttest_role',
+ 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;
@@ -133,8 +137,16 @@ $$ 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;
-- Check support of both 1B and 4B header sizes of short varlena datum
CREATE TABLE varlena_bug (v text);
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index d92a9466224..fbd25760bc1 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -286,6 +286,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, false);
}
else
{
diff --git a/contrib/fuzzystrmatch/expected/fuzzystrmatch.out b/contrib/fuzzystrmatch/expected/fuzzystrmatch.out
index 3195e1ec3c8..aeab031a2ed 100644
--- a/contrib/fuzzystrmatch/expected/fuzzystrmatch.out
+++ b/contrib/fuzzystrmatch/expected/fuzzystrmatch.out
@@ -41,6 +41,14 @@ SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
3
(1 row)
+SELECT levenshtein('GUMBO', 'GAMBOL', 1, 1, 2000000000);
+ levenshtein
+-------------
+ 3
+(1 row)
+
+SELECT levenshtein('GUMBO', 'GAMBOL', 2000000000, 2000000000, 2000000000);
+ERROR: levenshtein distance out of range
SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
levenshtein_less_equal
------------------------
@@ -53,6 +61,12 @@ SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
4
(1 row)
+SELECT levenshtein_less_equal('aaa', 'aaaaa', 1073741824, 0, 1073741824, 10);
+ levenshtein_less_equal
+------------------------
+ 11
+(1 row)
+
SELECT metaphone('GUMBO', 4);
metaphone
-----------
diff --git a/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql b/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql
index 0b4bb9be57e..c1d55f823d7 100644
--- a/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql
+++ b/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql
@@ -11,8 +11,11 @@ SELECT soundex(''), difference('', '');
SELECT levenshtein('GUMBO', 'GAMBOL');
SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
+SELECT levenshtein('GUMBO', 'GAMBOL', 1, 1, 2000000000);
+SELECT levenshtein('GUMBO', 'GAMBOL', 2000000000, 2000000000, 2000000000);
SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
+SELECT levenshtein_less_equal('aaa', 'aaaaa', 1073741824, 0, 1073741824, 10);
SELECT metaphone('GUMBO', 4);
diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c
index 945b90eba64..047f25ee0c1 100644
--- a/contrib/hstore_plperl/hstore_plperl.c
+++ b/contrib/hstore_plperl/hstore_plperl.c
@@ -116,8 +116,9 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
errmsg("cannot transform non-hash Perl value to hstore")));
hv = (HV *) in;
- pcount = hv_iterinit(hv);
+ (void) hv_iterinit(hv);
+ pcount = 64; /* arbitrary initial guess */
pairs = palloc_array(Pairs, pcount);
i = 0;
@@ -126,6 +127,12 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
char *key = sv2cstr(HeSVKEY_force(he));
SV *value = HeVAL(he);
+ if (i >= pcount)
+ {
+ pcount *= 2;
+ pairs = repalloc_array(pairs, Pairs, pcount);
+ }
+
pairs[i].key = pstrdup(key);
pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
pairs[i].needfree = true;
@@ -146,7 +153,7 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
i++;
}
- pcount = hstoreUniquePairs(pairs, pcount, &buflen);
+ pcount = hstoreUniquePairs(pairs, i, &buflen);
out = hstorePairs(pairs, pcount, buflen);
PG_RETURN_POINTER(out);
}
diff --git a/contrib/pg_trgm/trgm_gist.c b/contrib/pg_trgm/trgm_gist.c
index 9ef2e38560a..4835a37adf8 100644
--- a/contrib/pg_trgm/trgm_gist.c
+++ b/contrib/pg_trgm/trgm_gist.c
@@ -897,7 +897,7 @@ gtrgm_picksplit(PG_FUNCTION_ARGS)
else
size_alpha = SIGLENBIT(siglen) -
sizebitvec((cache[j].allistrue) ? GETSIGN(datum_l) :
- GETSIGN(cache[j].sign),
+ cache[j].sign,
siglen);
}
else
@@ -910,7 +910,7 @@ gtrgm_picksplit(PG_FUNCTION_ARGS)
else
size_beta = SIGLENBIT(siglen) -
sizebitvec((cache[j].allistrue) ? GETSIGN(datum_r) :
- GETSIGN(cache[j].sign),
+ cache[j].sign,
siglen);
}
else
diff --git a/contrib/pgcrypto/expected/pgp-decrypt.out b/contrib/pgcrypto/expected/pgp-decrypt.out
index 1db89e8c00a..960bf52c855 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt.out
@@ -438,3 +438,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-decrypt_1.out b/contrib/pgcrypto/expected/pgp-decrypt_1.out
index d214e0bc0e0..f00acdcecf2 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt_1.out
@@ -11,7 +11,7 @@ yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
=JcP+
-----END PGP MESSAGE-----
'), 'foobar');
-ERROR: Wrong key or corrupt data
+ERROR: encrypt error: Cipher cannot be initialized
select pgp_sym_decrypt(dearmor('
-----BEGIN PGP MESSAGE-----
Comment: dat1.aes.sha1.mdc.s2k3.z0
@@ -434,3 +434,33 @@ 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: encrypt error: Cipher cannot be initialized
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
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 100644
--- 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 100644
--- 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/expected/pgp-pubkey-decrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
index f41c6c9893a..ac9307daf5e 100644
--- a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.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;
@@ -595,6 +609,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;
+ERROR: encrypt error: Cipher cannot be initialized
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
ERROR: Wrong key or corrupt data
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=3 and encdata.id=3;
@@ -650,3 +667,16 @@ 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: encrypt error: Cipher cannot be initialized
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ pgp_pub_decrypt
+-----------------
+ Secret msg
+(1 row)
+
diff --git a/contrib/pgcrypto/pgp-cfb.c b/contrib/pgcrypto/pgp-cfb.c
index de41e825b0c..c81534bf1ef 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;
@@ -71,6 +72,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);
@@ -195,7 +197,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,8 +223,21 @@ cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst,
while (len > 0)
{
unsigned rlen;
+ int err;
+
+ err = px_cipher_encrypt(ctx->ciph, 0, ctx->fr, ctx->block_size, ctx->fre, &rlen);
+
+ /*
+ * 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))));
- px_cipher_encrypt(ctx->ciph, 0, ctx->fr, ctx->block_size, ctx->fre, &rlen);
if (ctx->block_no < 5)
ctx->block_no++;
@@ -253,7 +268,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
@@ -261,5 +277,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 e1ea5b3e58d..887fbc19278 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;
@@ -983,7 +984,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;
@@ -1026,7 +1028,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 f7467c9b1cb..968e8c92ce4 100644
--- a/contrib/pgcrypto/pgp-encrypt.c
+++ b/contrib/pgcrypto/pgp-encrypt.c
@@ -174,7 +174,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 838a7c381fc..b7e4d05e2c5 100644
--- a/contrib/pgcrypto/pgp-pgsql.c
+++ b/contrib/pgcrypto/pgp-pgsql.c
@@ -192,6 +192,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
diff --git a/contrib/pgcrypto/pgp-pubkey.c b/contrib/pgcrypto/pgp-pubkey.c
index 9a6561caf9d..470e0debbb0 100644
--- a/contrib/pgcrypto/pgp-pubkey.c
+++ b/contrib/pgcrypto/pgp-pubkey.c
@@ -382,8 +382,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 44d9aebc2ed..9c2aaadbf76 100644
--- a/contrib/pgcrypto/pgp.c
+++ b/contrib/pgcrypto/pgp.c
@@ -49,6 +49,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
{
@@ -229,6 +230,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;
@@ -374,6 +376,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 f85bbae7545..7033c97433f 100644
--- a/contrib/pgcrypto/pgp.h
+++ b/contrib/pgcrypto/pgp.h
@@ -153,6 +153,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
*/
@@ -261,6 +264,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 len);
int pgp_set_pubkey(PGP_Context *ctx, MBuf *keypkt,
@@ -281,7 +285,8 @@ int pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int key_len);
typedef struct PGP_CFB PGP_CFB;
int pgp_cfb_create(PGP_CFB **ctx_p, int algo,
- const uint8 *key, int key_len, int resync, uint8 *iv);
+ 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 2fe498f2f02..b27ecacaeb2 100644
--- a/contrib/pgcrypto/sql/pgp-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-decrypt.sql
@@ -327,3 +327,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/config.sgml b/doc/src/sgml/config.sgml
index 1aaa29b9c4d..3b8b60d9347 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1136,6 +1136,19 @@ include_dir 'conf.d'
time of encryption. In order to make use of a changed value, a new
password must be set.
+
+
+ If a role password was created with a different iteration count than
+ the value of scram_iterations specified in the
+ postgresql.conf file or on the server command
+ line, an unauthenticated user can discern the existence of the role by
+ observing discrepancies in the server's responses to connection
+ attempts. If you find this concerning, ensure that all role passwords
+ are created with scram_iterations set to the value
+ specified in the postgresql.conf file or on the
+ server command line.
+
+
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 6dbfec91f54..c2b24f7817f 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -927,6 +927,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 ac089f44bc3..ca3b8a85014 100644
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -189,6 +189,11 @@ CREATE TYPE name
type name. Otherwise, the multirange type name is formed by appending a
_multirange suffix to the range type name.
+
+
+ To be able to create a range type, you must have USAGE
+ privilege on the subtype.
+
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 4a121a3e5da..03bdc63775b 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3561,6 +3561,11 @@ testdb=> \setenv LESS -imx4F
pg_dumpall, and
pg_restore, but it may be useful elsewhere.
+
+ Unlike most other meta-commands, the entire remainder of the line is
+ always taken to be the argument of \unrestrict, and
+ neither variable interpolation nor backquote expansion are performed.
+
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 17f7489e298..5650cdd8b07 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1375,7 +1375,7 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
Oid *p_argtypes;
char **p_argnames;
char *p_argmodes;
- bool arggiven[FUNC_MAX_ARGS];
+ bool *arggiven;
bool isnull;
int ap; /* call args position */
int pp; /* proargs position */
@@ -1399,8 +1399,8 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
Assert(include_out_arguments ? (pronargs == pronallargs) : (pronargs <= pronallargs));
/* initialize state for matching */
- *argnumbers = (int *) palloc(pronargs * sizeof(int));
- memset(arggiven, false, pronargs * sizeof(bool));
+ *argnumbers = palloc_array(int, pronargs);
+ arggiven = palloc0_array(bool, pronallargs);
/* there are numposargs positional args before the named args */
for (ap = 0; ap < numposargs; ap++)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b8b69336b89..53adcd73021 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -20376,6 +20376,7 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
ObjectAddress tableobj,
typeobj;
HeapTuple classtuple;
+ AclResult aclresult;
/* Validate the type. */
typetuple = typenameType(NULL, ofTypename, NULL);
@@ -20383,6 +20384,10 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
typeform = (Form_pg_type) GETSTRUCT(typetuple);
typeid = typeform->oid;
+ aclresult = object_aclcheck(TypeRelationId, typeid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typeid);
+
/* Fail if the table has any inheritance parents. */
inheritsRelation = table_open(InheritsRelationId, AccessShareLock);
ScanKeyInit(&key,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 5b3a11519cf..81f2d71698e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1582,6 +1582,10 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
errmsg("range subtype cannot be %s",
format_type_be(rangeSubtype))));
+ aclresult = object_aclcheck(TypeRelationId, rangeSubtype, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, rangeSubtype);
+
/* Identify subopclass */
rangeSubOpclass = findRangeSubOpclass(rangeSubOpclassName, rangeSubtype);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 9ceb1fb5377..f0dd67ca7ee 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1295,6 +1295,20 @@ eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
oldContext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
+ /*
+ * Protect fixed-size fcinfo. Ordinarily this would have been checked
+ * while creating the WindowFunc, but it's possible that we are looking at
+ * a parsetree from a stored view that was made by a server executable
+ * with a different value of FUNC_MAX_ARGS.
+ */
+ if (perfuncstate->numArguments > FUNC_MAX_ARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("cannot pass more than %d argument to a function",
+ "cannot pass more than %d arguments to a function",
+ FUNC_MAX_ARGS,
+ FUNC_MAX_ARGS)));
+
/*
* We don't pass any normal arguments to a window function, but we do pass
* it the number of arguments, in order to permit window function
@@ -3128,6 +3142,25 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
numArguments = list_length(wfunc->args);
+ /*
+ * Check the number of arguments, to protect fixed-size arrays here and
+ * later in node execution.
+ *
+ * Aggregates can have at most FUNC_MAX_ARGS-1 args (compare
+ * AggregateCreate, whose error message we want to match). Ordinarily
+ * this would have been checked while creating the WindowFunc, but it's
+ * possible that we are looking at a parsetree from a stored view that was
+ * made by a server executable with a different value of FUNC_MAX_ARGS, or
+ * an executable in which parse_func.c didn't enforce the correct limit.
+ */
+ if (numArguments > FUNC_MAX_ARGS - 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("aggregates cannot have more than %d argument",
+ "aggregates cannot have more than %d arguments",
+ FUNC_MAX_ARGS - 1,
+ FUNC_MAX_ARGS - 1)));
+
i = 0;
foreach(lc, wfunc->args)
{
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 1b9e1cc0cc1..ce9f4ba5eb8 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -722,7 +722,7 @@ mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
encoded_salt[encoded_len] = '\0';
*salt = encoded_salt;
- *iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
+ *iterations = scram_sha_256_iterations;
/* StoredKey and ServerKey are not used in a doomed authentication */
memset(stored_key, 0, SCRAM_MAX_KEY_LEN);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 1669ef066b8..c3b59943107 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1985,7 +1985,23 @@ get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes)
int numArguments = 0;
ListCell *lc;
- Assert(list_length(aggref->aggargtypes) <= FUNC_MAX_ARGS);
+ /*
+ * Check the number of arguments to protect fixed-size arrays in callers.
+ *
+ * Aggregates can have at most FUNC_MAX_ARGS-1 args (compare
+ * AggregateCreate, whose error message we want to match). Ordinarily
+ * this would have been checked while creating the Aggref, but it's
+ * possible that we are looking at a parsetree from a stored view that was
+ * made by a server executable with a different value of FUNC_MAX_ARGS, or
+ * an executable in which parse_func.c didn't enforce the correct limit.
+ */
+ if (list_length(aggref->aggargtypes) > FUNC_MAX_ARGS - 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("aggregates cannot have more than %d argument",
+ "aggregates cannot have more than %d arguments",
+ FUNC_MAX_ARGS - 1,
+ FUNC_MAX_ARGS - 1)));
foreach(lc, aggref->aggargtypes)
{
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 4af519f5a9a..cf59ef1f5c3 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -615,6 +615,10 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
if (targetTypeId == ANYTABLEOID || inputTypeId == ANYTABLEOID)
return false;
+ /* reject all cases of casting something else to/from "internal" */
+ if (inputTypeId == INTERNALOID || targetTypeId == INTERNALOID)
+ return false;
+
/* accept if target is ANY */
if (targetTypeId == ANYOID)
continue;
@@ -3215,6 +3219,10 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
if (sourceTypeId == targetTypeId)
return COERCION_PATH_RELABELTYPE;
+ /* Reject all cases of casting something else to/from "internal" */
+ if (sourceTypeId == INTERNALOID || targetTypeId == INTERNALOID)
+ return COERCION_PATH_NONE;
+
/* Look in pg_cast */
tuple = SearchSysCache2(CASTSOURCETARGET,
ObjectIdGetDatum(sourceTypeId),
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 93cfe006834..3054b2f56d9 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -680,6 +680,32 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
rettype,
false);
+ /*
+ * Reject any attempt to call a function that takes or returns type
+ * internal from SQL. (The FUNCDETAIL_COERCION case does not reach this
+ * check because of the early return above, but that's okay because we
+ * disallow coercions to or from type internal.) Note that we are
+ * checking the resolved argument and result types, so this will reject
+ * calls to polymorphic functions that pass internal-type arguments. The
+ * casting rules should prevent that anyway, since we won't cast internal
+ * to any polymorphic type, but no harm in being doubly sure.
+ */
+ for (int i = 0; i < nargsplusdefs; i++)
+ {
+ if (declared_arg_types[i] == INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("functions accepting type \"%s\" cannot be called explicitly",
+ "internal"),
+ parser_errposition(pstate, location)));
+ }
+ if (rettype == INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("functions returning type \"%s\" cannot be called explicitly",
+ "internal"),
+ parser_errposition(pstate, location)));
+
/* perform the necessary typecasting of arguments */
make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
@@ -789,6 +815,22 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
aggref->aggtransno = -1;
aggref->location = location;
+ /*
+ * The argument-count limit for aggregates is one less than for other
+ * kinds of functions (cf. AggregateCreate). Now that we know it's an
+ * aggregate, apply the stricter limit. We need an explicit check
+ * because hypothetical-set aggregates don't have a fixed number of
+ * arguments, so having matched the pg_proc entry proves nothing.
+ */
+ if (list_length(fargs) > FUNC_MAX_ARGS - 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("aggregates cannot have more than %d argument",
+ "aggregates cannot have more than %d arguments",
+ FUNC_MAX_ARGS - 1,
+ FUNC_MAX_ARGS - 1),
+ parser_errposition(pstate, location)));
+
/*
* Reject attempt to call a parameterless aggregate without (*)
* syntax. This is mere pedantry but some folks insisted ...
@@ -869,6 +911,19 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("DISTINCT is supported only for single-argument window aggregates")));
}
+ /*
+ * As above, enforce the correct argument-count limit if it's really
+ * an aggregate.
+ */
+ if (wfunc->winagg && list_length(fargs) > FUNC_MAX_ARGS - 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("aggregates cannot have more than %d argument",
+ "aggregates cannot have more than %d arguments",
+ FUNC_MAX_ARGS - 1,
+ FUNC_MAX_ARGS - 1),
+ parser_errposition(pstate, location)));
+
/*
* Reject attempt to call a parameterless aggregate without (*)
* syntax. This is mere pedantry but some folks insisted ...
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index bdc8f8e26af..bac0fe4ad09 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -749,6 +749,28 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
opform->oprresult,
false);
+ /*
+ * Reject any attempt to call a function that takes or returns type
+ * internal from SQL. This is just like the check in ParseFuncOrColumn,
+ * but for operator syntax. (Despite that, we say "function" in the error
+ * messages; doesn't seem worth having two sets of translatable strings.)
+ */
+ for (int i = 0; i < nargs; i++)
+ {
+ if (declared_arg_types[i] == INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("functions accepting type \"%s\" cannot be called explicitly",
+ "internal"),
+ parser_errposition(pstate, location)));
+ }
+ if (rettype == INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("functions returning type \"%s\" cannot be called explicitly",
+ "internal"),
+ parser_errposition(pstate, location)));
+
/* perform the necessary typecasting of arguments */
make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index e20d844f3c4..36565fadebd 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1224,8 +1224,8 @@ FillPortalStore(Portal portal, bool isTopLevel)
portal->holdStore,
portal->holdContext,
false,
- NULL,
- NULL);
+ portal->tupDesc,
+ gettext_noop("query result type does not match portal result type"));
switch (portal->strategy)
{
diff --git a/src/backend/tsearch/to_tsany.c b/src/backend/tsearch/to_tsany.c
index fe39d6c4b93..be53631810b 100644
--- a/src/backend/tsearch/to_tsany.c
+++ b/src/backend/tsearch/to_tsany.c
@@ -166,8 +166,8 @@ TSVector
make_tsvector(ParsedText *prs)
{
int i,
- j,
- lenstr = 0,
+ j;
+ size_t lenstr = 0,
totallen;
TSVector in;
WordEntry *ptr;
@@ -178,10 +178,22 @@ make_tsvector(ParsedText *prs)
if (prs->curwords > 0)
prs->curwords = uniqueWORD(prs->words, prs->curwords);
- /* Determine space needed */
+ /*
+ * Determine space needed. Since what we are calculating is equivalent to
+ * the size of a portion of the input data structure, lenstr surely can't
+ * overflow size_t.
+ */
for (i = 0; i < prs->curwords; i++)
{
- lenstr += prs->words[i].len;
+ int toklen = prs->words[i].len;
+
+ /* Double-check that caller passed only lexemes of valid lengths */
+ if (toklen <= 0 || toklen > MAXSTRLEN)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("lexeme is too long for tsvector (%zu bytes, max %zu bytes)",
+ (size_t) toklen, (size_t) MAXSTRLEN)));
+ lenstr += toklen;
if (prs->words[i].alen)
{
lenstr = SHORTALIGN(lenstr);
@@ -192,7 +204,8 @@ make_tsvector(ParsedText *prs)
if (lenstr > MAXSTRPOS)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("string is too long for tsvector (%d bytes, max %d bytes)", lenstr, MAXSTRPOS)));
+ errmsg("string is too long for tsvector (%zu bytes, max %zu bytes)",
+ lenstr, (size_t) MAXSTRPOS)));
totallen = CALCDATASIZE(prs->curwords, lenstr);
in = (TSVector) palloc0(totallen);
diff --git a/src/backend/tsearch/ts_parse.c b/src/backend/tsearch/ts_parse.c
index c347129009e..bc5d5197e71 100644
--- a/src/backend/tsearch/ts_parse.c
+++ b/src/backend/tsearch/ts_parse.c
@@ -401,12 +401,30 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen)
while ((norms = LexizeExec(&ldata, NULL)) != NULL)
{
- TSLexeme *ptr = norms;
-
prs->pos++; /* set pos */
- while (ptr->lexeme)
+ for (TSLexeme *ptr = norms; ptr->lexeme; ptr++)
{
+ size_t lexeme_len = strlen(ptr->lexeme);
+
+ if (lexeme_len > MAXSTRLEN)
+ {
+#ifdef IGNORE_LONGLEXEME
+ ereport(NOTICE,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("word is too long to be indexed"),
+ errdetail("Words longer than %d characters are ignored.",
+ MAXSTRLEN)));
+ continue;
+#else
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("word is too long to be indexed"),
+ errdetail("Words longer than %d characters are ignored.",
+ MAXSTRLEN)));
+#endif
+ }
+
if (prs->curwords == prs->lenwords)
{
prs->lenwords *= 2;
@@ -415,13 +433,12 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen)
if (ptr->flags & TSL_ADDPOS)
prs->pos++;
- prs->words[prs->curwords].len = strlen(ptr->lexeme);
+ prs->words[prs->curwords].len = lexeme_len;
prs->words[prs->curwords].word = ptr->lexeme;
prs->words[prs->curwords].nvariant = ptr->nvariant;
prs->words[prs->curwords].flags = ptr->flags & TSL_PREFIX;
prs->words[prs->curwords].alen = 0;
prs->words[prs->curwords].pos.pos = LIMITPOS(prs->pos);
- ptr++;
prs->curwords++;
}
pfree(norms);
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 5b01b7753df..545f61b809c 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -80,7 +80,7 @@ enum RoleRecurseType
};
static Oid cached_role[] = {InvalidOid, InvalidOid, InvalidOid};
static List *cached_roles[] = {NIL, NIL, NIL};
-static uint32 cached_db_hash;
+uint32 cached_db_hash;
static const char *getid(const char *s, char *n, Node *escontext);
diff --git a/src/backend/utils/adt/levenshtein.c b/src/backend/utils/adt/levenshtein.c
index 9a84c8d0fc4..c060d73706e 100644
--- a/src/backend/utils/adt/levenshtein.c
+++ b/src/backend/utils/adt/levenshtein.c
@@ -9,7 +9,7 @@
* Levenshtein distance with custom costings, and (2) Levenshtein distance with
* custom costings and a "max" value above which exact distances are not
* interesting. Before the inclusion, we rely on the presence of the inline
- * function rest_of_char_same().
+ * functions rest_of_char_same() and levenshtein_result().
*
* Written based on a description of the algorithm by Michael Gilleland found
* at http://www.merriampark.com/ld.htm. Also looked at levenshtein.c in the
@@ -78,13 +78,16 @@ varstr_levenshtein(const char *source, int slen,
{
int m,
n;
- int *prev;
- int *curr;
+ int64 *prev;
+ int64 *curr;
int *s_char_len = NULL;
int j;
const char *y;
const char *send = source + slen;
const char *tend = target + tlen;
+ int64 ins_c_64 = ins_c;
+ int64 del_c_64 = del_c;
+ int64 sub_c_64 = sub_c;
/*
* For varstr_levenshtein_less_equal, we have real variables called
@@ -115,9 +118,9 @@ varstr_levenshtein(const char *source, int slen,
* 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
@@ -147,20 +150,20 @@ varstr_levenshtein(const char *source, int slen,
*/
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
@@ -174,12 +177,12 @@ varstr_levenshtein(const char *source, int slen,
* 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
@@ -211,7 +214,7 @@ varstr_levenshtein(const char *source, int slen,
++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;
/*
@@ -219,12 +222,12 @@ varstr_levenshtein(const char *source, int slen,
* t, we must perform i deletions.
*/
for (int 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 = target, j = 1; j < n; j++)
{
- int *temp;
+ int64 *temp;
const char *x = source;
int y_char_len = n != tlen + 1 ? pg_mblen_range(y, tend) : 1;
int i;
@@ -239,7 +242,7 @@ varstr_levenshtein(const char *source, int slen,
*/
if (stop_column < m)
{
- prev[stop_column] = max_d + 1;
+ prev[stop_column] = (int64) max_d + 1;
++stop_column;
}
@@ -251,13 +254,13 @@ varstr_levenshtein(const char *source, int slen,
*/
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 @@ varstr_levenshtein(const char *source, int slen,
{
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 @@ varstr_levenshtein(const char *source, int slen,
* 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 @@ varstr_levenshtein(const char *source, int slen,
{
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 @@ varstr_levenshtein(const char *source, int slen,
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 @@ varstr_levenshtein(const char *source, int slen,
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 @@ varstr_levenshtein(const char *source, int slen,
* 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)
source += (s_char_len != NULL) ? s_char_len[start_column - 1] : 1;
start_column++;
@@ -390,7 +393,7 @@ varstr_levenshtein(const char *source, int slen,
/* 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 @@ varstr_levenshtein(const char *source, int slen,
* 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/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index e845b80f553..05f853f3556 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -5061,6 +5061,10 @@ numeric_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
{
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
if (state1 == NULL)
PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
@@ -5157,6 +5161,10 @@ numeric_avg_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
{
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
if (state1 == NULL)
PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
@@ -5665,6 +5673,10 @@ numeric_poly_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
{
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
if (state1 == NULL)
PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
@@ -5880,6 +5892,10 @@ int8_avg_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
{
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
if (state1 == NULL)
PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index d43a0577ee4..e0e0bc0bfac 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -1444,7 +1444,7 @@ setup_regexp_matches(text *orig_str, text *pattern, pg_re_flags *re_flags,
/* convert string to pg_wchar form for matching */
orig_len = VARSIZE_ANY_EXHDR(orig_str);
- wide_str = (pg_wchar *) palloc(sizeof(pg_wchar) * (orig_len + 1));
+ wide_str = palloc_array(pg_wchar, orig_len + 1);
wide_len = pg_mb2wchar_with_len(VARDATA_ANY(orig_str), wide_str, orig_len);
/* set up the compiled pattern */
@@ -1580,23 +1580,24 @@ setup_regexp_matches(text *orig_str, text *pattern, pg_re_flags *re_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/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fd9c4daea67..7e07dd2e15a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10881,7 +10881,7 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
Assert(IsA(con, Const) &&
con->consttype == TEXTOID &&
!con->constisnull);
- appendStringInfoString(buf, TextDatumGetCString(con->constvalue));
+ appendStringInfoString(buf, quote_identifier(TextDatumGetCString(con->constvalue)));
}
appendStringInfoString(buf, " FROM ");
get_rule_expr((Node *) lsecond(expr->args), context, false);
@@ -10901,6 +10901,7 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
Assert(IsA(con, Const) &&
con->consttype == TEXTOID &&
!con->constisnull);
+ /* NB: safe because no allowed words need quoted/escaped */
appendStringInfo(buf, " %s",
TextDatumGetCString(con->constvalue));
}
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6750fb8e6..98ddc9a7ade 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -608,7 +608,8 @@ scalarineqsel(PlannerInfo *root, Oid operator, bool isgt, bool iseq,
* make an estimate based on comparing the constant to the table size.
*/
if (vardata->var && IsA(vardata->var, Var) &&
- ((Var *) vardata->var)->varattno == SelfItemPointerAttributeNumber)
+ ((Var *) vardata->var)->varattno == SelfItemPointerAttributeNumber &&
+ consttype == TIDOID)
{
ItemPointer itemptr;
double block;
diff --git a/src/backend/utils/adt/tsquery_util.c b/src/backend/utils/adt/tsquery_util.c
index 7b6970a6f82..9eabaf75cfe 100644
--- a/src/backend/utils/adt/tsquery_util.c
+++ b/src/backend/utils/adt/tsquery_util.c
@@ -289,7 +289,7 @@ QTNBinary(QTNode *in)
* Caller must initialize *sumlen and *nnode to zeroes.
*/
static void
-cntsize(QTNode *in, int *sumlen, int *nnode)
+cntsize(QTNode *in, size_t *sumlen, size_t *nnode)
{
/* since this function recurses, it could be driven to stack overflow. */
check_stack_depth();
@@ -327,10 +327,17 @@ fillQT(QTN2QTState *state, QTNode *in)
if (in->valnode->type == QI_VAL)
{
+ size_t distance;
+
memcpy(state->curitem, in->valnode, sizeof(QueryOperand));
memcpy(state->curoperand, in->word, in->valnode->qoperand.length);
- state->curitem->qoperand.distance = state->curoperand - state->operand;
+ distance = state->curoperand - state->operand;
+ if (distance > MAXSTRPOS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("tsquery is too large")));
+ state->curitem->qoperand.distance = distance;
state->curoperand[in->valnode->qoperand.length] = '\0';
state->curoperand += in->valnode->qoperand.length + 1;
state->curitem++;
@@ -364,7 +371,7 @@ QTN2QT(QTNode *in)
{
TSQuery out;
int len;
- int sumlen = 0,
+ size_t sumlen = 0,
nnode = 0;
QTN2QTState state;
diff --git a/src/backend/utils/adt/tsvector.c b/src/backend/utils/adt/tsvector.c
index 39e16f8a7cd..d3981bafd37 100644
--- a/src/backend/utils/adt/tsvector.c
+++ b/src/backend/utils/adt/tsvector.c
@@ -218,8 +218,8 @@ tsvectorin(PG_FUNCTION_ARGS)
if (cur - tmpbuf > MAXSTRPOS)
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("string is too long for tsvector (%ld bytes, max %ld bytes)",
- (long) (cur - tmpbuf), (long) MAXSTRPOS)));
+ errmsg("string is too long for tsvector (%zu bytes, max %zu bytes)",
+ (size_t) (cur - tmpbuf), (size_t) MAXSTRPOS)));
/*
* Enlarge buffers if needed
@@ -272,7 +272,8 @@ tsvectorin(PG_FUNCTION_ARGS)
if (buflen > MAXSTRPOS)
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("string is too long for tsvector (%d bytes, max %d bytes)", buflen, MAXSTRPOS)));
+ errmsg("string is too long for tsvector (%zu bytes, max %zu bytes)",
+ (size_t) buflen, (size_t) MAXSTRPOS)));
totallen = CALCDATASIZE(len, buflen);
in = (TSVector) palloc0(totallen);
@@ -317,8 +318,8 @@ tsvectorout(PG_FUNCTION_ARGS)
TSVector out = PG_GETARG_TSVECTOR(0);
char *outbuf;
int32 i,
- lenbuf = 0,
pp;
+ size_t lenbuf;
WordEntry *ptr = ARRPTR(out);
char *curin,
*curout;
@@ -327,7 +328,7 @@ tsvectorout(PG_FUNCTION_ARGS)
lenbuf = out->size * 2 /* '' */ + out->size - 1 /* space */ + 2 /* \0 */ ;
for (i = 0; i < out->size; i++)
{
- lenbuf += ptr[i].len * 2 * pg_database_encoding_max_length() /* for escape */ ;
+ lenbuf += ptr[i].len * 2 /* allow for escapes */ ;
if (ptr[i].haspos)
lenbuf += 1 /* : */ + 7 /* int2 + , + weight */ * POSDATALEN(out, &(ptr[i]));
}
@@ -459,12 +460,14 @@ tsvectorrecv(PG_FUNCTION_ARGS)
bool needSort = false;
nentries = pq_getmsgint(buf, sizeof(int32));
- if (nentries < 0 || nentries > (MaxAllocSize / sizeof(WordEntry)))
+
+ /* We disallow empty lexemes, so more than MAXSTRPOS of them can't fit */
+ if (nentries < 0 || nentries > MAXSTRPOS)
elog(ERROR, "invalid size of tsvector");
hdrlen = DATAHDRSIZE + sizeof(WordEntry) * nentries;
- len = hdrlen * 2; /* times two to make room for lexemes */
+ len = hdrlen * 2; /* times two to make some room for lexemes */
vec = (TSVector) palloc0(len);
vec->size = nentries;
@@ -481,6 +484,8 @@ tsvectorrecv(PG_FUNCTION_ARGS)
/* sanity checks */
lex_len = strlen(lexeme);
+ if (lex_len == 0)
+ elog(ERROR, "invalid tsvector: empty lexeme");
if (lex_len > MAXSTRLEN)
elog(ERROR, "invalid tsvector: lexeme too long");
@@ -546,6 +551,15 @@ tsvectorrecv(PG_FUNCTION_ARGS)
}
}
+ /*
+ * Enforce that datalen is still within MAXSTRPOS, ie the last lexeme
+ * didn't go past that. We could allow that, since no "pos" field
+ * overflowed, but tsvectorrecv shouldn't accept values that other
+ * tsvector-constructing routines wouldn't.
+ */
+ if (datalen > MAXSTRPOS)
+ elog(ERROR, "invalid tsvector: maximum total lexeme length exceeded");
+
SET_VARSIZE(vec, hdrlen + datalen);
if (needSort)
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index ae90e750604..b0f6257fc7d 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -175,6 +175,7 @@ tsvector_strip(PG_FUNCTION_ARGS)
*arrout;
char *cur;
+ /* Output can't be bigger than input, so no need for overflow checks */
for (i = 0; i < in->size; i++)
len += arrin[i].len;
@@ -499,6 +500,8 @@ tsvector_delete_by_indices(TSVector tsv, int *indices_to_delete,
/*
* Copy tsv to tsout, skipping lexemes listed in indices_to_delete.
+ *
+ * Output can't be bigger than input, so no need for overflow checks.
*/
arrout = ARRPTR(tsout);
dataout = STRPTR(tsout);
@@ -727,7 +730,7 @@ tsvector_to_array(PG_FUNCTION_ARGS)
int i;
ArrayType *array;
- elements = palloc(tsin->size * sizeof(Datum));
+ elements = palloc_array(Datum, tsin->size);
for (i = 0; i < tsin->size; i++)
{
@@ -762,20 +765,29 @@ array_to_tsvector(PG_FUNCTION_ARGS)
deconstruct_array_builtin(v, TEXTOID, &dlexemes, &nulls, &nitems);
/*
- * Reject nulls and zero length strings (maybe we should just ignore them,
- * instead?)
+ * Reject nulls and zero-length or over-length strings (maybe we should
+ * just ignore them, instead?)
*/
for (i = 0; i < nitems; i++)
{
+ int toklen;
+
if (nulls[i])
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("lexeme array may not contain nulls")));
- if (VARSIZE(dlexemes[i]) - VARHDRSZ == 0)
+ toklen = VARSIZE(dlexemes[i]) - VARHDRSZ;
+ if (toklen == 0)
ereport(ERROR,
(errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
errmsg("lexeme array may not contain empty strings")));
+ if (toklen >= MAXSTRLEN)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("word is too long (%d bytes, max %d bytes)",
+ toklen,
+ MAXSTRLEN - 1)));
}
/* Sort and de-dup, because this is required for a valid tsvector. */
@@ -789,6 +801,11 @@ array_to_tsvector(PG_FUNCTION_ARGS)
/* Calculate space needed for surviving lexemes. */
for (i = 0; i < nitems; i++)
datalen += VARSIZE(dlexemes[i]) - VARHDRSZ;
+ if (datalen > MAXSTRPOS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("string is too long for tsvector (%zu bytes, max %zu bytes)",
+ (size_t) datalen, (size_t) MAXSTRPOS)));
tslen = CALCDATASIZE(nitems, datalen);
/* Allocate and fill tsvector. */
@@ -872,9 +889,15 @@ tsvector_filter(PG_FUNCTION_ARGS)
}
}
+ /*
+ * The output tsvector might be smaller than the input, but it can't be
+ * bigger, so VARSIZE(tsin) is surely enough space. Also, we don't need
+ * to worry about overflows below.
+ */
tsout = (TSVector) palloc0(VARSIZE(tsin));
tsout->size = tsin->size;
arrout = ARRPTR(tsout);
+ /* worst-case location of output's lexemes; we may need to adjust below */
dataout = STRPTR(tsout);
for (i = j = 0; i < tsin->size; i++)
@@ -974,6 +997,12 @@ tsvector_concat(PG_FUNCTION_ARGS)
* Conservative estimate of space needed. We might need all the data in
* both inputs, and conceivably add a pad byte before position data for
* each item where there was none before.
+ *
+ * Note: since the MAXSTRPOS limit constrains each input tsvector to be
+ * considerably less than MaxAllocSize, we don't need to worry about
+ * integer overflow here, nor in the data-copying steps below. We do need
+ * to enforce that the result meets the MAXSTRPOS limit, but we check that
+ * once at the end.
*/
output_bytes = VARSIZE(in1) + VARSIZE(in2) + i1 + i2;
@@ -1125,7 +1154,8 @@ tsvector_concat(PG_FUNCTION_ARGS)
if (dataoff > MAXSTRPOS)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("string is too long for tsvector (%d bytes, max %d bytes)", dataoff, MAXSTRPOS)));
+ errmsg("string is too long for tsvector (%zu bytes, max %zu bytes)",
+ (size_t) dataoff, (size_t) MAXSTRPOS)));
/*
* Adjust sizes (asserting that we didn't overrun the original estimates)
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 1f8fcd1f406..b7f1fa33014 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -6094,6 +6094,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;
+}
+
/* Expand each Levenshtein distance variant */
#include "levenshtein.c"
#define LEVENSHTEIN_LESS_EQUAL
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 06c909b0aa8..e4c7f18313f 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -36,7 +36,10 @@
* certain other system catalogs, such as pg_namespace; but for them, our
* response is just to invalidate all plans. We expect updates on those
* catalogs to be infrequent enough that more-detailed tracking is not worth
- * the effort.
+ * the effort. We likewise watch pg_authid, pg_auth_members, and
+ * pg_database, which can change which row-level security policies apply.
+ * Since those are shared catalogs whose inval events reach every backend
+ * in the cluster, we invalidate only the role-dependent plans.
*
* In addition to full-fledged query plans, we provide a facility for
* detecting invalidations of simple scalar expressions. This is fairly
@@ -67,6 +70,7 @@
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
+#include "utils/acl.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/resowner_private.h"
@@ -122,6 +126,7 @@ static bool ScanQueryWalker(Node *node, bool *acquire);
static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
static void PlanCacheRelCallback(Datum arg, Oid relid);
static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue);
+static void PlanCacheRoleCallback(Datum arg, int cacheid, uint32 hashvalue);
static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
/* GUC parameter */
@@ -143,6 +148,9 @@ InitPlanCache(void)
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, PlanCacheRoleCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(AUTHOID, PlanCacheRoleCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(DATABASEOID, PlanCacheRoleCallback, (Datum) 0);
}
/*
@@ -2254,6 +2262,56 @@ PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue)
}
}
+/*
+ * PlanCacheRoleCallback
+ * Syscache inval callback function for AUTHMEMROLEMEM, AUTHOID, and
+ * DATABASEOID caches
+ *
+ * Role membership, role attributes, and database ownership (which confers
+ * membership in pg_database_owner) affect planning by way of row-level
+ * security, so invalidate just the role-dependent plans. For DATABASEOID, we
+ * can ignore changes to other databases' pg_database rows.
+ */
+static void
+PlanCacheRoleCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ dlist_iter iter;
+
+ if (cacheid == DATABASEOID &&
+ hashvalue != cached_db_hash &&
+ hashvalue != 0)
+ return; /* ignore pg_database changes for other DBs */
+
+ dlist_foreach(iter, &saved_plan_list)
+ {
+ CachedPlanSource *plansource = dlist_container(CachedPlanSource,
+ node, iter.cur);
+
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+
+ /* No work if it's already invalidated */
+ if (!plansource->is_valid)
+ continue;
+
+ /* Never invalidate if parse/plan would be a no-op anyway */
+ if (!StmtPlanRequiresRevalidation(plansource))
+ continue;
+
+ if (plansource->dependsOnRLS)
+ {
+ /* Invalidate the querytree and generic plan */
+ plansource->is_valid = false;
+ if (plansource->gplan)
+ plansource->gplan->is_valid = false;
+ }
+ else if (plansource->gplan && plansource->gplan->dependsOnRole)
+ {
+ /* Invalidate the generic plan only */
+ plansource->gplan->is_valid = false;
+ }
+ }
+}
+
/*
* PlanCacheSysCallback
* Syscache inval callback function for other caches
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 05578d8f876..878b920cd10 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -1446,7 +1446,7 @@ get_func_arg_info(HeapTuple procTup,
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
Assert(numargs >= procStruct->pronargs);
- *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
+ *p_argtypes = palloc_array(Oid, numargs);
memcpy(*p_argtypes, ARR_DATA_PTR(arr),
numargs * sizeof(Oid));
}
@@ -1455,7 +1455,7 @@ get_func_arg_info(HeapTuple procTup,
/* If no proallargtypes, use proargtypes */
numargs = procStruct->proargtypes.dim1;
Assert(numargs == procStruct->pronargs);
- *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
+ *p_argtypes = palloc_array(Oid, numargs);
memcpy(*p_argtypes, procStruct->proargtypes.values,
numargs * sizeof(Oid));
}
@@ -1534,7 +1534,7 @@ get_func_trftypes(HeapTuple procTup,
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "protrftypes is not a 1-D Oid array or it contains nulls");
- *p_trftypes = (Oid *) palloc(nelems * sizeof(Oid));
+ *p_trftypes = palloc_array(Oid, nelems);
memcpy(*p_trftypes, ARR_DATA_PTR(arr),
nelems * sizeof(Oid));
diff --git a/src/backend/utils/init/usercontext.c b/src/backend/utils/init/usercontext.c
index dd9a0dd6a83..1a3abc62ae8 100644
--- a/src/backend/utils/init/usercontext.c
+++ b/src/backend/utils/init/usercontext.c
@@ -70,6 +70,12 @@ SwitchToUntrustedUser(Oid userid, UserContext *context)
* session state. Also set up a new GUC nest level, so that we can
* roll back any GUC changes that may be made by code running as the
* target user, inasmuch as they could be malicious.
+ *
+ * Unlike most use of SECURITY_RESTRICTED_OPERATION, this opts not to
+ * use RestrictSearchPath(). Calling it would just stop the current
+ * user from attacking "userid", but we've already established that
+ * the current user could just SET ROLE to "userid". Calling it would
+ * be a compatibility break.
*/
sec_context |= SECURITY_RESTRICTED_OPERATION;
SetUserIdAndSecContext(userid, sec_context);
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index d6416086b34..269f9f58ca5 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -1044,50 +1044,146 @@ findOwningExtension(CatalogId catalogId)
/*
* parseOidArray
- * parse a string of numbers delimited by spaces into a character array
+ * parse a string of unsigned numbers separated by spaces
+ * into an array of OIDs
*
- * Note: actually this is used for both Oids and potentially-signed
- * attribute numbers. This should cause no trouble, but we could split
- * the function into two functions with different argument types if it does.
+ * The result is a malloc'd array.
+ *
+ * If arraysize >= 0, we insist that the input contain exactly that many
+ * OIDs, and the allocated array is of that length too. If arraysize < 0,
+ * we dynamically size the array to have one more entry than the input
+ * provides, and fill the extra entry with zero.
*/
-
-void
-parseOidArray(const char *str, Oid *array, int arraysize)
+Oid *
+parseOidArray(const char *str, int arraysize)
{
- int j,
- argNum;
- char temp[100];
- char s;
-
+ Oid *array;
+ int allocsize,
+ argNum,
+ templen;
+ char temp[32];
+
+ if (arraysize >= 0)
+ allocsize = arraysize;
+ else
+ {
+ /*
+ * Make enough room for input + one extra entry (could be more than
+ * enough, if there are redundant spaces in the input).
+ */
+ allocsize = 2;
+ for (const char *s1 = str; *s1; s1++)
+ {
+ if (*s1 == ' ')
+ allocsize++;
+ }
+ }
+ array = pg_malloc_array(Oid, allocsize);
argNum = 0;
- j = 0;
- for (;;)
+ templen = 0;
+ for (const char *s1 = str;; s1++)
{
- s = *str++;
+ char s = *s1;
+
if (s == ' ' || s == '\0')
{
- if (j > 0)
+ if (templen > 0)
{
- if (argNum >= arraysize)
+ if (arraysize >= 0 && argNum >= arraysize)
pg_fatal("could not parse numeric array \"%s\": too many numbers", str);
- temp[j] = '\0';
+ temp[templen] = '\0';
array[argNum++] = atooid(temp);
- j = 0;
+ templen = 0;
}
if (s == '\0')
break;
}
else
{
- if (!(isdigit((unsigned char) s) || s == '-') ||
- j >= sizeof(temp) - 1)
+ if (!isdigit((unsigned char) s) ||
+ templen >= sizeof(temp) - 1)
pg_fatal("could not parse numeric array \"%s\": invalid character in number", str);
- temp[j++] = s;
+ temp[templen++] = s;
}
}
- while (argNum < arraysize)
+ if (arraysize >= 0 && argNum != arraysize)
+ pg_fatal("could not parse numeric array \"%s\": too few numbers", str);
+
+ while (argNum < allocsize)
array[argNum++] = InvalidOid;
+
+ return array;
+}
+
+
+/*
+ * parseIntArray
+ * parse a string of possibly-signed numbers separated by spaces
+ * into an array of ints
+ *
+ * This is exactly like parseOidArray, but for integers.
+ */
+int *
+parseIntArray(const char *str, int arraysize)
+{
+ int *array;
+ int allocsize,
+ argNum,
+ templen;
+ char temp[32];
+
+ if (arraysize >= 0)
+ allocsize = arraysize;
+ else
+ {
+ /*
+ * Make enough room for input + one extra entry (could be more than
+ * enough, if there are redundant spaces in the input).
+ */
+ allocsize = 2;
+ for (const char *s1 = str; *s1; s1++)
+ {
+ if (*s1 == ' ')
+ allocsize++;
+ }
+ }
+ array = pg_malloc_array(int, allocsize);
+ argNum = 0;
+ templen = 0;
+ for (const char *s1 = str;; s1++)
+ {
+ char s = *s1;
+
+ if (s == ' ' || s == '\0')
+ {
+ if (templen > 0)
+ {
+ if (arraysize >= 0 && argNum >= arraysize)
+ pg_fatal("could not parse numeric array \"%s\": too many numbers", str);
+ temp[templen] = '\0';
+ array[argNum++] = atoi(temp);
+ templen = 0;
+ }
+ if (s == '\0')
+ break;
+ }
+ else
+ {
+ if (!(isdigit((unsigned char) s) || s == '-') ||
+ templen >= sizeof(temp) - 1)
+ pg_fatal("could not parse numeric array \"%s\": invalid character in number", str);
+ temp[templen++] = s;
+ }
+ }
+
+ if (arraysize >= 0 && argNum != arraysize)
+ pg_fatal("could not parse numeric array \"%s\": too few numbers", str);
+
+ while (argNum < allocsize)
+ array[argNum++] = 0;
+
+ return array;
}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7604417e262..b024acaf768 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6972,12 +6972,8 @@ getAggregates(Archive *fout, int *numAggs)
if (agginfo[i].aggfn.nargs == 0)
agginfo[i].aggfn.argtypes = NULL;
else
- {
- agginfo[i].aggfn.argtypes = (Oid *) pg_malloc(agginfo[i].aggfn.nargs * sizeof(Oid));
- parseOidArray(PQgetvalue(res, i, i_proargtypes),
- agginfo[i].aggfn.argtypes,
- agginfo[i].aggfn.nargs);
- }
+ agginfo[i].aggfn.argtypes = parseOidArray(PQgetvalue(res, i, i_proargtypes),
+ agginfo[i].aggfn.nargs);
agginfo[i].aggfn.postponed_def = false; /* might get set during sort */
/* Decide whether we want to dump it */
@@ -7298,11 +7294,8 @@ getFuncs(Archive *fout, int *numFuncs)
if (finfo[i].nargs == 0)
finfo[i].argtypes = NULL;
else
- {
- finfo[i].argtypes = (Oid *) pg_malloc(finfo[i].nargs * sizeof(Oid));
- parseOidArray(PQgetvalue(res, i, i_proargtypes),
- finfo[i].argtypes, finfo[i].nargs);
- }
+ finfo[i].argtypes = parseOidArray(PQgetvalue(res, i, i_proargtypes),
+ finfo[i].nargs);
finfo[i].postponed_def = false; /* might get set during sort */
/* Decide whether we want to dump it */
@@ -8254,9 +8247,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
indxinfo[j].indreloptions = pg_strdup(PQgetvalue(res, j, i_indreloptions));
indxinfo[j].indstatcols = pg_strdup(PQgetvalue(res, j, i_indstatcols));
indxinfo[j].indstatvals = pg_strdup(PQgetvalue(res, j, i_indstatvals));
- indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnattrs * sizeof(Oid));
- parseOidArray(PQgetvalue(res, j, i_indkey),
- indxinfo[j].indkeys, indxinfo[j].indnattrs);
+ indxinfo[j].indkeys = parseIntArray(PQgetvalue(res, j, i_indkey),
+ indxinfo[j].indnattrs);
indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
indxinfo[j].indnullsnotdistinct = (PQgetvalue(res, j, i_indnullsnotdistinct)[0] == 't');
@@ -13230,12 +13222,10 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (*protrftypes)
{
- Oid *typeids = palloc(FUNC_MAX_ARGS * sizeof(Oid));
- int i;
+ Oid *typeids = parseOidArray(protrftypes, -1);
appendPQExpBufferStr(q, " TRANSFORM ");
- parseOidArray(protrftypes, typeids, FUNC_MAX_ARGS);
- for (i = 0; typeids[i]; i++)
+ for (int i = 0; typeids[i]; i++)
{
if (i != 0)
appendPQExpBufferStr(q, ", ");
@@ -18881,7 +18871,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo)
appendPQExpBufferStr(q, " (");
for (k = 0; k < indxinfo->indnkeyattrs; k++)
{
- int indkey = (int) indxinfo->indkeys[k];
+ int indkey = indxinfo->indkeys[k];
const char *attname;
if (indkey == InvalidAttrNumber)
@@ -18898,7 +18888,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo)
for (k = indxinfo->indnkeyattrs; k < indxinfo->indnattrs; k++)
{
- int indkey = (int) indxinfo->indkeys[k];
+ int indkey = indxinfo->indkeys[k];
const char *attname;
if (indkey == InvalidAttrNumber)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 879f01e6f23..c37d158ea7b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -436,7 +436,7 @@ typedef struct _indxInfo
char *indstatvals; /* statistic values for columns */
int indnkeyattrs; /* number of index key attributes */
int indnattrs; /* total number of index attributes */
- Oid *indkeys; /* In spite of the name 'indkeys' this field
+ int *indkeys; /* In spite of the name 'indkeys' this field
* contains both key and nonkey attributes */
bool indisclustered;
bool indisreplident;
@@ -737,7 +737,8 @@ extern PublicationInfo *findPublicationByOid(Oid oid);
extern void recordExtensionMembership(CatalogId catId, ExtensionInfo *ext);
extern ExtensionInfo *findOwningExtension(CatalogId catalogId);
-extern void parseOidArray(const char *str, Oid *array, int arraysize);
+extern Oid *parseOidArray(const char *str, int arraysize);
+extern int *parseIntArray(const char *str, int arraysize);
extern void sortDumpableObjects(DumpableObject **objs, int numObjs,
DumpId preBoundaryId, DumpId postBoundaryId);
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 81859e02ae6..bacab5f7bb4 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2379,6 +2379,12 @@ exec_command_restrict(PsqlScanState scan_state, bool active_branch,
Assert(!restricted);
+ /*
+ * Unlike \unrestrict, this argument may safely undergo backquote and
+ * variable expansion: HandleSlashCmds() rejects \restrict in
+ * restricted mode before its argument is scanned, so we only get here
+ * when the input could execute such things anyway.
+ */
opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
if (opt == NULL || opt[0] == '\0')
{
@@ -2692,14 +2698,23 @@ exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
if (active_branch)
{
char *opt;
+ size_t len;
- opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+ opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
if (opt == NULL || opt[0] == '\0')
{
pg_log_error("\\%s: missing required argument", cmd);
return PSQL_CMD_ERROR;
}
+ /* strip any trailing spaces and semicolons */
+ len = strlen(opt);
+ while (len > 0 &&
+ (opt[len - 1] == ';' ||
+ (isascii((unsigned char) opt[len - 1]) &&
+ isspace((unsigned char) opt[len - 1]))))
+ opt[--len] = '\0';
+
if (!restricted)
{
pg_log_error("\\%s: not currently in restricted mode", cmd);
@@ -2717,7 +2732,7 @@ exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
}
}
else
- ignore_slash_options(scan_state);
+ ignore_slash_whole_line(scan_state);
return PSQL_CMD_SKIP_LINE;
}
@@ -3246,8 +3261,8 @@ is_branching_command(const char *cmd)
* Prepare to possibly restore query buffer to its current state
* (cf. discard_query_text).
*
- * We need to remember the length of the query buffer, and the lexer's
- * notion of the parenthesis nesting depth.
+ * We need to remember the length of the query buffer, and assorted
+ * lexer internal state such as parenthesis nesting depth.
*/
static void
save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
@@ -3255,8 +3270,8 @@ save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
{
if (query_buf)
conditional_stack_set_query_len(cstack, query_buf->len);
- conditional_stack_set_paren_depth(cstack,
- psql_scan_get_paren_depth(scan_state));
+ conditional_stack_set_lex_state(cstack,
+ psql_scan_get_lex_state(scan_state));
}
/*
@@ -3265,9 +3280,7 @@ save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
* We must discard data that was appended to query_buf during an inactive
* \if branch. We don't have to do anything there if there's no query_buf.
*
- * Also, reset the lexer state to the same paren depth there was before.
- * (The rest of its state doesn't need attention, since we could not be
- * inside a comment or literal or partial token.)
+ * Also, reset the lexer's state to what it was before.
*/
static void
discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
@@ -3281,8 +3294,8 @@ discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
query_buf->len = new_len;
query_buf->data[new_len] = '\0';
}
- psql_scan_set_paren_depth(scan_state,
- conditional_stack_get_paren_depth(cstack));
+ psql_scan_set_lex_state(scan_state,
+ conditional_stack_get_lex_state(cstack));
}
/*
diff --git a/src/bin/psql/psqlscanslash.h b/src/bin/psql/psqlscanslash.h
index 7724242f371..3515cb1e96f 100644
--- a/src/bin/psql/psqlscanslash.h
+++ b/src/bin/psql/psqlscanslash.h
@@ -31,6 +31,11 @@ extern char *psql_scan_slash_option(PsqlScanState state,
extern void psql_scan_slash_command_end(PsqlScanState state);
+extern PsqlScanStateSave *psql_scan_get_lex_state(PsqlScanState state);
+
+extern void psql_scan_set_lex_state(PsqlScanState state,
+ const PsqlScanStateSave *lex_state);
+
extern int psql_scan_get_paren_depth(PsqlScanState state);
extern void psql_scan_set_paren_depth(PsqlScanState state, int depth);
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index b56d443ac4a..193c0541aec 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -701,8 +701,46 @@ psql_scan_slash_command_end(PsqlScanState state)
psql_scan_reselect_sql_lexer(state);
}
+/*
+ * Save current lexer state
+ *
+ * Relevant parts of the state are returned in a pg_malloc'd struct.
+ * It is caller's responsibility to free the struct eventually.
+ */
+PsqlScanStateSave *
+psql_scan_get_lex_state(PsqlScanState state)
+{
+ PsqlScanStateSave *lex_state = pg_malloc_object(PsqlScanStateSave);
+ StaticAssertDecl(sizeof(lex_state->identifiers) == sizeof(state->identifiers),
+ "identifiers array lengths must match");
+
+ lex_state->paren_depth = state->paren_depth;
+ lex_state->begin_depth = state->begin_depth;
+ lex_state->identifier_count = state->identifier_count;
+ memcpy(lex_state->identifiers, state->identifiers,
+ sizeof(lex_state->identifiers));
+ return lex_state;
+}
+
+/*
+ * Restore lexer state to what it was when saved
+ */
+void
+psql_scan_set_lex_state(PsqlScanState state,
+ const PsqlScanStateSave *lex_state)
+{
+ state->paren_depth = lex_state->paren_depth;
+ state->begin_depth = lex_state->begin_depth;
+ state->identifier_count = lex_state->identifier_count;
+ memcpy(state->identifiers, lex_state->identifiers,
+ sizeof(state->identifiers));
+}
+
/*
* Fetch current paren nesting depth
+ *
+ * (These functions are obsolete, and kept around only to avoid API/ABI
+ * breakage in the back branches.)
*/
int
psql_scan_get_paren_depth(PsqlScanState state)
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index f5ec8fab1f3..b953e662a09 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -395,4 +395,11 @@ sub psql_fails_like
qr/backslash commands are restricted; only \\unrestrict is allowed/,
'meta-command in restrict mode fails');
+psql_fails_like(
+ $node,
+ qq{\\restrict test
+\\unrestrict `echo test`},
+ qr/wrong key/,
+ '\unrestrict does not do backquote expansion');
+
done_testing();
diff --git a/src/fe_utils/conditional.c b/src/fe_utils/conditional.c
index e67fb0dbe8c..07acd5d0ab6 100644
--- a/src/fe_utils/conditional.c
+++ b/src/fe_utils/conditional.c
@@ -57,6 +57,7 @@ conditional_stack_push(ConditionalStack cstack, ifState new_state)
p->if_state = new_state;
p->query_len = -1;
p->paren_depth = -1;
+ p->lex_state = NULL;
p->next = cstack->head;
cstack->head = p;
}
@@ -73,6 +74,8 @@ conditional_stack_pop(ConditionalStack cstack)
if (!p)
return false;
cstack->head = cstack->head->next;
+ if (p->lex_state)
+ free(p->lex_state);
free(p);
return true;
}
@@ -166,8 +169,39 @@ conditional_stack_get_query_len(ConditionalStack cstack)
return cstack->head->query_len;
}
+/*
+ * Save current lexer state in topmost stack entry.
+ *
+ * The lexer state is presumed to be a single pg_malloc'd chunk.
+ * It will be freed automatically when the stack entry is popped.
+ */
+void
+conditional_stack_set_lex_state(ConditionalStack cstack,
+ struct PsqlScanStateSave *lex_state)
+{
+ Assert(!conditional_stack_empty(cstack));
+ if (cstack->head->lex_state) /* free old state, if any */
+ free(cstack->head->lex_state);
+ cstack->head->lex_state = lex_state;
+}
+
+/*
+ * Fetch last-recorded lexer state from topmost stack entry.
+ * Will return NULL if no stack or it was never saved.
+ */
+struct PsqlScanStateSave *
+conditional_stack_get_lex_state(ConditionalStack cstack)
+{
+ if (conditional_stack_empty(cstack))
+ return NULL;
+ return cstack->head->lex_state;
+}
+
/*
* Save current parenthesis nesting depth in topmost stack entry.
+ *
+ * (These functions are obsolete, and kept around only to avoid API/ABI
+ * breakage in the back branches.)
*/
void
conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
diff --git a/src/include/fe_utils/conditional.h b/src/include/fe_utils/conditional.h
index 36e72c977ce..e7bfc1136f8 100644
--- a/src/include/fe_utils/conditional.h
+++ b/src/include/fe_utils/conditional.h
@@ -49,18 +49,18 @@ typedef enum ifState
* query_len is used to determine what accumulated text to throw away at the
* end of an inactive branch. (We could, perhaps, teach the lexer to not add
* stuff to the query buffer in the first place when inside an inactive branch;
- * but that would be very invasive.) We also need to save and restore the
- * lexer's parenthesis nesting depth when throwing away text. (We don't need
- * to save and restore any of its other state, such as comment nesting depth,
- * because a backslash command could never appear inside a comment or SQL
- * literal.)
+ * but that would be very invasive.) We also need to save and restore some
+ * lexer state, such as parenthesis nesting depth, when throwing away text.
*/
+struct PsqlScanStateSave; /* opaque outside lexer */
+
typedef struct IfStackElem
{
ifState if_state; /* current state, see enum above */
int query_len; /* length of query_buf at last branch start */
- int paren_depth; /* parenthesis depth at last branch start */
+ int paren_depth; /* (obsolete, not used anymore) */
struct IfStackElem *next; /* next surrounding \if, if any */
+ struct PsqlScanStateSave *lex_state; /* lexer state at last branch start */
} IfStackElem;
typedef struct ConditionalStackData
@@ -95,6 +95,11 @@ extern void conditional_stack_set_query_len(ConditionalStack cstack, int len);
extern int conditional_stack_get_query_len(ConditionalStack cstack);
+extern void conditional_stack_set_lex_state(ConditionalStack cstack,
+ struct PsqlScanStateSave *lex_state);
+
+extern struct PsqlScanStateSave *conditional_stack_get_lex_state(ConditionalStack cstack);
+
extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth);
extern int conditional_stack_get_paren_depth(ConditionalStack cstack);
diff --git a/src/include/fe_utils/psqlscan.h b/src/include/fe_utils/psqlscan.h
index 6a90fcab9eb..2084814ecdd 100644
--- a/src/include/fe_utils/psqlscan.h
+++ b/src/include/fe_utils/psqlscan.h
@@ -26,6 +26,9 @@
/* Abstract type for lexer's internal state */
typedef struct PsqlScanStateData *PsqlScanState;
+/* Abstract type for state save/restore */
+typedef struct PsqlScanStateSave PsqlScanStateSave;
+
/* Termination states for psql_scan() */
typedef enum
{
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 373e7e14763..a05dcd43613 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -131,6 +131,23 @@ typedef struct PsqlScanStateData
void *cb_passthrough;
} PsqlScanStateData;
+/*
+ * Conditional scanning (\if ... \endif) needs to be able to reset the
+ * lexer's state to what it was at the beginning of a chunk of text that
+ * we choose to ignore. PsqlScanStateSave holds the values that need
+ * to be saved and restored. We assume that saving/restoring happens only
+ * while processing a backslash command, so we needn't save state that is
+ * concerned with comment or SQL literal processing: we won't be inside
+ * one of those.
+ */
+struct PsqlScanStateSave
+{
+ int paren_depth; /* depth of nesting in parentheses */
+ int begin_depth; /* depth of begin/end pairs */
+ int identifier_count; /* identifiers since start of statement */
+ char identifiers[4]; /* records the first few identifiers */
+};
+
/*
* Functions exported by psqlscan.l, but only meant for use within
diff --git a/src/include/tsearch/ts_type.h b/src/include/tsearch/ts_type.h
index b076039c1c1..5bfae27e32e 100644
--- a/src/include/tsearch/ts_type.h
+++ b/src/include/tsearch/ts_type.h
@@ -35,7 +35,9 @@
*
* The positions for each lexeme must be sorted.
*
- * Note, tsvectorsend/recv believe that sizeof(WordEntry) == 4
+ * Note that while the WordEntry items must be sorted per tsCompareString(),
+ * the per-lexeme data storage could be in some other order, ie the series
+ * of WordEntry->pos values need not be strictly ascending.
*/
typedef struct
@@ -46,13 +48,15 @@ typedef struct
pos:20; /* MAX 1Mb */
} WordEntry;
-#define MAXSTRLEN ( (1<<11) - 1)
-#define MAXSTRPOS ( (1<<20) - 1)
+#define MAXSTRLEN ( (1<<11) - 1) /* maximum value of WordEntry.len */
+#define MAXSTRPOS ( (1<<20) - 1) /* maximum value of WordEntry.pos */
extern int compareWordEntryPos(const void *a, const void *b);
/*
- * Equivalent to
+ * Representation of positions (and weights) associated with a lexeme.
+ *
+ * WordEntryPos is equivalent to
* typedef struct {
* uint16
* weight:2,
@@ -75,40 +79,53 @@ typedef struct
WordEntryPos pos[1];
} WordEntryPosVector1;
+#define MAXNUMPOS (256) /* semi-arbitrary limit on npos */
+/* Macros for getting/setting the fields of a WordEntryPos */
#define WEP_GETWEIGHT(x) ( (x) >> 14 )
#define WEP_GETPOS(x) ( (x) & 0x3fff )
#define WEP_SETWEIGHT(x,v) ( (x) = ( (v) << 14 ) | ( (x) & 0x3fff ) )
#define WEP_SETPOS(x,v) ( (x) = ( (x) & 0xc000 ) | ( (v) & 0x3fff ) )
-#define MAXENTRYPOS (1<<14)
-#define MAXNUMPOS (256)
+#define MAXENTRYPOS (1<<14) /* max value of WordEntryPos pos field, +1 */
+/* Macro for clamping a position to what will fit in WordEntryPos pos field */
#define LIMITPOS(x) ( ( (x) >= MAXENTRYPOS ) ? (MAXENTRYPOS-1) : (x) )
/* This struct represents a complete tsvector datum */
typedef struct
{
int32 vl_len_; /* varlena header (do not touch directly!) */
- int32 size;
+ int32 size; /* number of entries[] items */
WordEntry entries[FLEXIBLE_ARRAY_MEMBER];
/* lexemes follow the entries[] array */
} TSVectorData;
typedef TSVectorData *TSVector;
+/*
+ * Calculate the size of a TSVector given the number of WordEntries and
+ * the total space needed for lexeme text and positions. NOTE: callers
+ * must enforce lenstr <= MAXSTRPOS, which ensures that WordEntry.pos
+ * fields will not overflow, and also protects against integer overflow here.
+ * (Since we prohibit empty lexemes, nentries can't exceed lenstr.)
+ */
#define DATAHDRSIZE (offsetof(TSVectorData, entries))
#define CALCDATASIZE(nentries, lenstr) (DATAHDRSIZE + (nentries) * sizeof(WordEntry) + (lenstr) )
/* pointer to start of a tsvector's WordEntry array */
-#define ARRPTR(x) ( (x)->entries )
+#define ARRPTR(tsv) ( (tsv)->entries )
/* pointer to start of a tsvector's lexeme storage */
-#define STRPTR(x) ( (char *) &(x)->entries[(x)->size] )
+#define STRPTR(tsv) ( (char *) &(tsv)->entries[(tsv)->size] )
-#define _POSVECPTR(x, e) ((WordEntryPosVector *)(STRPTR(x) + SHORTALIGN((e)->pos + (e)->len)))
-#define POSDATALEN(x,e) ( ( (e)->haspos ) ? (_POSVECPTR(x,e)->npos) : 0 )
-#define POSDATAPTR(x,e) (_POSVECPTR(x,e)->pos)
+/* pointer to WordEntryPosVector for a WordEntry */
+#define _POSVECPTR(tsv,we) ((WordEntryPosVector *) \
+ (STRPTR(tsv) + SHORTALIGN((we)->pos + (we)->len)))
+/* number of positions stored for a WordEntry */
+#define POSDATALEN(tsv,we) ( (we)->haspos ? _POSVECPTR(tsv,we)->npos : 0 )
+/* pointer to start of positions stored for a WordEntry */
+#define POSDATAPTR(tsv,we) (_POSVECPTR(tsv,we)->pos)
/*
* fmgr interface functions
@@ -233,7 +250,8 @@ typedef TSQueryData *TSQuery;
*/
#define COMPUTESIZE(size, lenofoperand) ( HDRSIZETQ + (size) * sizeof(QueryItem) + (lenofoperand) )
#define TSQUERY_TOO_BIG(size, lenofoperand) \
- ((size) > (MaxAllocSize - HDRSIZETQ - (lenofoperand)) / sizeof(QueryItem))
+ ((size_t) (lenofoperand) > MaxAllocSize - HDRSIZETQ || \
+ (size) > (MaxAllocSize - HDRSIZETQ - (lenofoperand)) / sizeof(QueryItem))
/* Returns a pointer to the first QueryItem in a TSQuery */
#define GETQUERY(x) ((QueryItem*)( (char*)(x)+HDRSIZETQ ))
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index e3ea31d83f5..4ce5d40201f 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -235,6 +235,9 @@ extern void select_best_grantor(Oid roleId, AclMode privileges,
const Acl *acl, Oid ownerId,
Oid *grantorId, AclMode *grantOptions);
+/* DATABASEOID syscache hash value for our own database, set by initialize_acl */
+extern uint32 cached_db_hash;
+
extern void initialize_acl(void);
extern bool revoked_something;
diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c
index fa562767585..a63d465acf4 100644
--- a/src/interfaces/ecpg/ecpglib/data.c
+++ b/src/interfaces/ecpg/ecpglib/data.c
@@ -526,6 +526,13 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
src_size,
dec_size;
+ if (size < 2 || pval[0] != '\\' || pval[1] != 'x')
+ {
+ ecpg_raise(lineno, ECPG_BYTEA_FORMAT,
+ ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
+ return false;
+ }
+
dst_size = ecpg_hex_enc_len(varcharsize);
src_size = size - 2; /* exclude backslash + 'x' */
dec_size = src_size < dst_size ? src_size : dst_size;
diff --git a/src/interfaces/ecpg/ecpglib/error.c b/src/interfaces/ecpg/ecpglib/error.c
index 26fdcdb69e9..fba8b4468dd 100644
--- a/src/interfaces/ecpg/ecpglib/error.c
+++ b/src/interfaces/ecpg/ecpglib/error.c
@@ -130,6 +130,13 @@ ecpg_raise(int line, int code, const char *sqlstate, const char *str)
ecpg_gettext("inserting an array of variables is not supported on line %d"), line);
break;
+ case ECPG_BYTEA_FORMAT:
+ snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
+ /*------
+ translator: this string will be truncated at 149 characters expanded. */
+ ecpg_gettext("invalid input syntax for type bytea: \"%s\", on line %d"), str, line);
+ break;
+
case ECPG_NO_CONN:
snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
/*------
diff --git a/src/interfaces/ecpg/include/ecpgerrno.h b/src/interfaces/ecpg/include/ecpgerrno.h
index c4bc526463d..d8216b365b6 100644
--- a/src/interfaces/ecpg/include/ecpgerrno.h
+++ b/src/interfaces/ecpg/include/ecpgerrno.h
@@ -32,6 +32,7 @@
#define ECPG_NO_ARRAY -214
#define ECPG_DATA_NOT_ARRAY -215
#define ECPG_ARRAY_INSERT -216
+#define ECPG_BYTEA_FORMAT -217
#define ECPG_NO_CONN -220
#define ECPG_NOT_CONN -221
diff --git a/src/interfaces/ecpg/test/expected/sql-bytea.c b/src/interfaces/ecpg/test/expected/sql-bytea.c
index 8338c6008dd..901594a6f63 100644
--- a/src/interfaces/ecpg/test/expected/sql-bytea.c
+++ b/src/interfaces/ecpg/test/expected/sql-bytea.c
@@ -356,17 +356,36 @@ if (sqlca.sqlcode < 0) sqlprint();}
if (sqlca.sqlcode < 0) sqlprint();}
#line 115 "bytea.pgc"
+
+ /* Test for invalid bytea format */
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select '' :: text", ECPGt_EOIT,
+ ECPGt_bytea,&(recv_buf[0]),(long)DATA_SIZE,(long)1,sizeof(struct bytea_2),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 118 "bytea.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 118 "bytea.pgc"
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select '\\\\a1234' :: text", ECPGt_EOIT,
+ ECPGt_bytea,&(recv_buf[0]),(long)DATA_SIZE,(long)1,sizeof(struct bytea_2),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 119 "bytea.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 119 "bytea.pgc"
+
+
{ ECPGtrans(__LINE__, NULL, "commit");
-#line 116 "bytea.pgc"
+#line 121 "bytea.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 116 "bytea.pgc"
+#line 121 "bytea.pgc"
{ ECPGdisconnect(__LINE__, "CURRENT");
-#line 117 "bytea.pgc"
+#line 122 "bytea.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 117 "bytea.pgc"
+#line 122 "bytea.pgc"
return 0;
diff --git a/src/interfaces/ecpg/test/expected/sql-bytea.stderr b/src/interfaces/ecpg/test/expected/sql-bytea.stderr
index cb828a76020..58589474856 100644
--- a/src/interfaces/ecpg/test/expected/sql-bytea.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-bytea.stderr
@@ -181,7 +181,29 @@ SQL error: invalid statement name "cursor1" on line 82
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_process_output on line 115: OK: DROP TABLE
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ECPGtrans on line 116: action "commit"; connection "ecpg1_regression"
+[NO_PID]: ecpg_execute on line 118: query: select '' :: text; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlcode -217 on line 118: invalid input syntax for type bytea: "", on line 118
+[NO_PID]: sqlca: code: -217, state: 42804
+SQL error: invalid input syntax for type bytea: "", on line 118
+[NO_PID]: ecpg_execute on line 119: query: select '\\a1234' :: text; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 119: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 119: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 119: RESULT: \\a1234 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlcode -217 on line 119: invalid input syntax for type bytea: "\\a1234", on line 119
+[NO_PID]: sqlca: code: -217, state: 42804
+SQL error: invalid input syntax for type bytea: "\\a1234", on line 119
+[NO_PID]: ECPGtrans on line 121: action "commit"; connection "ecpg1_regression"
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: deallocate_one on line 0: name sel_stmt
[NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/sql/bytea.pgc b/src/interfaces/ecpg/test/sql/bytea.pgc
index e8741231194..da9152758a2 100644
--- a/src/interfaces/ecpg/test/sql/bytea.pgc
+++ b/src/interfaces/ecpg/test/sql/bytea.pgc
@@ -113,6 +113,11 @@ while (0)
dump_binary(recv_short_buf.arr, recv_short_buf.len, ind[1]);
exec sql drop table test;
+
+ /* Test for invalid bytea format */
+ exec sql select ''::text into :recv_buf[0];
+ exec sql select '\\a1234'::text into :recv_buf[0];
+
exec sql commit;
exec sql disconnect;
diff --git a/src/pl/plperl/SPI.xs b/src/pl/plperl/SPI.xs
index fe9388e18c5..87a2a81452b 100644
--- a/src/pl/plperl/SPI.xs
+++ b/src/pl/plperl/SPI.xs
@@ -78,7 +78,7 @@ spi_spi_prepare(sv, ...)
char* query = sv2cstr(sv);
if (items < 1)
Perl_croak(aTHX_ "Usage: spi_prepare(query, ...)");
- argv = ( SV**) palloc(( items - 1) * sizeof(SV*));
+ argv = palloc_array(SV*, items - 1);
for ( i = 1; i < items; i++)
argv[i - 1] = ST(i);
RETVAL = plperl_spi_prepare(query, items - 1, argv);
@@ -106,7 +106,7 @@ spi_spi_exec_prepared(sv, ...)
offset++;
}
argc = items - offset;
- argv = ( SV**) palloc( argc * sizeof(SV*));
+ argv = palloc_array(SV*, argc);
for ( i = 0; offset < items; offset++, i++)
argv[i] = ST(offset);
ret_hash = plperl_spi_exec_prepared(query, attr, argc, argv);
@@ -126,7 +126,7 @@ spi_spi_query_prepared(sv, ...)
if ( items < 1)
Perl_croak(aTHX_ "Usage: spi_query_prepared(query, "
"[\\@bind_values])");
- argv = ( SV**) palloc(( items - 1) * sizeof(SV*));
+ argv = palloc_array(SV*, items - 1);
for ( i = 1; i < items; i++)
argv[i - 1] = ST(i);
RETVAL = plperl_spi_query_prepared(query, items - 1, argv);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 7fb585b4bd1..2c055375572 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -1081,8 +1081,8 @@ plperl_build_tuple_result(HV *perlhash, TupleDesc td)
HE *he;
HeapTuple tup;
- values = palloc0(sizeof(Datum) * td->natts);
- nulls = palloc(sizeof(bool) * td->natts);
+ values = palloc0_array(Datum, td->natts);
+ nulls = palloc_array(bool, td->natts);
memset(nulls, true, sizeof(bool) * td->natts);
hv_iterinit(perlhash);
@@ -1166,6 +1166,10 @@ get_perl_array_ref(SV *sv)
* if we didn't do it like that, we'd need some other convention for knowing
* whether we'd already found any scalars (and thus the number of dimensions
* is frozen).
+ *
+ * Caller is required to have set dims[cur_depth - 1] to the length of the
+ * input array, i.e., av_len(av) + 1. We make this requirement so as to
+ * avoid reading av_len() twice, which is hazardous for tied arrays.
*/
static void
array_to_datum_internal(AV *av, ArrayBuildState **astatep,
@@ -1175,7 +1179,7 @@ array_to_datum_internal(AV *av, ArrayBuildState **astatep,
{
dTHX;
int i;
- int len = av_len(av) + 1;
+ int len = dims[cur_depth - 1];
for (i = 0; i < len; i++)
{
@@ -1501,7 +1505,7 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
* Currently we make no effort to cache any of the stuff we look up here,
* which is bad.
*/
- info = palloc0(sizeof(plperl_array_info));
+ info = palloc0_object(plperl_array_info);
/* get element type information, including output conversion function */
get_type_io_data(elementtype, IOFunc_output,
@@ -1537,7 +1541,7 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
&nitems);
/* Get total number of elements in each dimension */
- info->nelems = palloc(sizeof(int) * info->ndims);
+ info->nelems = palloc_array(int, info->ndims);
info->nelems[0] = nitems;
for (i = 1; i < info->ndims; i++)
info->nelems[i] = info->nelems[i - 1] / dims[i - 1];
@@ -1787,9 +1791,9 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
tupdesc = tdata->tg_relation->rd_att;
natts = tupdesc->natts;
- modvalues = (Datum *) palloc0(natts * sizeof(Datum));
- modnulls = (bool *) palloc0(natts * sizeof(bool));
- modrepls = (bool *) palloc0(natts * sizeof(bool));
+ modvalues = palloc0_array(Datum, natts);
+ modnulls = palloc0_array(bool, natts);
+ modrepls = palloc0_array(bool, natts);
hv_iterinit(hvNew);
while ((he = hv_iternext(hvNew)))
@@ -2800,7 +2804,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
* struct prodesc and subsidiary data must all live in proc_cxt.
************************************************************/
oldcontext = MemoryContextSwitchTo(proc_cxt);
- prodesc = (plperl_proc_desc *) palloc0(sizeof(plperl_proc_desc));
+ prodesc = palloc0_object(plperl_proc_desc);
prodesc->proname = pstrdup(NameStr(procStruct->proname));
MemoryContextSetIdentifier(proc_cxt, prodesc->proname);
prodesc->fn_cxt = proc_cxt;
@@ -2808,9 +2812,9 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
prodesc->fn_tid = procTup->t_self;
prodesc->nargs = procStruct->pronargs;
- prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
- prodesc->arg_is_rowtype = (bool *) palloc0(prodesc->nargs * sizeof(bool));
- prodesc->arg_arraytype = (Oid *) palloc0(prodesc->nargs * sizeof(Oid));
+ prodesc->arg_out_func = palloc0_array(FmgrInfo, prodesc->nargs);
+ prodesc->arg_is_rowtype = palloc0_array(bool, prodesc->nargs);
+ prodesc->arg_arraytype = palloc0_array(Oid, prodesc->nargs);
MemoryContextSwitchTo(oldcontext);
/* Remember if function is STABLE/IMMUTABLE */
@@ -3596,13 +3600,13 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
"PL/Perl spi_prepare query",
ALLOCSET_SMALL_SIZES);
MemoryContextSwitchTo(plan_cxt);
- qdesc = (plperl_query_desc *) palloc0(sizeof(plperl_query_desc));
+ qdesc = palloc0_object(plperl_query_desc);
snprintf(qdesc->qname, sizeof(qdesc->qname), "%p", qdesc);
qdesc->plan_cxt = plan_cxt;
qdesc->nargs = argc;
- qdesc->argtypes = (Oid *) palloc(argc * sizeof(Oid));
- qdesc->arginfuncs = (FmgrInfo *) palloc(argc * sizeof(FmgrInfo));
- qdesc->argtypioparams = (Oid *) palloc(argc * sizeof(Oid));
+ qdesc->argtypes = palloc_array(Oid, argc);
+ qdesc->arginfuncs = palloc_array(FmgrInfo, argc);
+ qdesc->argtypioparams = palloc_array(Oid, argc);
MemoryContextSwitchTo(oldcontext);
/************************************************************
@@ -3773,8 +3777,8 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv)
************************************************************/
if (argc > 0)
{
- nulls = (char *) palloc(argc);
- argvalues = (Datum *) palloc(argc * sizeof(Datum));
+ nulls = palloc_array(char, argc);
+ argvalues = palloc_array(Datum, argc);
}
else
{
@@ -3886,8 +3890,8 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
************************************************************/
if (argc > 0)
{
- nulls = (char *) palloc(argc);
- argvalues = (Datum *) palloc(argc * sizeof(Datum));
+ nulls = palloc_array(char, argc);
+ argvalues = palloc_array(Datum, argc);
}
else
{
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 904ee126d16..8639dd0e405 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -2498,6 +2498,20 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
if (procStruct->pronargs > 0)
{
+ /*
+ * Protect against overrun of fixed-size hashkey->argtypes array.
+ * Ordinarily the parser would have checked this long since, but it's
+ * possible that we are looking at a pg_proc entry that was made by a
+ * server executable with a different value of FUNC_MAX_ARGS.
+ */
+ if (procStruct->pronargs > FUNC_MAX_ARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("cannot pass more than %d argument to a function",
+ "cannot pass more than %d arguments to a function",
+ FUNC_MAX_ARGS,
+ FUNC_MAX_ARGS)));
+
/* get the argument types */
memcpy(hashkey->argtypes, procStruct->proargtypes.values,
procStruct->pronargs * sizeof(Oid));
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 78a57328852..f14d5ce2ccf 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -7896,13 +7896,21 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
* If there's no cast path according to the parser, fall back to using
* an I/O coercion; this is semantically dubious but matches plpgsql's
* historical behavior. We would need something of the sort for
- * UNKNOWN literals in any case. (This is probably now only reachable
- * in the case where srctype is UNKNOWN/RECORD.)
+ * UNKNOWN literals in any case. The only case we reject is casting
+ * to/from INTERNAL. (Other than that case, this is probably now only
+ * reachable in the case where srctype is UNKNOWN/RECORD.)
*/
if (cast_expr == NULL)
{
CoerceViaIO *iocoerce = makeNode(CoerceViaIO);
+ if (srctype == INTERNALOID || dsttype == INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(srctype),
+ format_type_be(dsttype))));
+
iocoerce->arg = (Expr *) placeholder;
iocoerce->resulttype = dsttype;
iocoerce->resultcollid = InvalidOid;
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index bff00aec350..48558b34472 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -1410,6 +1410,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
volatile MemoryContext proc_cxt = NULL;
Tcl_DString proc_internal_def;
Tcl_DString proc_internal_body;
+ Tcl_DString proc_internal_args;
/* We'll need the pg_proc tuple in any case... */
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
@@ -1457,16 +1458,16 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
************************************************************/
Tcl_DStringInit(&proc_internal_def);
Tcl_DStringInit(&proc_internal_body);
+ Tcl_DStringInit(&proc_internal_args);
PG_TRY();
{
bool is_trigger = OidIsValid(tgreloid);
char internal_proname[128];
HeapTuple typeTup;
Form_pg_type typeStruct;
- char proc_internal_args[33 * FUNC_MAX_ARGS];
Datum prosrcdatum;
char *proc_source;
- char buf[48];
+ char buf[64];
Tcl_Interp *interp;
int i;
int tcl_rc;
@@ -1499,7 +1500,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
* struct prodesc and subsidiary data must all live in proc_cxt.
************************************************************/
oldcontext = MemoryContextSwitchTo(proc_cxt);
- prodesc = (pltcl_proc_desc *) palloc0(sizeof(pltcl_proc_desc));
+ prodesc = palloc0_object(pltcl_proc_desc);
prodesc->user_proname = pstrdup(NameStr(procStruct->proname));
MemoryContextSetIdentifier(proc_cxt, prodesc->user_proname);
prodesc->internal_proname = pstrdup(internal_proname);
@@ -1508,8 +1509,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
prodesc->fn_tid = procTup->t_self;
prodesc->nargs = procStruct->pronargs;
- prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
- prodesc->arg_is_rowtype = (bool *) palloc0(prodesc->nargs * sizeof(bool));
+ prodesc->arg_out_func = palloc0_array(FmgrInfo, prodesc->nargs);
+ prodesc->arg_is_rowtype = palloc0_array(bool, prodesc->nargs);
MemoryContextSwitchTo(oldcontext);
/* Remember if function is STABLE/IMMUTABLE */
@@ -1576,7 +1577,6 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
************************************************************/
if (!is_trigger && !is_event_trigger)
{
- proc_internal_args[0] = '\0';
for (i = 0; i < prodesc->nargs; i++)
{
Oid argtype = procStruct->proargtypes.values[i];
@@ -1609,8 +1609,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
}
if (i > 0)
- strcat(proc_internal_args, " ");
- strcat(proc_internal_args, buf);
+ Tcl_DStringAppend(&proc_internal_args, " ", -1);
+ Tcl_DStringAppend(&proc_internal_args, buf, -1);
ReleaseSysCache(typeTup);
}
@@ -1618,13 +1618,14 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
else if (is_trigger)
{
/* trigger procedure has fixed args */
- strcpy(proc_internal_args,
- "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
+ Tcl_DStringAppend(&proc_internal_args,
+ "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args",
+ -1);
}
else if (is_event_trigger)
{
/* event trigger procedure has fixed args */
- strcpy(proc_internal_args, "TG_event TG_tag");
+ Tcl_DStringAppend(&proc_internal_args, "TG_event TG_tag", -1);
}
/************************************************************
@@ -1637,7 +1638,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
************************************************************/
Tcl_DStringAppendElement(&proc_internal_def, "proc");
Tcl_DStringAppendElement(&proc_internal_def, internal_proname);
- Tcl_DStringAppendElement(&proc_internal_def, proc_internal_args);
+ Tcl_DStringAppendElement(&proc_internal_def,
+ Tcl_DStringValue(&proc_internal_args));
/************************************************************
* prefix procedure body with
@@ -1717,6 +1719,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
MemoryContextDelete(proc_cxt);
Tcl_DStringFree(&proc_internal_def);
Tcl_DStringFree(&proc_internal_body);
+ Tcl_DStringFree(&proc_internal_args);
PG_RE_THROW();
}
PG_END_TRY();
@@ -1745,6 +1748,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
Tcl_DStringFree(&proc_internal_def);
Tcl_DStringFree(&proc_internal_body);
+ Tcl_DStringFree(&proc_internal_args);
ReleaseSysCache(procTup);
@@ -2028,7 +2032,7 @@ pltcl_quote(ClientData cdata, Tcl_Interp *interp,
* grow to and initialize pointers
************************************************************/
cp1 = Tcl_GetStringFromObj(objv[1], &length);
- tmp = palloc(length * 2 + 1);
+ tmp = palloc(add_size(mul_size(length, 2), 1));
cp2 = tmp;
/************************************************************
@@ -2583,12 +2587,12 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
"PL/Tcl spi_prepare query",
ALLOCSET_SMALL_SIZES);
MemoryContextSwitchTo(plan_cxt);
- qdesc = (pltcl_query_desc *) palloc0(sizeof(pltcl_query_desc));
+ qdesc = palloc0_object(pltcl_query_desc);
snprintf(qdesc->qname, sizeof(qdesc->qname), "%p", qdesc);
qdesc->nargs = nargs;
- qdesc->argtypes = (Oid *) palloc(nargs * sizeof(Oid));
- qdesc->arginfuncs = (FmgrInfo *) palloc(nargs * sizeof(FmgrInfo));
- qdesc->argtypioparams = (Oid *) palloc(nargs * sizeof(Oid));
+ qdesc->argtypes = palloc_array(Oid, nargs);
+ qdesc->arginfuncs = palloc_array(FmgrInfo, nargs);
+ qdesc->argtypioparams = palloc_array(Oid, nargs);
MemoryContextSwitchTo(oldcontext);
/************************************************************
@@ -2831,7 +2835,7 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
* Setup the value array for SPI_execute_plan() using
* the type specific input functions
************************************************************/
- argvalues = (Datum *) palloc(callObjc * sizeof(Datum));
+ argvalues = palloc_array(Datum, callObjc);
for (j = 0; j < callObjc; j++)
{
@@ -3202,7 +3206,7 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
attinmeta = NULL;
}
- values = (char **) palloc0(tupdesc->natts * sizeof(char *));
+ values = palloc0_array(char *, tupdesc->natts);
if (kvObjc % 2 != 0)
ereport(ERROR,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index fc42d418bf1..1863183dab1 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -43,6 +43,10 @@ ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM
CREATE TRANSFORM FOR int LANGUAGE SQL (
FROM SQL WITH FUNCTION prsd_lextype(internal),
TO SQL WITH FUNCTION int4recv(internal));
+-- make a function that uses it too, mainly to exercise pg_dump
+CREATE FUNCTION public.sql_func_with_transform(int) RETURNS int LANGUAGE sql
+AS 'select $1 + 1'
+TRANSFORM FOR TYPE int;
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 82c4805d49b..b26f447b99e 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -1395,6 +1395,9 @@ CREATE TABLE test5a (a int, b priv_testdomain1);
ERROR: permission denied for type priv_testdomain1
CREATE TABLE test6a OF priv_testtype1;
ERROR: permission denied for type priv_testtype1
+CREATE TABLE test6a2 (a int, b text);
+ALTER TABLE test6a2 OF priv_testtype1;
+ERROR: permission denied for type priv_testtype1
CREATE TABLE test10a (a int[], b priv_testtype1[]);
ERROR: permission denied for type priv_testtype1
CREATE TABLE test9a (a int, b int);
@@ -1426,6 +1429,8 @@ CREATE FUNCTION priv_testfunc6b(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS
CREATE OPERATOR !! (PROCEDURE = priv_testfunc5b, RIGHTARG = priv_testdomain1);
CREATE TABLE test5b (a int, b priv_testdomain1);
CREATE TABLE test6b OF priv_testtype1;
+CREATE TABLE test6b2 (a int, b text);
+ALTER TABLE test6b2 OF priv_testtype1;
CREATE TABLE test10b (a int[], b priv_testtype1[]);
CREATE TABLE test9b (a int, b int);
ALTER TABLE test9b ADD COLUMN c priv_testdomain1;
@@ -1446,6 +1451,7 @@ DROP FUNCTION priv_testfunc5b(a priv_testdomain1);
DROP FUNCTION priv_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/psql.out b/src/test/regress/expected/psql.out
index ab8f55b7358..12029d5cfd0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4659,6 +4659,22 @@ invalid command \lo
\echo 'should print #8-1'
should print #8-1
\endif
+-- test that begin/end matching ignores to-be-ignored text
+create function silly_function(int) returns int
+begin atomic select $1;
+\if false
+end
+\endif
+;
+end;
+\sf silly_function(int)
+CREATE OR REPLACE FUNCTION public.silly_function(integer)
+ RETURNS integer
+ LANGUAGE sql
+BEGIN ATOMIC
+ SELECT $1;
+END
+drop function silly_function(int);
-- :{?...} defined variable test
\set i 1
\if :{?i}
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 0833e0da736..06f132fede1 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1556,6 +1556,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
-- (type float8range was already made in test_setup.sql)
diff --git a/src/test/regress/expected/rangetypes_optimizer.out b/src/test/regress/expected/rangetypes_optimizer.out
index 65b38d385a7..6c434170aa6 100644
--- a/src/test/regress/expected/rangetypes_optimizer.out
+++ b/src/test/regress/expected/rangetypes_optimizer.out
@@ -1553,6 +1553,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
-- (type float8range was already made in test_setup.sql)
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 1a6c61f49d5..119ef9ed02a 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -46,6 +46,10 @@ ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM
CREATE TRANSFORM FOR int LANGUAGE SQL (
FROM SQL WITH FUNCTION prsd_lextype(internal),
TO SQL WITH FUNCTION int4recv(internal));
+-- make a function that uses it too, mainly to exercise pg_dump
+CREATE FUNCTION public.sql_func_with_transform(int) RETURNS int LANGUAGE sql
+AS 'select $1 + 1'
+TRANSFORM FOR TYPE int;
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index d530d14fd8b..89a01ed421e 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -966,6 +966,8 @@ CREATE OPERATOR !+! (PROCEDURE = int4pl, LEFTARG = priv_testdomain1, RIGHTARG =
CREATE TABLE test5a (a int, b priv_testdomain1);
CREATE TABLE test6a OF priv_testtype1;
+CREATE TABLE test6a2 (a int, b text);
+ALTER TABLE test6a2 OF priv_testtype1;
CREATE TABLE test10a (a int[], b priv_testtype1[]);
CREATE TABLE test9a (a int, b int);
@@ -1001,6 +1003,8 @@ CREATE OPERATOR !! (PROCEDURE = priv_testfunc5b, RIGHTARG = priv_testdomain1);
CREATE TABLE test5b (a int, b priv_testdomain1);
CREATE TABLE test6b OF priv_testtype1;
+CREATE TABLE test6b2 (a int, b text);
+ALTER TABLE test6b2 OF priv_testtype1;
CREATE TABLE test10b (a int[], b priv_testtype1[]);
CREATE TABLE test9b (a int, b int);
@@ -1025,6 +1029,7 @@ DROP FUNCTION priv_testfunc5b(a priv_testdomain1);
DROP FUNCTION priv_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/psql.sql b/src/test/regress/sql/psql.sql
index 574843cc00e..aaadadb4da5 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1044,6 +1044,17 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\echo 'should print #8-1'
\endif
+-- test that begin/end matching ignores to-be-ignored text
+create function silly_function(int) returns int
+begin atomic select $1;
+\if false
+end
+\endif
+;
+end;
+\sf silly_function(int)
+drop function silly_function(int);
+
-- :{?...} defined variable test
\set i 1
\if :{?i}
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 2fc7dcea7e4..d66f18be5a8 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -435,6 +435,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
-- (type float8range was already made in test_setup.sql)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 0b1b1df3b4a..7598eb60780 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2177,6 +2177,7 @@ PsqlScanQuoteType
PsqlScanResult
PsqlScanState
PsqlScanStateData
+PsqlScanStateSave
PsqlSettings
Publication
PublicationActions