Skip to content

Commit 00dc391

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: Fix pdo_odbc output-buffer leak and stale-binding out-of-bounds read
2 parents 070f8b2 + f97387a commit 00dc391

4 files changed

Lines changed: 129 additions & 15 deletions

File tree

ext/pdo_odbc/odbc_stmt.c

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S)
133133
}
134134
}
135135

136+
static void odbc_free_out_buffer(zval *el)
137+
{
138+
efree(Z_PTR_P(el));
139+
}
140+
136141
static int odbc_stmt_dtor(pdo_stmt_t *stmt)
137142
{
138143
pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
@@ -149,11 +154,41 @@ static int odbc_stmt_dtor(pdo_stmt_t *stmt)
149154
if (S->convbuf) {
150155
efree(S->convbuf);
151156
}
157+
if (S->out_buffers) {
158+
zend_hash_destroy(S->out_buffers);
159+
FREE_HASHTABLE(S->out_buffers);
160+
}
152161
efree(S);
153162

154163
return 1;
155164
}
156165

166+
static bool odbc_bind_param(pdo_stmt_t *stmt, struct pdo_bound_param_data *param)
167+
{
168+
pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
169+
pdo_odbc_param *P = (pdo_odbc_param*)param->driver_data;
170+
RETCODE rc;
171+
172+
if (!P) {
173+
return true;
174+
}
175+
176+
rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1,
177+
P->paramtype, P->ctype, P->sqltype, P->precision, P->scale,
178+
P->paramtype == SQL_PARAM_INPUT ?
179+
(SQLPOINTER)param :
180+
P->outbuf,
181+
P->outbuf ? P->outbuflen : 0,
182+
&P->len
183+
);
184+
185+
if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
186+
return true;
187+
}
188+
pdo_odbc_stmt_error("SQLBindParameter");
189+
return false;
190+
}
191+
157192
static int odbc_stmt_execute(pdo_stmt_t *stmt)
158193
{
159194
RETCODE rc, rc1;
@@ -165,6 +200,24 @@ static int odbc_stmt_execute(pdo_stmt_t *stmt)
165200
SQLCloseCursor(S->stmt);
166201
}
167202

203+
if (S->params_dirty) {
204+
rc = SQLFreeStmt(S->stmt, SQL_RESET_PARAMS);
205+
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
206+
pdo_odbc_stmt_error("SQLFreeStmt: SQL_RESET_PARAMS");
207+
return 0;
208+
}
209+
if (stmt->bound_params) {
210+
struct pdo_bound_param_data *param;
211+
212+
ZEND_HASH_FOREACH_PTR(stmt->bound_params, param) {
213+
if (!odbc_bind_param(stmt, param)) {
214+
return 0;
215+
}
216+
} ZEND_HASH_FOREACH_END();
217+
}
218+
S->params_dirty = false;
219+
}
220+
168221
rc = SQLExecute(S->stmt);
169222

170223
while (rc == SQL_NEED_DATA) {
@@ -308,6 +361,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
308361
if (P) {
309362
efree(P);
310363
}
364+
S->params_dirty = true;
311365
break;
312366

313367
case PDO_PARAM_EVT_ALLOC:
@@ -382,6 +436,11 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
382436
}
383437
P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1));
384438
P->outbuflen = P->len;
439+
if (!S->out_buffers) {
440+
ALLOC_HASHTABLE(S->out_buffers);
441+
zend_hash_init(S->out_buffers, HT_MIN_SIZE, NULL, odbc_free_out_buffer, false);
442+
}
443+
zend_hash_next_index_insert_ptr(S->out_buffers, P->outbuf);
385444
}
386445
}
387446

@@ -390,20 +449,12 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
390449
return 0;
391450
}
392451

393-
rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1,
394-
P->paramtype, ctype, sqltype, precision, scale,
395-
P->paramtype == SQL_PARAM_INPUT ?
396-
(SQLPOINTER)param :
397-
P->outbuf,
398-
P->len,
399-
&P->len
400-
);
401-
402-
if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
403-
return 1;
404-
}
405-
pdo_odbc_stmt_error("SQLBindParameter");
406-
return 0;
452+
P->sqltype = sqltype;
453+
P->ctype = ctype;
454+
P->precision = precision;
455+
P->scale = scale;
456+
S->params_dirty = true;
457+
return 1;
407458
}
408459

409460
case PDO_PARAM_EVT_EXEC_PRE:

ext/pdo_odbc/php_pdo_odbc_int.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,22 @@ typedef struct {
143143
pdo_odbc_errinfo einfo;
144144
char *convbuf;
145145
zend_ulong convbufsize;
146+
HashTable *out_buffers;
146147
unsigned going_long:1;
147148
unsigned assume_utf8:1;
149+
unsigned params_dirty:1;
148150
signed col_count:16;
149-
unsigned _spare:14;
151+
unsigned _spare:13;
150152
} pdo_odbc_stmt;
151153

152154
typedef struct {
153155
SQLLEN len;
154156
SQLLEN outbuflen;
155157
SQLSMALLINT paramtype;
158+
SQLSMALLINT sqltype;
159+
SQLSMALLINT ctype;
160+
SQLSMALLINT scale;
161+
SQLULEN precision;
156162
char *outbuf;
157163
unsigned is_unicode:1;
158164
unsigned _spare:31;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
pdo_odbc: a bound parameter does not leave a stale ODBC binding on re-execute
3+
--EXTENSIONS--
4+
pdo_odbc
5+
--SKIPIF--
6+
<?php
7+
require __DIR__ . '/config.inc';
8+
try {
9+
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
10+
} catch (PDOException $e) {
11+
die("skip requires the SQLite3 ODBC driver");
12+
}
13+
?>
14+
--FILE--
15+
<?php
16+
require __DIR__ . '/config.inc';
17+
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
18+
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
19+
20+
$stmt = $pdo->prepare('SELECT ? AS v');
21+
$v = 'bound';
22+
$stmt->bindParam(1, $v, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 256);
23+
$stmt->execute(['from_array']);
24+
25+
var_dump($stmt->fetch(PDO::FETCH_ASSOC)['v']);
26+
?>
27+
--EXPECT--
28+
string(10) "from_array"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
pdo_odbc: bound output parameter buffer is released (no leak)
3+
--EXTENSIONS--
4+
pdo_odbc
5+
--SKIPIF--
6+
<?php
7+
require __DIR__ . '/config.inc';
8+
try {
9+
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
10+
} catch (PDOException $e) {
11+
die("skip requires the SQLite3 ODBC driver");
12+
}
13+
?>
14+
--FILE--
15+
<?php
16+
require __DIR__ . '/config.inc';
17+
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
18+
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
19+
20+
$stmt = $pdo->prepare('SELECT ? AS v');
21+
$var = 'seed';
22+
$stmt->bindParam(1, $var, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 256);
23+
$stmt->execute();
24+
$row = $stmt->fetch(PDO::FETCH_ASSOC);
25+
26+
var_dump($row['v']);
27+
?>
28+
--EXPECT--
29+
string(4) "seed"

0 commit comments

Comments
 (0)