From 50e9e72530a4805980384b8ea6672877af816145 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Thu, 26 Sep 2024 22:22:27 +0200
Subject: [PATCH 4/7] Fix GHSA-g665-fm4p-vhff: OOB access in ldap_escape

(cherry picked from commit f9ecf90070a11dad09ca7671a712f81cc2a7d52f)
(cherry picked from commit 9f367d847989b339c33369737daf573e30bab5f1)
---
 ext/ldap/ldap.c                           | 21 ++++++++++++++--
 ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt | 28 ++++++++++++++++++++++
 ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt | 29 +++++++++++++++++++++++
 3 files changed, 76 insertions(+), 2 deletions(-)
 create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
 create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt

diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
index 72a39bd93df..75adf1b5df2 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -49,6 +49,7 @@
 
 #include "ext/standard/php_string.h"
 #include "ext/standard/info.h"
+#include "Zend/zend_exceptions.h"
 
 #ifdef HAVE_LDAP_SASL
 #include <sasl/sasl.h>
@@ -3836,13 +3837,23 @@ static zend_string* php_ldap_do_escape(const zend_bool *map, const char *value,
 	zend_string *ret;
 
 	for (i = 0; i < valuelen; i++) {
-		len += (map[(unsigned char) value[i]]) ? 3 : 1;
+		size_t addend = (map[(unsigned char) value[i]]) ? 3 : 1;
+		if (len > ZSTR_MAX_LEN - addend) {
+			return NULL;
+		}
+		len += addend;
 	}
 	/* Per RFC 4514, a leading and trailing space must be escaped */
 	if ((flags & PHP_LDAP_ESCAPE_DN) && (value[0] == ' ')) {
+		if (len > ZSTR_MAX_LEN - 2) {
+			return NULL;
+		}
 		len += 2;
 	}
 	if ((flags & PHP_LDAP_ESCAPE_DN) && ((valuelen > 1) && (value[valuelen - 1] == ' '))) {
+		if (len > ZSTR_MAX_LEN - 2) {
+			return NULL;
+		}
 		len += 2;
 	}
 
@@ -3909,7 +3920,13 @@ PHP_FUNCTION(ldap_escape)
 		php_ldap_escape_map_set_chars(map, ignores, ignoreslen, 0);
 	}
 
-	RETURN_NEW_STR(php_ldap_do_escape(map, value, valuelen, flags));
+	zend_string *result = php_ldap_do_escape(map, value, valuelen, flags);
+	if (UNEXPECTED(!result)) {
+		zend_throw_exception(NULL, "Argument #1 ($value) is too long", 0);
+		return;
+	}
+
+	RETURN_NEW_STR(result);
 }
 
 #ifdef STR_TRANSLATION
diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
new file mode 100644
index 00000000000..734bbe91d42
--- /dev/null
+++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
@@ -0,0 +1,28 @@
+--TEST--
+GHSA-g665-fm4p-vhff (OOB access in ldap_escape)
+--EXTENSIONS--
+ldap
+--INI--
+memory_limit=-1
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE !== 4) die("skip only for 32-bit");
+if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
+?>
+--FILE--
+<?php
+try {
+    ldap_escape(' '.str_repeat("#", 1431655758), "", LDAP_ESCAPE_DN);
+} catch (Exception $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    ldap_escape(str_repeat("#", 1431655758).' ', "", LDAP_ESCAPE_DN);
+} catch (Exception $e) {
+    echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+ldap_escape(): Argument #1 ($value) is too long
+ldap_escape(): Argument #1 ($value) is too long
diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
new file mode 100644
index 00000000000..5c1b0fb6611
--- /dev/null
+++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
@@ -0,0 +1,29 @@
+--TEST--
+GHSA-g665-fm4p-vhff (OOB access in ldap_escape)
+--EXTENSIONS--
+ldap
+--INI--
+memory_limit=-1
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE !== 4) die("skip only for 32-bit");
+if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
+?>
+--FILE--
+<?php
+try {
+    ldap_escape(str_repeat("*", 1431655759), "", LDAP_ESCAPE_FILTER);
+} catch (Exception $e) {
+    echo $e->getMessage(), "\n";
+}
+
+// would allocate a string of length 2
+try {
+    ldap_escape(str_repeat("*", 1431655766), "", LDAP_ESCAPE_FILTER);
+} catch (Exception $e) {
+    echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+ldap_escape(): Argument #1 ($value) is too long
+ldap_escape(): Argument #1 ($value) is too long
-- 
2.47.0