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