[php-src] master: ext/dba: fix oob read on malformed length field in dba flatfile handler

alhudz via Gina Peter Banyard <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: alhudz (alhudz)
Committer: Gina Peter Banyard (Girgias)
Date: 2026-07-02T11:51:35+01:00

Commit: https://github.com/php/php-src/commit/a9c7dfefbdcab6b6d2a5c5692f5dcfb5676814ae
Raw diff: https://github.com/php/php-src/commit/a9c7dfefbdcab6b6d2a5c5692f5dcfb5676814ae.diff

ext/dba: fix oob read on malformed length field in dba flatfile handler

Closes GH-22266

Changed paths:
  A  ext/dba/tests/dba_flatfile_oob.phpt
  M  ext/dba/libflatfile/flatfile.c


Diff:

diff --git a/ext/dba/libflatfile/flatfile.c b/ext/dba/libflatfile/flatfile.c
index bd76ecfdd0ae..a68fed7e38c4 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 000000000000..3328e1dcba90
--- /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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.