Parameterize PDO::lastInsertId() sequence-name lookup and fix non-ASCII sequence names#1673
Draft
David-Engel wants to merge 2 commits into
Draft
Parameterize PDO::lastInsertId() sequence-name lookup and fix non-ASCII sequence names#1673David-Engel wants to merge 2 commits into
David-Engel wants to merge 2 commits into
Conversation
Previous code didn't correctly handle non-ASCII bytes
…ssion for lastInsertId Add coverage to pdo_278_lastinsertid_seq.phpt for the parameterized sequence-name lookup: a sequence whose name contains non-ASCII (Unicode) characters now resolves correctly via lastInsertId(), and an injection-style name is treated as a literal value that matches no sequence (returns an empty string) rather than altering the query.
639c0aa to
0eaae1f
Compare
There was a problem hiding this comment.
Pull request overview
This PR hardens and fixes PDO::lastInsertId($name) for SQL Server sequences by parameterizing the sequence-name lookup and ensuring sequence names are encoded using the connection’s configured encoding, addressing failures with non-ASCII sequence names and removing string interpolation from the SQL text.
Changes:
- Replaced interpolated sequence-name lookup with a parameterized query (
WHERE name=?) inpdo_sqlsrv_dbh_last_id. - Bound the sequence name as a character parameter using the connection encoding and added explicit
zvallifetime cleanup. - Extended the functional test to cover a Unicode sequence name and an injection-payload regression case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
source/pdo_sqlsrv/pdo_dbh.cpp |
Parameterizes the sequence current-value lookup and binds the sequence name using the connection encoding with explicit zval lifetime management. |
test/functional/pdo_sqlsrv/pdo_278_lastinsertid_seq.phpt |
Adds coverage for Unicode sequence names and verifies injection payload is treated as a literal name. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $lastRow = $conn->lastInsertId(); | ||
|
|
||
| if ($lastSeq == 3 && $lastRow == 1) { | ||
| // The sequence name passed to lastInsertId() is bound as a parameter. Verify |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #1673 +/- ##
=======================================
Coverage 85.77% 85.78%
=======================================
Files 23 23
Lines 7213 7217 +4
=======================================
+ Hits 6187 6191 +4
Misses 1026 1026
🚀 New features to boost your workflow:
|
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.
Summary
PDO::lastInsertId($name)looks up a sequence's current value viaSELECT current_value FROM sys.sequences WHERE name=.... Previously the$nameargument was interpolated directly into the query string withsnprintf("... WHERE name=N'%s'", name), and the resulting query was always encoded using the system (ANSI) code page (SQLSRV_ENCODING_CHAR).This change parameterizes the lookup (binds
nameas a query parameter) and encodes it using the connection's configured encoding (UTF-8 by default for PDO). This fixes a functional bug and hardens the lookup against string-interpolation issues.Why
1. Functional bug: non-ASCII sequence names
sys.sequences.nameissysname(nvarchar(128)) and fully supports Unicode. Because the old code hard-coded the system code page when encoding the query+name, a sequence whose name contained non-ASCII characters could bemis-encoded and fail to match — so
lastInsertId()returned an empty result for a sequence that exists. Binding the name withdriver_dbh->encoding()honors the connection encoding and resolves these names correctly.2. Defense in depth: parameterize instead of interpolate
The name was concatenated into the SQL text. While
$nameis a developer-supplied schema identifier (not request-routed user input) — so this is not a remotely exploitable injection in normal usage (MSRC severity: None / CVSS 0.0) — parameterizing removes the string-interpolation entirely, which is the correct, safer pattern.Changes
source/pdo_sqlsrv/pdo_dbh.cppSEQUENCE_CURRENT_VALUE_QUERYfrom... WHERE name=N'%s'to... WHERE name=?.LAST_INSERT_ID_QUERY_MAX_LENand thesnprintfbuffer construction.nameviacore_sqlsrv_bind_param(...)usingdriver_dbh->encoding()(the connection encoding) instead of forcingSQLSRV_ENCODING_CHAR.zval name_zlifetime management withzval_ptr_dtorcleanup in both the success and catch paths (the CHAR binding references the zval's buffer until afterSQLExecDirectW).PHP_VERSION_ID < 80100branches for thezend_stringvs.char*name representation.test/functional/pdo_sqlsrv/pdo_278_lastinsertid_seq.phptséquence_Ñ_日本): creates and advances the sequence, then assertslastInsertId($unicodeName) == 1. This exercises the encoding fix.x' UNION ALL SELECT DB_NAME()--and asserts the result is strictly'', proving the payload is treated as a literal name rather than altering the query.--EXPECT-- Donecontract.Testing
Built the extension from source and ran the updated test against a local SQL Server 2022 container:
Done(pass).sequence value or identity does not match as expected), confirming the test detects both the non-ASCII regression and the interpolation behavior.lastInsertId('sequence1') == 3,lastInsertId() == 1) continue to pass.