This omission allowed roles without USAGE on a type to create
tables that depend on it, which could prevent the owner from
changing the type later.
Reported-by: Nathan Bossart <nathandbossart@gmail.com>
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Security: CVE-2026-6470
Backpatch-through: 14
This omission allowed roles without USAGE on a type to create
stored expressions that depend on it, which could prevent the owner
from changing the type later.
The checks deliberately live in the command paths rather than the
dependency-recording routines. Those routines also run whenever
the server re-derives an existing expression, and re-checking there
would break routine maintenance for an owner who has since lost
USAGE on a type its objects already reference. (Checking in the
dependency-recording routines would also require additional
CommandCounterIncrement() calls to avoid spurious errors.)
The addition of a parameter to AlterDomainAddConstraint() breaks
ABI compatibility, but we are unaware of any impacted third-party
code.
Reported-by: Noah Misch <noah@leadboat.com>
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Security: CVE-2026-6470
Backpatch-through: 14
This omission allowed roles without USAGE on a type to create range
types that depend on it, which could prevent the owner from
changing the type later.
Reported-by: Jingzhou Fu <fuboat@outlook.com>
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Security: CVE-2026-6470
Backpatch-through: 14
levenshtein() and levenshtein_less_equal() let the caller specify
the insertion, deletion, and substitution costs, and
fuzzystrmatch's corresponding SQL functions accept any 32-bit
integer for each. Since the distances are calculated with 32-bit
arithmetic, large costs can cause overflows, thereby producing
nonsensical results. Certain inputs to levenshtein_less_equal()
can even cause out-of-bounds writes. To fix, use 64-bit arithmetic
instead, and error whenever the final result won't fit in the
returned 32-bit integer.
We may want to teach these functions to reject negative costs, too,
but that didn't seem appropriate for a security fix, and therefore
it is left as a future exercise.
Reported-by: Ben Morris in collaboration with Claude and Anthropic Research
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com>
Security: CVE-2026-15742
Backpatch-through: 14
When an EXECUTE or FETCH statement is executed, there are two portals:
an outer portal that is created for the EXECUTE or FETCH statement itself,
and an inner portal for the statement being executed on its behalf.
Before this commit, nothing checked that these two portals agreed on
the tuple descriptor of the rows being returned. This can be leveraged
to disclose server memory contents and achieve arbitrary code execution.
To prevent that, we can make use of an existing safety mechanism,
added by Tom Lane in commit 2f48ede080,
which allows a tuplestore DestReceiver to be informed of the tupleDesc
required by the caller, and which will cause an ERROR to occur if
that doesn't match the tupleDesc of what emerges from the executor
(modulo dropped columns, which aren't an issue in the case at hand).
Reported-by: Ben Morris in collaboration with Claude and Anthropic Research
Reported-by: Peter Geoghegan <pg@bowt.ie>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Security: CVE-2026-16239
REPLICATION users were not previously subject to restrictions on output
plugin paths, so they were able to bypass LOAD-time protections during
logical decoding. Unfortunately, adding the standard LOAD restrictions
now would retroactively require all third-party output plugins to be
installed under the $libdir/plugins directory. This would prevent the
use of dynamic_library_path, introduce a wire incompatibility for
clients, and require all plugin authors to check that their libraries
are safe for use by any unprivileged user; we want to avoid that.
Instead, introduce an output_plugin_libraries GUC so that DBAs can
specify the output plugins that are trusted for use in logical decoding.
For simplicity, superusers are subject to the restriction as well
(though they're free to modify the GUC at will during a session, so no
power is actually lost).
The default setting is 'pgoutput, test_decoding'. If other third-party
plugins are in use, DBAs will need to modify this parameter after they
update. Some pointers have been added to the documentation to assist
with this.
Author: Jacob Champion <jacob.champion@enterprisedb.com>
Reported-by: Vladimir Tokarev <vladimirelitokarev@gmail.com>
Reported-by: Yu Kunpeng <yu443940816@live.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Reviewed-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Backpatch-through: 14
Security: CVE-2026-6471
This is needed so that all versions of pg_upgrade that migrate logical
replication slots can parse the new output_plugin_libraries GUC.
Backpatch-through: 17
Security: CVE-2026-6471
A grantee of amcheck function EXECUTE privilege could execute arbitrary
functions as the owners of expression indexes that depend on the search
path. An expression like (lower(col)) was not vulnerable, because
lower() is resolved at CREATE INDEX time. However, an expression
calling an sql-language or plpgsql-language function often was
vulnerable, even if it used search_path only to find objects in
pg_catalog. The amcheck documentation has been warning about data
disclosure after such a GRANT, not about function execution.
This might cause new amcheck errors when index expressions rely on a
broader search_path. Such indexes have seen errors during auto-analyze
since CVE-2018-1058 commit 582edc369c, and
v17 amcheck always worked this way. Hence, the risk is low.
Leave a comment on the one other sandbox entrance that doesn't empty
search_path. In its case, the choice was valid.
Back-patch to v14 (all supported versions), but v17 was safe already.
Commit 2af07e2f74 (v17) unintentionally
blocked the attack, and commit d70b17636d
(v18) unintentionally removed that protection. Hence, this adds to v17
just a test and a comment. While emptying search_path became more
widespread in commit 2af07e2f74 (v17),
none of its other changes blocked an attack available in v16, even when
considering GRANT. For example, brin_summarize_range() has had an owner
check that GRANT does not override.
Reported-by: 王跃林 <violin0613@tju.edu.cn>
Reported-by: Jacob Brazeal <jacob.brazeal@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14673
Presently, mock_scram_secret() always uses
SCRAM_SHA_256_DEFAULT_ITERATIONS, which poses an observable
response discrepancy hazard when scram_iterations is set to
something else. To fix, use the value of the configuration
parameter instead, and document that unauthenticated users can
discover the existence of roles with passwords created with
different iteration counts.
Reported-by: Radim Marek <radim@boringsql.com>
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi>
Reviewed-by: Jacob Champion <champion.p@gmail.com>
Security: CVE-2026-14672
Backpatch-through: 16
The parser accepts any string as an EXTRACT() field name, but
deparsing does not quote and escape it accordingly. To fix, quote
and escape the field name during deparsing as needed. It might be
a good idea to validate the field name during parsing and
deparsing, too, but that is left as a future exercise.
Reported-by: Ben Morris in collaboration with Claude and Anthropic Research
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Etsuro Fujita <etsuro.fujita@gmail.com>
Security: CVE-2026-15741
Backpatch-through: 14
statatt_get_type() unconditionally converted multirange types to their
underlying range type. This choice affected all the type information,
like atttypid, atttyptype and operators.
This made the bounds histogram work correctly (range type is required),
but it was wrong for all the other stat kinds. MCV values for a
multirange column should be parsed as multirange arrays, not range
arrays. It also made the TYPTYPE_MULTIRANGE check for the range stats
validation as dead code, since atttyptype was always TYPTYPE_RANGE
after the conversion due to the centralized statatt_get_type().
pg_restore_extended_stats() handles the same case correctly: it keeps
the original type and explicitly converts to the range type only at the
point where range_histogram_bounds is built.
The fix of this issue is simple: the multirange-to-range conversion
needs to be moved from the centralized statatt_get_type() up to where
attribute stats build their range_histogram_bounds, matching what is
done for extended statistics restore.
The regression tests for multiranges with attribute stats are extended
to cover this case.
Author: OpenAI Security Research Team
Backpatch-through: 18
Security: CVE-2026-16238
When a direct SSL connection was established, ProcessStartupPacket()
still accepted GSSEncRequest messages. The GSSAPI negotiation would
then use raw writes and reads, bypassing the TLS encryption layer.
After the GSS encryption was established, the connection continued to
use TLS. This could betray the HBA rules so as the backend does
protocol exchanges inconsistent with the connection policies in place,
with TLS taking priority over GSS in the backend.
The SSL negotiation path already guarded against attempts to request
SSL after a direct SSL request has been processed. The GSS path is now
guarded the same way when receiving a startup packet.
Reported-by: p4p3r <kbfanta@naver.com>
Author: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Jacob Champion <champion.p@gmail.com>
Backpatch-through: 17
Security: CVE-2026-14681
ECPG assumes that any bytea data it receives from a backend starts with
'\x' as its first two bytes, but a check was missed to enforce that. A
rogue server sending some garbage bytea data would be able to crash a
client, resulting in a client-side DoS, in the most common cases.
Reported-by: ylwangtju <ylwangtju@qq.com>
Backpatch-through: 14
Security: CVE-2026-16241
Some of these could overflow on 32-bit systems with the right input.
Convert all cases where we called palloc() with multiplication to fix
them. Not all of them were bugs, but it's better to be safe than
sorry.
Reported-by: Tulya Project, Team Dhiutsa, Bitecope Technologies Private Ltd
Backpatch-through: 14
Security: CVE-2026-14677
The CACHESIGN.sign field is a BITVECP, not a TRGM, so you should not
use GETSIGN() on it. You don't get a compiler warning because the
GETSIGN() macro includes a cast. It resulted in a bogus read beyond
end of buffer, which would cause bad split decisions or a crash if
you're very unlucky.
Reported-by: Mehmet D. INCE <mehmet@mehmetince.net>
Backpatch-through: 14
Security: CVE-2026-14678
Since commit 62d712ecfd, pg_stat_statements has been underestimating
the size of the result buffer possible for a normalized query, in cases
where the query includes many squashable lists, causing the normalized
query to write past the allocated area.
The allocated buffer size forgot to account for the comment appended in
a squashable list, "/*, ... */". Instead of trying to track down
precisely how much space we need, fix by switch to using an expansible
StringInfo. This not only fixes the bug, but it also makes the code
simpler to follow.
Author: Álvaro Herrera <alvherre@kurilemu.de>
Reported-by: Sajeeb Lohani with TrendAI Zero Day Initiative
Reported-by: Yuelin Wang <3020001251@tju.edu.cn>
Diagnosed-by: Michaël Paquier <michael@paquier.xyz>
Backpatch-through: 18
Security: CVE-2026-14676
Discussion: https://postgr.es/m/19528-7290dd7e6f7dcc22@postgresql.org
When ALTER TABLE ... ALTER COLUMN TYPE (or any subcommand that rebuilds
them) drops and re-creates the extended statistics objects depending on
the altered column, the re-created objects were owned by the role running
ALTER TABLE rather than by the original owner of the statistics.
Remember each object's owner before dropping it, and restore it on
re-creation.
CreateStatistics()'s signature changes and CreateStatsStmt gains a field,
but no known third-party code calls the former or constructs the latter.
Author: Masahiko Sawada <sawada.mshk@gmail.com>
Reviewed-by: Noah Misch <noah@leadboat.com>
Backpatch-through: 14
Security: CVE-2026-6469
numeric_combine() and several other state-combining functions for
aggregates cheated for the case of both inputs being NULL: they
returned a null pointer without bothering to mark it as a SQL NULL.
This was harmless in the expected usage where the result would be
passed to the same combine function or a related aggregate final
function. But it's bad news from a security standpoint, because
now that value can be passed to an internal-accepting function
even if said function is strict. While a previous patch prevented
such queries from being issued, it seems like good defense-in-depth
to expend the few additional lines of code needed to do this properly.
Comparable functions such as array_agg_combine() already do so.
Reported-by: Amy Burnett (OpenAI Codex Security)
Author: Tom Lane <tgl@sss.pgh.pa.us>
Backpatch-through: 14
Security: CVE-2026-14680
Allowing that is a security hole, since there are many different
functions with different ideas of what their "internal" argument or
result is. We already had a defense against the easy case of
"'foo'::internal", but that turns out to be insufficient. Lock down
both function and operator syntax. Also disallow attempts to cast to
or from type internal; those would mostly fail anyway, but we have
created some holes with features such as CoerceViaIO.
Reported-by: Amy Burnett (OpenAI Codex Security)
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14680
The maximum number of arguments allowed for an aggregate function
is FUNC_MAX_ARGS-1 (since the underlying transfn and/or finalfn
will be called with one more argument). parse_func.c failed to
enforce this, allowing construction of calls that would try to
pass FUNC_MAX_ARGS+1 to the underlying functions, resulting in
a memory stomp in the executor. Add correct checking there.
Since it's possible that a bad call has been stored in a view or
SQL function, also add checks in various aggregate-related and
window-function-related code that there are not more than
FUNC_MAX_ARGS arguments. These will also protect us against the
possibility that we're trying to run a stored view that was made
by a server executable with different FUNC_MAX_ARGS. (Arguably,
that scenario does not qualify as a security problem. But let's
just tighten up all of this while we're here, rather than split
hairs over whether an overrun is reachable.)
Likewise check in compute_function_hashkey. Here the hazard is
directly from a pg_proc row, but the scenario is the same.
PL/Tcl has a similar issue with a fixed-size string buffer.
Let's just replace that buffer with a Tcl_DString, removing the
whole issue and making the code look more like what's around it.
There are a lot of other FUNC_MAX_ARGS-sized arrays, but the rest
have nearby guards already, some with comments explicitly pointing
out the hazard of FUNC_MAX_ARGS changing.
I also used palloc_array() in a few related places in funcapi.c.
Those aren't live hazards AFAICS, but nearby code has been
palloc_array-ified already, so it seemed inconsistent to not use
it here.
Reported-by: Masahiko Sawada <sawada.mshk@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14679
The backend doesn't impose any particular limit on the length of this
array, and since there could be entries for both input and output
arguments, it's feasible for the length to exceed FUNC_MAX_ARGS
even without funny business. This could lead to crashes or worse.
Moreover, pg_dump shouldn't rely on hard-coding FUNC_MAX_ARGS in the
first place: it has no business assuming that the backend it's dumping
from was compiled with the same value of FUNC_MAX_ARGS that it is.
So the stanza in dumpFunc() that allocates exactly FUNC_MAX_ARGS space
for the parsed OID array is fundamentally misguided. And it's broken
in another way too: if there are exactly FUNC_MAX_ARGS OIDs, then
parseOidArray won't zero-fill any entries, allowing the subsequent
loop to run off the end of the array. A crash seems unlikely in
this variant, but garbage output is certain.
To fix, redesign parseOidArray's API so that it does the
array-mallocing, which simplifies the callers anyway. While we're
here, tighten and modernize it a bit; in particular, split it into
separate functions for OIDs and integers, as was foreseen long ago.
This lets us get rid of the confusing type-punning involved in
having IndxInfo.indkeys be declared as "Oid *" when it's really
potentially-signed ints. Also, most of the callers expect an exact
number of array entries, so make it verify that not just check for
"too many".
I noted while testing that this dumpFunc() stanza isn't even reached
during check-world. Add a function with transform to the regression
tests to rectify that.
Reported-by: Masahiko Sawada <sawada.mshk@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Backpatch-through: 14
Security: CVE-2026-19385
MatchNamedCall's arggiven array was declared FUNC_MAX_ARGS long,
but we may actually use up to pronallargs elements, and that can
be more than FUNC_MAX_ARGS if the function has OUT arguments
(cf. ProcedureCreate). Convert it to a palloc'd array.
Reported-by: Zheng Yu <zheng@depthfirst.com>
Reported-by: ylwangtju <ylwangtju@qq.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14679
The special case here for estimating conditions involving a ctid
column failed to check that the RHS constant is of type tid.
While that'd always be true for the built-in operators that
reference this selectivity estimator, a maliciously constructed
operator could provide a user-controlled Datum value that would
get interpreted as an ItemPointer pointer. That at least risks
SIGSEGV, and perhaps with a bit of sweat it could be used for
server memory disclosure.
Reported-by: Hcamael <baiyjrh@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Noah Misch <noah@leadboat.com>
Backpatch-through: 14
Security: CVE-2026-14668
Tied arrays might report different sizes each time they are inspected.
To avoid generating a corrupt result array, fix plperl_array_to_datum()
to read av_len() of each input array only once. If the input does
appear to get shorter, we'll fill nulls for the now-missing entries,
which seems fine. Conversely, if it gets longer, we'll ignore the new
entries.
plperl_to_hstore() assumed that Perl's hv_iterinit() returns the
number of entries in the given Perl hash. Usually that's true,
but per the Perl docs, "the return value is currently only meaningful
for hashes without tie magic". That could potentially end in a memory
stomp. We don't depend on that result value anywhere else, so don't
do so here either.
Reported-by: Hcamael <baiyjrh@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Backpatch-through: 14
Security: CVE-2026-14670
setup_regexp_matches() sizes the buffer used to convert matched
substrings back from pg_wchar form at the smaller of maxlen*eml and
the original string's byte length, on the assumption that such a
conversion cannot produce more bytes than the string it came
from. That assumption holds only for validly encoded input. But
pg_mb2wchar_with_len() silently accepts bytes that are invalid in the
database encoding, turning each such byte into one pg_wchar, and
converting that back can take more bytes than the input did. A string
made of such bytes therefore overruns the conversion buffer by up to
its own length, corrupting the following memory. regexp_match(),
regexp_matches(), regexp_split_to_table() and regexp_split_to_array()
are all affected.
Fix by dropping the tighter bound and always allocating maxlen*eml + 1
bytes.
Reported-by: Francesco Verardi <frevadiscor89@gmail.com>
Author: Masahiko Sawada <sawada.mshk@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Backpatch-through: 14
Security: CVE-2026-14664
The only overflow hazards I could find in tsquery construction
are in QTN2QT(), which builds a flat tsquery datum from the
QTNode tree representation used by tsquery_or, tsquery_rewrite,
and allied functions. There are two:
1. It seems theoretically possible for the outputs of cntsize() to
overflow an int, so I widened them to size_t. There's no hazard
certainly in tsquery_or and friends, but tsquery_rewrite could expand
the query tree by large multiples (by replacing many identical
subtrees with a large replacement tree), so in a 64-bit machine
with plenty of available memory it should be possible to build a
QTNode tree large enough to cause that. If these counters did
overflow then we'd under-allocate the output tsquery and have a heap
overwrite problem. size_t is sufficient, since it's counting the size
of a subset of an in-memory data structure. We also have to fix the
TSQUERY_TOO_BIG() macro to not get confused if sumlen exceeds
MaxAllocSize.
2. fillQT() neglects to check that the new "distance" value for a
QI_VAL item fits into the available 20-bit field. It's quite easy
to reach this, for example by tsquery_or'ing two near-megabyte-sized
tsquerys. However, the result is only a corrupt tsquery that does
not represent the expected query, so perhaps this doesn't rise to
the level of a security bug. Nonetheless it should be fixed.
Note: I followed the practice used in other tsquery code of checking
each distance value as it's assigned, which means that the last
operand string could extend past the MAXSTRPOS boundary. This is a
bit different from the pattern used for tsvectors, which insist that
the total data length not exceed MAXSTRPOS and thereby avoid making
per-item checks. Perhaps that should be harmonized sometime, but for
now it's okay for the two types to do this differently as long as
each one is self-consistent.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14662
The core of this patch is to prevent array_to_tsvector() from
generating invalid tsvectors. It did not check for overly-long
lexemes (so that WordEntry.len fields could overflow), nor did it
check that the total "datalen" fits within MAXSTRPOS (so that
WordEntry.pos fields could overflow, and the number of entries
in the tsvector could be much more than the normal limit).
While the field overflows couldn't do anything much worse than
produce a corrupted tsvector value, a sufficiently large number
of tsvector entries could cause integer overflows in later
processing, such as tsvectorout.
Another important fix is to prevent tsvectorrecv() from accepting
invalid tsvectors. The main problem there is that it did not
reject empty-string lexemes. Hence, even though it did (mostly)
enforce the MAXSTRPOS limit, it could still produce a result
with an unreasonable number of tsvector entries, if they were
primarily empty strings.
Also, fix tsvectorout's calculation of its required output
buffer size: it was multiplying the string lengths by
pg_database_encoding_max_length() for no reason. That contributed
to the risk of integer overflow there. With valid tsvector input,
there's no risk, but there's still no reason to make the output
buffer several times bigger than needed.
I also tried to make a couple of related routines more robust,
and spent some effort on improving the comments in ts_type.h.
Also, standardize on a single spelling of the "string is too long
for tsvector" message, using %zu instead of an assortment of formats.
These changes aren't security per se but came out of inspecting the
code for problems.
Reported-by: Yuhang Wu <yuhang@depthfirst.com> and Zhenpeng Lin
Reported-by: Zheng Yu <zheng@depthfirst.com>
Reported-by: Hcamael <baiyjrh@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14662
Commit b53b88109f made the postmaster maintain reachedConsistency in
addition to the startup process. Since the startup process is forked
from the postmaster, it begins life holding whatever value the
postmaster last set.
On a crash reset the postmaster re-forks the startup process while its
own copy still says true: it clears that copy only on receipt of
PMSIGNAL_RECOVERY_STARTED, which the replacement process cannot send
before it exists. The replacement therefore starts out believing the
database is already consistent.
CheckRecoveryConsistency() then skips the minRecoveryPoint comparison
altogether, so hot standby is announced at redo start while replay may
be arbitrarily far behind minRecoveryPoint. Read-only connections are
accepted and answer from heap pages that were flushed ahead of the
replay position, returning wrong results with no error raised. The
same branch also runs XLogCheckInvalidPages() and
CheckTablespaceDirectory(), which are skipped as well, and
log_invalid_page() treats page references that are normal before
consistency as a PANIC.
Fix by clearing reachedConsistency in InitWalRecovery(), so that a
startup process never depends on the value it inherited. The
postmaster's own copy is deliberately left alone: forked backends read
it to choose the "not yet accepting connections" errdetail, and it
converges once the new startup process sends PMSIGNAL_RECOVERY_STARTED
and, on reaching minRecoveryPoint, PMSIGNAL_RECOVERY_CONSISTENT.
Successive crash resets alternate. A startup process that skips the
branch never sends PMSIGNAL_RECOVERY_CONSISTENT, so the postmaster's
copy stays false and the next reset forks a process holding the correct
value; that pass reaches consistency properly, which sets the
postmaster's copy back to true and re-arms the problem for the reset
after it. Roughly every other crash reset is therefore affected, not
just the first one. EXEC_BACKEND builds are unaffected, as
reachedConsistency is not carried in BackendParameters.
Backpatch to v18, where commit b53b88109f introduced this issue.
Reported-by: Eric Ridge <eebbrr@planetscale.com>
Author: Nikhil Sontakke <nikhil@planetscale.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CA+UBoq2n2Zg9rKgMfUtUohzGssisF9cDeyjKqPrnRNFprEyX1Q@mail.gmail.com
Backpatch-through: 18
112c268380 restored failsafe vacuum's abandonment of a buffer
access strategy by clearing the ReadBuffersOperations' strategy
references. But it did so in lazy_scan_heap()'s main loop, meaning it
looped through all the ReadBuffersOperations once per block after
failsafe was engaged. Track it with a local flag and clear the strategy
only once.
Reported-by: Melanie Plageman <melanieplageman@gmail.com>
Discussion: https://postgr.es/m/CAAKRu_Zse14nSNeCgtnE1LUAH8Of7OmYR%2BCc3O_DAzxt3m6T-g%40mail.gmail.com
Backpatch-through: 18
The RI fast-path FK check asserted that the referenced key is never
NULL, in ri_FastPathFlushArray() and in recheck_matched_pk_tuple().
That holds for a primary key, but a foreign key may reference any
unique column, and a UNIQUE column is nullable.
The assertion is reachable under READ COMMITTED. ri_LockPKTuple()
locks the matched PK tuple with TUPLE_LOCK_FLAG_FIND_LAST_VERSION, so
when a concurrent transaction commits a key-changing UPDATE while the
check waits, the lock follows the update chain to the latest version.
If that version now has NULL in the referenced column, the fast path
reaches the assert; in a non-assert build it would compare against the
NULL and treat it as a match.
A NULL referenced key cannot equal any (non-null) FK value, so treat it
as no match and let the ordinary foreign-key violation be raised. This
matches the SPI path, whose requalifying "pkatt = $n" evaluates to NULL
for such a row, so the row is not returned and the check reports a
violation.
Reported-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/20260705210533.ee.noahmisch@microsoft.com
Backpatch-through: 19
The logic for asynchronous Append assumes that pending requests made for
subplans of an Append are drained during ExecReScanAppend. To ensure
that, commit 9e283fc85 modified postgresReScanForeignScan to drain such
a request if any, but failed to take into account that if such a request
was made for a subplan that is re-scanned with parameter changes or
pruned in the next round by runtime pruning, the postgres_fdw callback
function is called after ExecReScanAppend or never called, respectively.
This would cause such a request to remain even after ExecReScanAppend,
leading to incorrect results, an infinite loop, or an assertion failure.
To fix, modify ExecReScanAppend to, for each of the pending requests,
give the FDW a chance to drain that request using the existing
ForeignAsyncConfigureWait/ForeignAsyncNotify callback functions. This
makes the change made to postgresReScanForeignScan useless, so remove it
as well.
Back-patch to v14 where asynchronous Append was added.
Reported-by: Alexander Korotkov <aekorotkov@gmail.com>
Co-authored-by: Alexander Korotkov <aekorotkov@gmail.com>
Co-authored-by: Gleb Kashkin <g.kashkin@postgrespro.ru>
Co-authored-by: Etsuro Fujita <etsuro.fujita@gmail.com>
Reviewed-by: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Reviewed-by: Gleb Kashkin <g.kashkin@postgrespro.ru>
Discussion: https://postgr.es/m/CAPpHfduMOTnV5Zj2KGJ7zanL_10QvccZHtPUaDfJvBhsh9axnQ%40mail.gmail.com
Backpatch-through: 14
AfterTriggerFireDeferred() drained its queued events in a loop, then
called FireAfterTriggerBatchCallbacks() once after the loop to flush
the RI fast-path FK-check batches the fired triggers accumulated.
A batch callback runs user-supplied cast or equality functions, whose
DML can queue further deferred trigger events. Because the flush ran
after the loop had exited, such an event was left in
afterTriggers.events with nothing to fire it, since
AfterTriggerFireDeferred() is the last drainer at commit. The deferred
check was skipped and a row violating the constraint committed.
Move the flush inside the loop, after afterTriggerInvokeEvents(), and
drop the "all fired" break, so afterTriggerMarkEvents() re-checks after
each flush and fires events a flush queued at the correct time. The
other FireAfterTriggerBatchCallbacks() callers leave any queued event
for the eventual commit-time AfterTriggerFireDeferred(), so only the
commit-time firing, which has no later drainer, lost events.
Reported-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/20260705210533.ee.noahmisch@microsoft.com
Backpatch-through: 19
ri_FastPathCheck() called index_beginscan() before switching to the
referenced relation's owner for the permission check and probe. For
btree this has no consequence, but starting the scan before the user id
switch sets a poor example for code dealing with out-of-tree access
methods, whose beginscan could observe the wrong user id. Move
index_beginscan() after the SetUserIdAndSecContext() call.
Reported-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/20260705210533.ee.noahmisch@microsoft.com
Backpatch-through: 19
The RI fast-path check probes the referenced index directly and, for
single-column keys, uses SK_SEARCHARRAY. Both assume the index is a
btree. A comment claimed "PK indexes are always btree", but a foreign
key's referenced index need not be a primary key:
transformFkeyCheckAttrs() accepts any unique (or, for temporal keys,
exclusion) index, so an out-of-tree access method advertising
amcanunique could supply a non-btree index reachable by the fast path.
Add pk_index_is_btree to RI_ConstraintInfo, set from the referenced
index's access method when the constraint is loaded, and make
ri_fastpath_is_applicable() return false for non-btree indexes so such
constraints fall back to the SPI path.
Reported-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/20260705210533.ee.noahmisch@microsoft.com
Backpatch-through: 19
The note claiming ri_FastPathCheck() is only used by the ALTER TABLE
validation path is no longer accurate. Rather than reword it, remove
it; the block comment above RI_FKey_check()'s call to the function
already documents when each path is taken, and a duplicated
cross-reference here would only drift out of date again.
Reported-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/20260705210533.ee.noahmisch@microsoft.com
Backpatch-through: 19
When pgstat_init_entry() fails due to an OOM in the DSA allocation,
pgstat_get_entry_ref() cleaned up the shared hashtable but forgot to
remove the local reference that pgstat_get_entry_ref_cached() had
already inserted into pgStatEntryRefHash.
Missing this cleanup would leave a backend with a stale local cache
entry whose entry_ref points to a NULL shared_stats. If
pgstat_gc_entry_refs() runs with this reference still around, it would
crash due to a pointer dereference.
The local reference is now removed before removing the shared entry,
the order being sensitive to pending interrupts.
Oversight in 8191e0c16a.
Author: Niall Newman <nn@turacolabs.com>
Discussion: https://postgr.es/m/2FDAA194-9CF3-4FD7-A450-F1A4BEB125F6@turacolabs.com
Backpatch-through: 15
VACUUM's wraparound failsafe mode exists to reclaim transaction IDs as
quickly as possible. 4830f10243 made the failsafe stop using the
BAS_VACUUM buffer access strategy so that the rest of the vacuum could
make use of all of shared buffers rather than being confined to the
small strategy ring.
However, when 9256822608 made vacuum's first heap pass use the read
stream, this was accidentally disabled. The read stream keeps its own
references to the buffer access strategy, so clearing vacrel->bstrategy
in lazy_check_wraparound_failsafe() no longer had any effect on the
reads issued by the first pass.
Fix this by adding clearing the BufferAccessStrategy reference actually
being used by the ongoing scan -- those in the ReadBuffersOperations
structs themselves.
Two things we accept rather than fix, as neither is worth the added
complexity given how rarely failsafe mode is reached:
- A small amount of read time for IOs that were already in progress when
the strategy was cleared may be attributed to IOCONTEXT_NORMAL instead
of IOCONTEXT_VACUUM. WaitReadBuffers() derives the IOContext from the
(now cleared) strategy, so the wait time of these in-flight IOs is
misattributed. This is bounded by the stream's look-ahead window and
happens at most once per vacuum, when the strategy is first cleared.
- The stream's buffer pin limit stays lower than it would have been had
no strategy been used at all. max_pinned_buffers is capped by the
strategy's pin limit when the stream is created and is not recomputed
when the strategy is cleared. Raising it would mean building a new,
larger ring, which would require first waiting for all in-progress IOs
to complete. That didn't seem worth it.
Reported-by: Jingtang Zhang <mrdrivingduck@gmail.com>
Discussion: https://postgr.es/m/CAPsk3_APRYVLhAJ5TMwdmpSx8W_%3DPHMm%3DPmKAvnC3gBrfNommQ%40mail.gmail.com
Backpatch-to: 18
Adding a property with the same name multiple times to a label on an
element, either within a single PROPERTIES clause or across statements
via ALTER PROPERTY GRAPH ... ADD PROPERTIES, previously failed with a
unique-index violation on pg_propgraph_label_property. The same class
of bug existed for labels: listing the same label multiple times on
one element in CREATE PROPERTY GRAPH, or adding a label to an element
that already has it via ALTER PROPERTY GRAPH ... ADD LABEL, failed
with a unique-index violation on pg_propgraph_element_label. Detect
the duplicates up front and raise a friendlier error in both cases.
For properties, the cross-statement duplicate is caught by a syscache
probe before insertion. The in-clause duplicate could have been
caught by the same probe if we issued a CommandCounterIncrement()
between property inserts, but that forces a catalog invalidation per
property purely to detect a condition we can check for free on the
in-memory target list. The list-based check is only needed when the
properties are listed explicitly; when they are derived from the
table's attributes, names are already unique.
For labels, a single syscache probe on pg_propgraph_element_label
suffices for both the in-clause and cross-statement cases because
insert_element_record() already issues CommandCounterIncrement()
between successive label inserts.
Author: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Reported-by: Noah Misch <noah@leadboat.com>
Discussion: https://www.postgresql.org/message-id/flat/20260630173053.51.noahmisch%40microsoft.com
Buildfarm member olingo intermittently failed this test, timing out while
waiting for the subscriber log to report an ERROR because
max_prepared_transactions is zero there. The test captured the log offset
only after issuing the publisher's
BEGIN/INSERT/PREPARE TRANSACTION/COMMIT PREPARED sequence.
Since streaming is enabled, the subscriber can receive and apply the
transaction, and log the expected ERROR, before that publisher SQL command
even returns, i.e. before the test captures the offset. The subsequent
wait_for_log() calls then searched only from a point after the message had
already been written, and timed out waiting for it.
Fix by moving the offset capture to before the publisher's transaction is
issued, ensuring it always precedes the point where the ERROR can appear
in the subscriber log.
Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Zhijie Hou <houzj.fnst@fujitsu.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Backpatch-through: 16, where test was introduced
Discussion: https://postgr.es/m/c43753d8-5265-4f77-83ff-9b1167276ec5@gmail.com
The code added in c456e3911 added populate_isnull_array() to bulk
populate the slot's tts_isnull array 8 elements at a time. When tuples
don't have an exact multiple-of-eight number of attributes, this will
lead to populating the tts_isnull elements for attributes that don't
exist in the tuple. This is ok as the array is large enough. However,
if we perform tuple deforming in two passes, and on the first pass
deform *some* of the attributes with slot_getmissingattrs() then later
when we deform the remaining missing attributes, the subsequent call to
populate_isnull_array() would overwrite the tts_isnull values previously
set by slot_getmissingattrs(), and since that function only continues
where it left off, it wouldn't reapply the previously set values and those
would be left as NULLs, as populate_isnull_array() would have set them.
Here, we fix by passing the tuple's natts to slot_getmissingattrs()
rather than the attnum we're deforming from. This means we apply all
the missing attribute values each deform pass, so slightly more work,
but deforming several missing values in different deform passes is
likely exceedingly rare. Doing that seems much better than adding
overhead in the happy path to check for this and skip the subsequent call
to populate_isnull_array().
Author: David Rowley <dgrowleyml@gmail.com>
Reported-by: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-WznHo4b+6AmAj0GZ0jXqDSK69MfHe8fAQwuY_01y7cVNdw@mail.gmail.com
Backpatch-through: 19