Skip to content

Commit b1d3c2a

Browse files
committed
ext/dba: bound CDB record lengths against the file size
The cdb handler passed 32-bit key and data lengths read from the file straight to zend_string_alloc(). On 32-bit a length near UINT32_MAX wraps the struct-size computation to a few bytes while the following read copies the trailing file data past it, overflowing the heap. Capture the file size at open and reject any record length that exceeds it before allocating, in the firstkey, nextkey and fetch handlers.
1 parent 0549c8e commit b1d3c2a

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

ext/dba/dba_cdb.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ typedef struct {
5454
#endif
5555
uint32 eod; /* size of constant database */
5656
uint32 pos; /* current position for traversing */
57+
size_t file_size;
5758
} dba_cdb;
5859

5960
DBA_OPEN_FUNC(cdb)
@@ -110,6 +111,22 @@ DBA_OPEN_FUNC(cdb)
110111
#endif
111112
cdb->file = file;
112113

114+
#ifdef DBA_CDB_BUILTIN
115+
if (!make) {
116+
php_stream_statbuf ssb;
117+
if (php_stream_stat(file, &ssb) == 0 && ssb.sb.st_size > 0) {
118+
cdb->file_size = ssb.sb.st_size;
119+
}
120+
}
121+
#else
122+
{
123+
zend_stat_t sb;
124+
if (zend_fstat(file, &sb) == 0 && sb.st_size > 0) {
125+
cdb->file_size = sb.st_size;
126+
}
127+
}
128+
#endif
129+
113130
pinfo->dbf = cdb;
114131
return SUCCESS;
115132
}
@@ -159,6 +176,9 @@ DBA_FETCH_FUNC(cdb)
159176
}
160177
}
161178
len = cdb_datalen(&cdb->c);
179+
if (len > cdb->file_size) {
180+
return NULL;
181+
}
162182
fetched_val = zend_string_alloc(len, /* persistent */ false);
163183

164184
if (php_cdb_read(&cdb->c, ZSTR_VAL(fetched_val), len, cdb_datapos(&cdb->c)) == -1) {
@@ -262,6 +282,10 @@ DBA_FIRSTKEY_FUNC(cdb)
262282
uint32_unpack(buf, &klen);
263283
uint32_unpack(buf + 4, &dlen);
264284

285+
if (klen > cdb->file_size) {
286+
return NULL;
287+
}
288+
265289
key = zend_string_alloc(klen, /* persistent */ false);
266290
if (cdb_file_read(cdb->file, ZSTR_VAL(key), klen) < klen) {
267291
zend_string_release_ex(key, /* persistent */ false);
@@ -293,6 +317,10 @@ DBA_NEXTKEY_FUNC(cdb)
293317
uint32_unpack(buf, &klen);
294318
uint32_unpack(buf + 4, &dlen);
295319

320+
if (klen > cdb->file_size) {
321+
return NULL;
322+
}
323+
296324
key = zend_string_alloc(klen, /* persistent */ false);
297325
if (cdb_file_read(cdb->file, ZSTR_VAL(key), klen) < klen) {
298326
zend_string_release_ex(key, /* persistent */ false);

ext/dba/tests/dba_cdb_oob.phpt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
DBA CDB handler bounds a record length that exceeds the file
3+
--EXTENSIONS--
4+
dba
5+
--SKIPIF--
6+
<?php
7+
require_once __DIR__ . '/setup/setup_dba_tests.inc';
8+
check_skip('cdb');
9+
?>
10+
--FILE--
11+
<?php
12+
$db_file = __DIR__ . '/dba_cdb_oob.db';
13+
14+
// 2048-byte hash-table header, then a first record whose key-length field is
15+
// 0xFFFFFFFF. That length drove zend_string_alloc() directly; on 32-bit it
16+
// wrapped the struct size to a few bytes while the read copied the trailing
17+
// data past it. eod (bytes 0-3) must exceed 2048 so the record is reached.
18+
$buf = str_repeat("\0", 2048 + 8);
19+
$buf[0] = "\x00"; $buf[1] = "\x00"; $buf[2] = "\x10"; $buf[3] = "\x00"; // eod = 1 MiB
20+
$buf[2048] = "\xff"; $buf[2049] = "\xff"; $buf[2050] = "\xff"; $buf[2051] = "\xff"; // klen
21+
$buf .= str_repeat("A", 8192);
22+
file_put_contents($db_file, $buf);
23+
24+
$db = dba_open($db_file, 'r', 'cdb');
25+
var_dump(dba_firstkey($db));
26+
dba_close($db);
27+
echo "done\n";
28+
?>
29+
--CLEAN--
30+
<?php
31+
@unlink(__DIR__ . '/dba_cdb_oob.db');
32+
?>
33+
--EXPECT--
34+
bool(false)
35+
done

0 commit comments

Comments
 (0)