Skip to content

Commit a385cfa

Browse files
committed
Fix #63208: BSTR to PHP string conversion not binary safe
A `BSTR` is similar to a `zend_string`; it stores the length of the string just before the actual string, and thus the string may contain NUL bytes. However, `php_com_olestring_to_string()` is supposed to deal with arbitrary `OLECHAR*`s which may not be `BSTR`s, so we introduce `php_com_bstr_to_string()` and use it for the only case where we actually have to deal with `BSTR`s which may contain NUL bytes. Contrary to `php_com_olestring_to_string()` we return a `zend_string`, so we can save the re-allocation when converting to a `zval`. We also cater to `php_com_string_to_olestring()` not being binary safe, with basically the same fix we did for `php_com_olestring_to_string()`.
1 parent 816b4c1 commit a385cfa

File tree

5 files changed

+80
-14
lines changed

5 files changed

+80
-14
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ PHP NEWS
66
. Fixed bug #79030 (Upgrade apache2handler's php_apache_sapi_get_request_time
77
to return usec). (Herbert256)
88

9+
- COM:
10+
. Fixed bug #63208 (BSTR to PHP string conversion not binary safe). (cmb)
11+
912
- Curl:
1013
. Fixed bug #79741 (curl_setopt CURLOPT_POSTFIELDS asserts on object with
1114
declared properties). (Nikita)

ext/com_dotnet/com_olechar.c

+55
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,58 @@ PHP_COM_DOTNET_API char *php_com_olestring_to_string(OLECHAR *olestring, size_t
103103

104104
return string;
105105
}
106+
107+
BSTR php_com_string_to_bstr(zend_string *string, int codepage)
108+
{
109+
BSTR bstr = NULL;
110+
DWORD flags = codepage == CP_UTF8 ? 0 : MB_PRECOMPOSED | MB_ERR_INVALID_CHARS;
111+
size_t mb_len = ZSTR_LEN(string);
112+
int wc_len;
113+
114+
if ((wc_len = MultiByteToWideChar(codepage, flags, ZSTR_VAL(string), (int)mb_len + 1, NULL, 0)) <= 0) {
115+
goto fail;
116+
}
117+
if ((bstr = SysAllocStringLen(NULL, (UINT)(wc_len - 1))) == NULL) {
118+
goto fail;
119+
}
120+
if ((wc_len = MultiByteToWideChar(codepage, flags, ZSTR_VAL(string), (int)mb_len + 1, bstr, wc_len)) <= 0) {
121+
goto fail;
122+
}
123+
return bstr;
124+
125+
fail:
126+
char *msg = php_win32_error_to_msg(GetLastError());
127+
php_error_docref(NULL, E_WARNING,
128+
"Could not convert string to unicode: `%s'", msg);
129+
LocalFree(msg);
130+
SysFreeString(bstr);
131+
return SysAllocString(L"");
132+
}
133+
134+
zend_string *php_com_bstr_to_string(BSTR bstr, int codepage)
135+
{
136+
zend_string *string = NULL;
137+
UINT wc_len = SysStringLen(bstr);
138+
int mb_len;
139+
140+
mb_len = WideCharToMultiByte(codepage, 0, bstr, wc_len + 1, NULL, 0, NULL, NULL);
141+
if (mb_len > 0) {
142+
string = zend_string_alloc(mb_len - 1, 0);
143+
mb_len = WideCharToMultiByte(codepage, 0, bstr, wc_len + 1, ZSTR_VAL(string), mb_len, NULL, NULL);
144+
}
145+
146+
if (mb_len <= 0) {
147+
char *msg = php_win32_error_to_msg(GetLastError());
148+
149+
php_error_docref(NULL, E_WARNING,
150+
"Could not convert string from unicode: `%s'", msg);
151+
LocalFree(msg);
152+
153+
if (string != NULL) {
154+
zend_string_release(string);
155+
}
156+
string = ZSTR_EMPTY_ALLOC();
157+
}
158+
159+
return string;
160+
}

ext/com_dotnet/com_variant.c

+3-14
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ static void safe_array_from_zval(VARIANT *v, zval *z, int codepage)
9696

9797
PHP_COM_DOTNET_API void php_com_variant_from_zval(VARIANT *v, zval *z, int codepage)
9898
{
99-
OLECHAR *olestring;
10099
php_com_dotnet_object *obj;
101100
zend_uchar ztype = IS_NULL;
102101

@@ -164,13 +163,7 @@ PHP_COM_DOTNET_API void php_com_variant_from_zval(VARIANT *v, zval *z, int codep
164163

165164
case IS_STRING:
166165
V_VT(v) = VT_BSTR;
167-
olestring = php_com_string_to_olestring(Z_STRVAL_P(z), Z_STRLEN_P(z), codepage);
168-
if (CP_UTF8 == codepage) {
169-
V_BSTR(v) = SysAllocStringByteLen((char*)olestring, (UINT)(wcslen(olestring) * sizeof(OLECHAR)));
170-
} else {
171-
V_BSTR(v) = SysAllocStringByteLen((char*)olestring, (UINT)(Z_STRLEN_P(z) * sizeof(OLECHAR)));
172-
}
173-
efree(olestring);
166+
V_BSTR(v) = php_com_string_to_bstr(Z_STR_P(z), codepage);
174167
break;
175168

176169
case IS_RESOURCE:
@@ -236,12 +229,8 @@ PHP_COM_DOTNET_API int php_com_zval_from_variant(zval *z, VARIANT *v, int codepa
236229
case VT_BSTR:
237230
olestring = V_BSTR(v);
238231
if (olestring) {
239-
size_t len;
240-
char *str = php_com_olestring_to_string(olestring,
241-
&len, codepage);
242-
ZVAL_STRINGL(z, str, len);
243-
// TODO: avoid reallocation???
244-
efree(str);
232+
zend_string *str = php_com_bstr_to_string(olestring, codepage);
233+
ZVAL_STR(z, str);
245234
olestring = NULL;
246235
}
247236
break;

ext/com_dotnet/php_com_dotnet_internal.h

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ PHP_COM_DOTNET_API char *php_com_olestring_to_string(OLECHAR *olestring,
8989
size_t *string_len, int codepage);
9090
PHP_COM_DOTNET_API OLECHAR *php_com_string_to_olestring(char *string,
9191
size_t string_len, int codepage);
92+
BSTR php_com_string_to_bstr(zend_string *string, int codepage);
93+
zend_string *php_com_bstr_to_string(BSTR bstr, int codepage);
9294

9395

9496
/* com_com.c */

ext/com_dotnet/tests/bug63208.phpt

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Bug #63208 (BSTR to PHP string conversion not binary safe)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('com_dotnet')) die('skip com_dotnet extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
$string = "\u{0905}b\0cd";
10+
$variant = new VARIANT($string, VT_ARRAY | VT_UI1, CP_UTF8); // Array of bytes
11+
$converted = (string) $variant;
12+
var_dump(bin2hex($string));
13+
var_dump(bin2hex($converted));
14+
?>
15+
--EXPECT--
16+
string(14) "e0a48562006364"
17+
string(14) "e0a48562006364"

0 commit comments

Comments
 (0)