Commit a9c7dfefbdc for php.net
commit a9c7dfefbdcab6b6d2a5c5692f5dcfb5676814ae
Author: alhudz <al.hudz.k@gmail.com>
Date: Wed Jun 10 11:15:01 2026 +0530
ext/dba: fix oob read on malformed length field in dba flatfile handler
Closes GH-22266
diff --git a/ext/dba/libflatfile/flatfile.c b/ext/dba/libflatfile/flatfile.c
index bd76ecfdd0a..a68fed7e38c 100644
--- a/ext/dba/libflatfile/flatfile.c
+++ b/ext/dba/libflatfile/flatfile.c
@@ -37,6 +37,18 @@
#define FLATFILE_BLOCK_SIZE 1024
+/* Parse the length prefix in `buf` into `num` and grow `buf` to hold it.
+ * atoi() narrows a malformed (e.g. negative) length to a huge size_t whose
+ * `+ FLATFILE_BLOCK_SIZE` would overflow erealloc(); the macro yields true in
+ * that case so the caller stops reading and the read stays within `buf_size`. */
+#define FLATFILE_GROW_BUF(num, buf, buf_size) ( \
+ (num) = atoi(buf), \
+ (num) >= (buf_size) && ( \
+ (num) > SIZE_MAX - FLATFILE_BLOCK_SIZE \
+ || ((buf) = erealloc((buf), (buf_size) = (num) + FLATFILE_BLOCK_SIZE), 0) \
+ ) \
+)
+
/*
* ret = -1 means that database was opened for read-only
* ret = 0 success
@@ -112,10 +124,8 @@ int flatfile_delete(flatfile *dba, datum key_datum) {
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
pos = php_stream_tell(dba->fp);
@@ -135,10 +145,8 @@ int flatfile_delete(flatfile *dba, datum key_datum) {
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
/* read in the value */
num = php_stream_read(dba->fp, buf, num);
@@ -162,10 +170,8 @@ int flatfile_findkey(flatfile *dba, datum key_datum) {
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
num = php_stream_read(dba->fp, buf, num);
@@ -178,10 +184,8 @@ int flatfile_findkey(flatfile *dba, datum key_datum) {
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
num = php_stream_read(dba->fp, buf, num);
}
@@ -202,10 +206,8 @@ datum flatfile_firstkey(flatfile *dba) {
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
num = php_stream_read(dba->fp, buf, num);
@@ -218,10 +220,8 @@ datum flatfile_firstkey(flatfile *dba) {
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
num = php_stream_read(dba->fp, buf, num);
}
@@ -244,20 +244,16 @@ datum flatfile_nextkey(flatfile *dba) {
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
num = php_stream_read(dba->fp, buf, num);
if (!php_stream_gets(dba->fp, buf, 15)) {
break;
}
- num = atoi(buf);
- if (num >= buf_size) {
- buf_size = num + FLATFILE_BLOCK_SIZE;
- buf = erealloc(buf, buf_size);
+ if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+ break;
}
num = php_stream_read(dba->fp, buf, num);
diff --git a/ext/dba/tests/dba_flatfile_oob.phpt b/ext/dba/tests/dba_flatfile_oob.phpt
new file mode 100644
index 00000000000..3328e1dcba9
--- /dev/null
+++ b/ext/dba/tests/dba_flatfile_oob.phpt
@@ -0,0 +1,31 @@
+--TEST--
+DBA FlatFile handler bounds with a malformed (negative) length field
+--EXTENSIONS--
+dba
+--SKIPIF--
+<?php
+require_once __DIR__ . '/setup/setup_dba_tests.inc';
+check_skip('flatfile');
+?>
+--FILE--
+<?php
+$db_file = __DIR__ . '/dba_flatfile_oob.db';
+// A negative length narrows to a huge size_t and previously overran the read buffer.
+file_put_contents($db_file, "-1\n" . str_repeat('A', 200000));
+
+$db = dba_open($db_file, 'r', 'flatfile');
+var_dump(dba_firstkey($db));
+var_dump(dba_exists("AAAA", $db));
+var_dump(dba_fetch("AAAA", $db));
+dba_close($db);
+echo "done\n";
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/dba_flatfile_oob.db');
+?>
+--EXPECT--
+bool(false)
+bool(false)
+bool(false)
+done