diff --git a/contrib/pg_trgm/trgm_gist.c b/contrib/pg_trgm/trgm_gist.c
index 6f28db7d1ed..acca96faa47 100644
--- a/contrib/pg_trgm/trgm_gist.c
+++ b/contrib/pg_trgm/trgm_gist.c
@@ -890,7 +890,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
@@ -903,7 +903,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 e8250b090ab..03058e0c40c 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt.out
@@ -423,3 +423,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
'), 'key', 'debug=1');
NOTICE: dbg: parse_compressed_data: bzip2 unsupported
ERROR: Unsupported compression algorithm
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+ERROR: Wrong key or corrupt data
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
+ERROR: Wrong key or corrupt data
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_1.out b/contrib/pgcrypto/expected/pgp-decrypt_1.out
index 63d5ab98654..3e2c84540ce 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
@@ -419,3 +419,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..743e7080033
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,202 @@
+--
+-- PGP encrypt
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+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 pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz');
+ pgp_sym_decrypt_bytea
+-----------------------
+ 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 encode(pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key'), 'hex');
+ encode
+----------------------
+ 310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select encode(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'), 'hex') as result,
+ encode(digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1'), 'hex') as expect;
+ result | expect
+------------------------------------------+------------------------------------------
+ 47bde5d88d6ef8770572b9cbb4278b402aa69966 | 47bde5d88d6ef8770572b9cbb4278b402aa69966
+(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..7731df40775 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 dafa562daa1..d441e3043ea 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;
@@ -220,7 +222,21 @@ cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst,
while (len > 0)
{
- px_cipher_encrypt(ctx->ciph, ctx->fr, ctx->block_size, ctx->fre);
+ int err;
+
+ err = px_cipher_encrypt(ctx->ciph, ctx->fr, ctx->block_size, ctx->fre);
+
+ /*
+ * XXX Ignoring cipher failures is dangerous, but we allow it during
+ * decryption to return to the behavior prior to the fix for
+ * CVE-2026-14663. This lets users recover data from a badly-encrypted
+ * message.
+ */
+ if (err && !ignore_cipher_failure)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
+ errmsg("encrypt error: %s", px_strerror(err))));
+
if (ctx->block_no < 5)
ctx->block_no++;
@@ -251,7 +267,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
@@ -259,5 +276,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 d12dcad1945..d03b097d79d 100644
--- a/contrib/pgcrypto/pgp-decrypt.c
+++ b/contrib/pgcrypto/pgp-decrypt.c
@@ -594,7 +594,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;
@@ -982,7 +983,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;
@@ -1025,7 +1027,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 0536bfb8921..3f42931eeae 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 c945fa3deea..aa64744fa17 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
{
@@ -239,6 +240,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;
@@ -384,6 +386,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 e00e0e657f8..4ce0f6456aa 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
*/
@@ -262,6 +265,7 @@ int pgp_set_compress_level(PGP_Context *ctx, int level);
int pgp_set_text_mode(PGP_Context *ctx, int mode);
int pgp_set_unicode_mode(PGP_Context *ctx, int mode);
int pgp_get_unicode_mode(PGP_Context *ctx);
+int pgp_set_ignore_cipher_failure(PGP_Context *ctx, int ignore);
int pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int klen);
int pgp_set_pubkey(PGP_Context *ctx, MBuf *keypkt,
@@ -282,7 +286,8 @@ int pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int klen);
typedef struct PGP_CFB PGP_CFB;
int pgp_cfb_create(PGP_CFB **ctx_p, int algo,
- const uint8 *key, int key_len, int 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 557948d7c75..089452479c6 100644
--- a/contrib/pgcrypto/sql/pgp-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-decrypt.sql
@@ -313,3 +313,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
=AZ9M
-----END PGP MESSAGE-----
'), 'key', 'debug=1');
+
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
diff --git a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
index 3f2bae9e40b..40a11e0b2dc 100644
--- a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
@@ -601,6 +601,21 @@ blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
-----END PGP MESSAGE-----
');
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
+
-- successful decrypt
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -608,6 +623,9 @@ from keytbl, encdata where keytbl.id=1 and encdata.id=1;
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=3 and encdata.id=3;
@@ -645,3 +663,12 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
-- test for a short read from prefix_init
select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index c2e537c81d9..afc78ee4529 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -923,6 +923,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 3ea3d661bf8..3327c8b8b01 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 f7c1ccad02e..74f138b0b0a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3407,6 +3407,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 be09847022b..24011967681 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1378,7 +1378,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 */
@@ -1402,8 +1402,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 76e939cf54d..1b3ed61002e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -19511,6 +19511,7 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
ObjectAddress tableobj,
typeobj;
HeapTuple classtuple;
+ AclResult aclresult;
/* Validate the type. */
typetuple = typenameType(NULL, ofTypename, NULL);
@@ -19518,6 +19519,10 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
typeform = (Form_pg_type) GETSTRUCT(typetuple);
typeid = typeform->oid;
+ aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typeid);
+
/* Fail if the table has any inheritance parents. */
inheritsRelation = table_open(InheritsRelationId, AccessShareLock);
ScanKeyInit(&key,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index fb47f3275ce..612e5083bc4 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1597,6 +1597,10 @@ DefineRange(CreateRangeStmt *stmt)
errmsg("range subtype cannot be %s",
format_type_be(rangeSubtype))));
+ aclresult = pg_type_aclcheck(rangeSubtype, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, rangeSubtype);
+
/* Identify subopclass */
rangeSubOpclass = findRangeSubOpclass(rangeSubOpclassName, rangeSubtype);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index f36a46cc79f..da891046f33 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
@@ -3011,6 +3025,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/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 58a4bd68a91..3d27cdb55e6 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1972,7 +1972,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_func.c b/src/backend/parser/parse_func.c
index 44acfd9c81b..78900c10b44 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -788,6 +788,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 ...
@@ -868,6 +884,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/tcop/pquery.c b/src/backend/tcop/pquery.c
index e5512bb8271..a992505133b 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1223,8 +1223,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 f4ddfc01059..c60b1bae341 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 dbd5b176e1a..8508a60b8ca 100644
--- a/src/backend/tsearch/ts_parse.c
+++ b/src/backend/tsearch/ts_parse.c
@@ -400,12 +400,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;
@@ -414,13 +432,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 906480c5137..b51f967636e 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -71,7 +71,7 @@ enum RoleRecurseType
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
static List *cached_roles[] = {NIL, NIL};
-static uint32 cached_db_hash;
+uint32 cached_db_hash;
static const char *getid(const char *s, char *n);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index a72546711c9..13a3ba380ec 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -2772,10 +2772,18 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
INVALID_FOR_INTERVAL;
if (tmtcTzn(in))
{
- /* We assume here that timezone names aren't localized */
+ /*
+ * We assume here that timezone abbreviations aren't
+ * localized, so ASCII-only downcasing is sufficient.
+ */
char *p = asc_tolower_z(tmtcTzn(in));
- strcpy(s, p);
+ if (strlen(p) <= n->key->len * DCH_MAX_ITEM_SIZ)
+ strcpy(s, p);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time zone format value too long")));
pfree(p);
s += strlen(s);
}
@@ -2784,7 +2792,14 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
INVALID_FOR_INTERVAL;
if (tmtcTzn(in))
{
- strcpy(s, tmtcTzn(in));
+ const char *p = tmtcTzn(in);
+
+ if (strlen(p) <= n->key->len * DCH_MAX_ITEM_SIZ)
+ strcpy(s, p);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time zone format value too long")));
s += strlen(s);
}
break;
diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index a32c5c82ab4..e5ee52946e9 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -1107,7 +1107,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 */
@@ -1241,23 +1241,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 dbbb2a70a07..3ea861dc6a3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10486,7 +10486,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);
@@ -10506,6 +10506,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 b83c29606c5..4446776c35e 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -604,7 +604,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 7f936427b5f..9340892ccaf 100644
--- a/src/backend/utils/adt/tsquery_util.c
+++ b/src/backend/utils/adt/tsquery_util.c
@@ -288,7 +288,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();
@@ -326,10 +326,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++;
@@ -363,7 +370,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 b02fecc0811..5f52e6d7595 100644
--- a/src/backend/utils/adt/tsvector.c
+++ b/src/backend/utils/adt/tsvector.c
@@ -219,8 +219,8 @@ tsvectorin(PG_FUNCTION_ARGS)
if (cur - tmpbuf > MAXSTRPOS)
ereport(ERROR,
(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
@@ -269,7 +269,8 @@ tsvectorin(PG_FUNCTION_ARGS)
if (buflen > MAXSTRPOS)
ereport(ERROR,
(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);
@@ -313,8 +314,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 *curbegin,
*curin,
@@ -323,7 +324,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]));
}
@@ -454,12 +455,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;
@@ -476,6 +479,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");
@@ -541,6 +546,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 ca23d32d7b3..f3d37c9960b 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -171,6 +171,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;
@@ -497,6 +498,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++)
{
@@ -761,13 +764,30 @@ array_to_tsvector(PG_FUNCTION_ARGS)
deconstruct_array(v, TEXTOID, -1, false, TYPALIGN_INT, &dlexemes, &nulls, &nitems);
- /* Reject nulls (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")));
+
+ 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. */
@@ -781,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. */
@@ -865,9 +890,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++)
@@ -967,6 +998,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;
@@ -1118,7 +1155,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/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 1f1c7635517..304c46fe367 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"
@@ -118,6 +122,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 */
@@ -139,6 +144,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);
}
/*
@@ -2239,6 +2247,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 487a46c30f7..bd68760d5ca 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -1402,7 +1402,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));
}
@@ -1411,7 +1411,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));
}
@@ -1491,7 +1491,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/bin/psql/command.c b/src/bin/psql/command.c
index b00fb1197f5..530a7a4bdf5 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2286,6 +2286,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')
{
@@ -2614,14 +2620,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);
@@ -2639,7 +2654,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;
}
@@ -3067,8 +3082,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,
@@ -3076,8 +3091,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));
}
/*
@@ -3086,9 +3101,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,
@@ -3102,8 +3115,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 074e961e18c..369eb6b41d6 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 063f181345d..96937a94537 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -699,8 +699,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);
+ StaticAssertStmt(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/fe_utils/conditional.c b/src/fe_utils/conditional.c
index a562e28846b..83a1797b3e2 100644
--- a/src/fe_utils/conditional.c
+++ b/src/fe_utils/conditional.c
@@ -45,6 +45,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;
}
@@ -61,6 +62,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;
}
@@ -154,8 +157,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 c64c6557759..68a9c3956e1 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
@@ -93,6 +93,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 e55f1fa2136..4fab2c4bec4 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 8ada9770927..8dc54a9b327 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 7f44f1b14d3..72d0ec09966 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 macros
@@ -218,7 +235,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 49068f04b2f..d6fca62afbb 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -230,6 +230,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 c94907bcc5f..7c7b8fb03a2 100644
--- a/src/interfaces/ecpg/ecpglib/data.c
+++ b/src/interfaces/ecpg/ecpglib/data.c
@@ -529,6 +529,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 b98c547e8be..39db3bb37a0 100644
--- a/src/pl/plperl/SPI.xs
+++ b/src/pl/plperl/SPI.xs
@@ -79,7 +79,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);
@@ -107,7 +107,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);
@@ -127,7 +127,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 c214a1daa91..24a878d9455 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -1084,8 +1084,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);
@@ -1504,7 +1504,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,
@@ -1540,7 +1540,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];
@@ -1790,9 +1790,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 +2800,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 +2808,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 */
@@ -3598,13 +3598,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);
/************************************************************
@@ -3775,8 +3775,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
{
@@ -3888,8 +3888,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 9175358bf52..7b7bf84e529 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -2508,6 +2508,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/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 4ce61a585d1..1f61c62335f 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -1394,6 +1394,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));
@@ -1441,17 +1442,17 @@ 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;
bool isnull;
char *proc_source;
- char buf[48];
+ char buf[64];
Tcl_Interp *interp;
int i;
int tcl_rc;
@@ -1484,7 +1485,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);
@@ -1493,8 +1494,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 */
@@ -1561,7 +1562,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];
@@ -1594,8 +1594,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);
}
@@ -1603,13 +1603,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);
}
/************************************************************
@@ -1622,7 +1623,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
@@ -1704,6 +1706,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();
@@ -1732,6 +1735,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);
@@ -2015,7 +2019,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;
/************************************************************
@@ -2569,12 +2573,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);
/************************************************************
@@ -2816,7 +2820,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++)
{
@@ -3187,7 +3191,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/privileges.out b/src/test/regress/expected/privileges.out
index ee9f8fa1530..598c2ca44ca 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -1081,6 +1081,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);
@@ -1112,6 +1115,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;
@@ -1132,6 +1137,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 800e2761083..3757e95cd52 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4604,6 +4604,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 4e6f580efe6..bfeb57dd4a9 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1495,6 +1495,21 @@ ERROR: range lower bound must be less than or equal to range upper bound
LINE 1: select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)':...
^
set timezone to default;
+-- CREATE TYPE AS RANGE checks for USAGE on subtype
+CREATE ROLE regress_subtype;
+CREATE TYPE mytype AS (a INT, b INT);
+REVOKE USAGE ON TYPE mytype FROM PUBLIC;
+SET ROLE regress_subtype;
+CREATE TYPE myrange AS RANGE (subtype = mytype);
+ERROR: permission denied for type mytype
+RESET ROLE;
+GRANT USAGE ON TYPE mytype TO regress_subtype;
+SET ROLE regress_subtype;
+CREATE TYPE myrange AS RANGE (subtype = mytype);
+RESET ROLE;
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to type myrange
+DROP ROLE regress_subtype;
--
-- Test user-defined range of floats
--
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 456d931b13c..9007b9e7408 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -708,6 +708,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);
@@ -743,6 +745,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);
@@ -767,6 +771,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 36a68595d5e..cf3b890eb04 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1010,6 +1010,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 50707f35529..f62eb8d0837 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -429,6 +429,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
--
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fe36db36936..5f42de85293 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2055,6 +2055,7 @@ PsqlScanQuoteType
PsqlScanResult
PsqlScanState
PsqlScanStateData
+PsqlScanStateSave
PsqlSettings
Publication
PublicationActions