Skip to content

Commit f97387a

Browse files
committed
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4: Fix pdo_odbc output-buffer leak and stale-binding out-of-bounds read
2 parents 670ccf8 + bdcf9ce commit f97387a

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
@@ -135,6 +135,11 @@ static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S)
135135
}
136136
}
137137

138+
static void odbc_free_out_buffer(zval *el)
139+
{
140+
efree(Z_PTR_P(el));
141+
}
142+
138143
static int odbc_stmt_dtor(pdo_stmt_t *stmt)
139144
{
140145
pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
@@ -151,11 +156,41 @@ static int odbc_stmt_dtor(pdo_stmt_t *stmt)
151156
if (S->convbuf) {
152157
efree(S->convbuf);
153158
}
159+
if (S->out_buffers) {
160+
zend_hash_destroy(S->out_buffers);
161+
FREE_HASHTABLE(S->out_buffers);
162+
}
154163
efree(S);
155164

156165
return 1;
157166
}
158167

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

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

172225
while (rc == SQL_NEED_DATA) {
@@ -310,6 +363,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
310363
if (P) {
311364
efree(P);
312365
}
366+
S->params_dirty = true;
313367
break;
314368

315369
case PDO_PARAM_EVT_ALLOC:
@@ -384,6 +438,11 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
384438
}
385439
P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1));
386440
P->outbuflen = P->len;
441+
if (!S->out_buffers) {
442+
ALLOC_HASHTABLE(S->out_buffers);
443+
zend_hash_init(S->out_buffers, HT_MIN_SIZE, NULL, odbc_free_out_buffer, false);
444+
}
445+
zend_hash_next_index_insert_ptr(S->out_buffers, P->outbuf);
387446
}
388447
}
389448

@@ -392,20 +451,12 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
392451
return 0;
393452
}
394453

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

411462
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
@@ -145,16 +145,22 @@ typedef struct {
145145
pdo_odbc_errinfo einfo;
146146
char *convbuf;
147147
zend_ulong convbufsize;
148+
HashTable *out_buffers;
148149
unsigned going_long:1;
149150
unsigned assume_utf8:1;
151+
unsigned params_dirty:1;
150152
signed col_count:16;
151-
unsigned _spare:14;
153+
unsigned _spare:13;
152154
} pdo_odbc_stmt;
153155

154156
typedef struct {
155157
SQLLEN len;
156158
SQLLEN outbuflen;
157159
SQLSMALLINT paramtype;
160+
SQLSMALLINT sqltype;
161+
SQLSMALLINT ctype;
162+
SQLSMALLINT scale;
163+
SQLULEN precision;
158164
char *outbuf;
159165
unsigned is_unicode:1;
160166
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)