From 32c2adc06f31a3bf09ca0b5f8adc7236ae758bab Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 16 Jul 2026 17:10:32 -0400 Subject: [PATCH] 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. Closes GH-22778 --- ext/dba/dba_cdb.c | 28 +++++++++++++++++++++++++++ ext/dba/tests/dba_cdb_oob.phpt | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 ext/dba/tests/dba_cdb_oob.phpt diff --git a/ext/dba/dba_cdb.c b/ext/dba/dba_cdb.c index 10a381b579da..d07b3ba4f8c9 100644 --- a/ext/dba/dba_cdb.c +++ b/ext/dba/dba_cdb.c @@ -54,6 +54,7 @@ typedef struct { #endif uint32 eod; /* size of constant database */ uint32 pos; /* current position for traversing */ + size_t file_size; } dba_cdb; DBA_OPEN_FUNC(cdb) @@ -110,6 +111,22 @@ DBA_OPEN_FUNC(cdb) #endif cdb->file = file; +#ifdef DBA_CDB_BUILTIN + if (!make) { + php_stream_statbuf ssb; + if (php_stream_stat(file, &ssb) == 0 && ssb.sb.st_size > 0) { + cdb->file_size = ssb.sb.st_size; + } + } +#else + { + zend_stat_t sb; + if (zend_fstat(file, &sb) == 0 && sb.st_size > 0) { + cdb->file_size = sb.st_size; + } + } +#endif + pinfo->dbf = cdb; return SUCCESS; } @@ -159,6 +176,9 @@ DBA_FETCH_FUNC(cdb) } } len = cdb_datalen(&cdb->c); + if (len > cdb->file_size) { + return NULL; + } fetched_val = zend_string_alloc(len, /* persistent */ false); if (php_cdb_read(&cdb->c, ZSTR_VAL(fetched_val), len, cdb_datapos(&cdb->c)) == -1) { @@ -262,6 +282,10 @@ DBA_FIRSTKEY_FUNC(cdb) uint32_unpack(buf, &klen); uint32_unpack(buf + 4, &dlen); + if (klen > cdb->file_size) { + return NULL; + } + key = zend_string_alloc(klen, /* persistent */ false); if (cdb_file_read(cdb->file, ZSTR_VAL(key), klen) < klen) { zend_string_release_ex(key, /* persistent */ false); @@ -293,6 +317,10 @@ DBA_NEXTKEY_FUNC(cdb) uint32_unpack(buf, &klen); uint32_unpack(buf + 4, &dlen); + if (klen > cdb->file_size) { + return NULL; + } + key = zend_string_alloc(klen, /* persistent */ false); if (cdb_file_read(cdb->file, ZSTR_VAL(key), klen) < klen) { zend_string_release_ex(key, /* persistent */ false); diff --git a/ext/dba/tests/dba_cdb_oob.phpt b/ext/dba/tests/dba_cdb_oob.phpt new file mode 100644 index 000000000000..4d8297ad6ac1 --- /dev/null +++ b/ext/dba/tests/dba_cdb_oob.phpt @@ -0,0 +1,35 @@ +--TEST-- +DBA CDB handler bounds a record length that exceeds the file +--EXTENSIONS-- +dba +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(false) +done