fix(pg): make prepared-statement caches prototypeless - #3767
Closed
nirmal-shaji wants to merge 1 commit into
Closed
Conversation
The connection tracks already-prepared statements in `parsedStatements`
and `submittedNamedStatements` (and `namedQueries` on the native client),
all created as plain `{}` objects. Because those inherit from
`Object.prototype`, a prepared statement named after a prototype key such
as `constructor` reads back as already-prepared even though it was never
sent to the backend. The client then skips the `Parse` message and issues
a `Bind` for a statement the server has never seen.
Create these caches with `Object.create(null)` so lookups only ever see
statements that were actually prepared.
Fixes brianc#3625
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A prepared statement whose name collides with an
Object.prototypekey — most obviouslyconstructor, but alsohasOwnProperty,toString,__proto__, etc. — is mishandled. The client believes the statement has already been prepared when it has not, so it skips theParsemessage and sends aBindfor a statement the backend has never seen, producing a protocol error rather than running the query.Cause
Connectiontracks which named statements have been prepared using plain object literals:The native
Clientdoes the same withthis.namedQueries = {}. These caches are keyed by user-supplied statement names, andQuery#hasBeenParsed/Query#submittest membership with a simple truthy lookup:Because the objects inherit from
Object.prototype,parsedStatements['constructor']resolves toObject.prototype.constructor(truthy) on a brand-new connection, soParseis never sent.Fix
Create the three caches with
Object.create(null)so lookups only ever reflect statements that were actually prepared. This matches the request in #3625 (makeparsedStatementsprototypeless, alongsidenamedQuerieson the native client);submittedNamedStatementsis fixed too since it is consulted in the same||expression and shares the flaw. No API surface changes.Testing
Added a pure-Node unit test (no live Postgres) at
packages/pg/test/unit/connection/prepared-statement-name-tests.js, mirroring the existingtest/unit/connectiontests and using the in-memory stream helper. It asserts that:Object.prototypenames;constructorsends aParsebefore itsBind;hasBeenParsedis false for unprepared prototype-named statements.The test fails on
masterand passes with this change. The fullpackages/pgunit suite (make test-unit, run file-by-file withnode) remains green.Targeting the default branch (
master); the issue is tagged toward thepg@9.0milestone — happy to retarget if a release branch is preferred.Fixes #3625