Repository: FreeRDP/FreeRDP (C)

Issue:
credentials module is not endian safe
I am getting a failure in the `TestMarshalUnmarshal` test on a s390x (big endian) system (running Fedora 42).

I have tracked it down to this check (https://github.com/FreeRDP/FreeRDP/blob/master/winpr/libwinpr/credentials/credentials.c#L211)
````
BOOL CredUnmarshalCredentialW(LPCWSTR cred, PCRED_MARSHAL_TYPE pcredType, PVOID* out)
{
        if (!cred || !pcredType || !out || cred[0] != '@' || cred[1] != '@')
        {
                SetLastError(ERROR_INVALID_PARAMETER);
                return FALSE;
        }
````
where `cred[0]` and `cred[1]` are `0x4000` on a my big endian system, but `@` (aka `0x40`) is expected.

from a gdb session
````
Breakpoint 1, CredUnmarshalCredentialW (cred=0x1015bb0, pcredType=0x3ffffff7bdc, out=0x3ffffff7bd0) at /home/sharkcz/projects/FreeRDP/winpr/libwinpr/credentials/credentials.c:211
211		if (!cred || !pcredType || !out || cred[0] != '@' || cred[1] != '@')
(gdb) p/x cred[0]
$1 = 0x4000
(gdb) p/x cred[1]
$2 = 0x4000
(gdb) p/x cred[2]
$3 = 0x4200
(gdb) p/x '@'
$4 = 0x40
(gdb) where
#0  CredUnmarshalCredentialW (cred=0x1015bb0, pcredType=0x3ffffff7bdc, out=0x3ffffff7bd0) at /home/sharkcz/projects/FreeRDP/winpr/libwinpr/credentials/credentials.c:211
#1  0x000003fff7e6dd8a in CredUnmarshalCredentialA (cred=0x1001820 "@@BQ9eNR0KWVU-CT8sPCp8z37POZHJ", CredType=0x3ffffff7bdc, Credential=0x3ffffff7bd0)
    at /home/sharkcz/projects/FreeRDP/winpr/libwinpr/credentials/credentials.c:262
#2  0x00000000010013da in TestUnmarshal (argc=1, argv=0x3ffffff8090) at /home/sharkcz/projects/FreeRDP/winpr/libwinpr/credentials/test/TestMarshalUnmarshal.c:45
#3  0x0000000001001604 in TestMarshalUnmarshal (argc=1, argv=0x3ffffff8090) at /home/sharkcz/projects/FreeRDP/winpr/libwinpr/credentials/test/TestMarshalUnmarshal.c:85
#4  0x00000000010012ce in main (ac=1, av=0x3ffffff8090) at /home/sharkcz/projects/FreeRDP/build/winpr/libwinpr/credentials/test/TestCredentials.c:191
````
-DWITH_UNICODE_BUILTIN=ON not endian safe
The implementation in `winpr/libwinpr/crt/unicode_builtin.c` is not endian safe.

see `winpr_ConvertUTF16toUTF8_Internal` and `winpr_ConvertUTF8toUTF16_Internal`


Discussion: a fix could be (after adding a check for big endian platforms)
````
diff --git a/winpr/libwinpr/credentials/credentials.c b/winpr/libwinpr/credentials/credentials.c
index f7e4810a0..b97649f74 100644
--- a/winpr/libwinpr/credentials/credentials.c
+++ b/winpr/libwinpr/credentials/credentials.c
@@ -201,6 +201,7 @@ BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE CredType, PVOID Credential,
        if (!CredMarshalCredentialW(CredType, Credential, &b) || !b)
                return FALSE;
 
+       ByteSwapUnicode(b, _wcslen(b));
        *MarshaledCredential = ConvertWCharNToUtf8Alloc(b, _wcslen(b), NULL);
        free(b);
        return (*MarshaledCredential != NULL);
@@ -259,6 +260,7 @@ BOOL CredUnmarshalCredentialA(LPCSTR cred, PCRED_MARSHAL_TYPE CredType, PVOID* C
        if (!b)
                return FALSE;
 
+       ByteSwapUnicode(b, _wcslen(b));
        BOOL ret = CredUnmarshalCredentialW(b, CredType, Credential);
        free(b);
        return ret;
````
It looks to me that the WCHAR strings are UTF-16LE encoded by default, so to do computations we need to convert them to "native" UTF-16 first.
Thanks, #11790 is much better solution. `TestMarshalUnmarshal` is green again on a s390x system.





Fix (patch):
diff --git a/winpr/libwinpr/credentials/credentials.c b/winpr/libwinpr/credentials/credentials.c
index f7e4810a0537..d1c28cb27065 100644
--- a/winpr/libwinpr/credentials/credentials.c
+++ b/winpr/libwinpr/credentials/credentials.c
@@ -34,7 +34,7 @@
 
 #ifndef _WIN32
 
-static BYTE wchar_decode(WCHAR c)
+static BYTE char_decode(char c)
 {
 	if (c >= 'A' && c <= 'Z')
 		return (BYTE)(c - 'A');
@@ -49,26 +49,26 @@ static BYTE wchar_decode(WCHAR c)
 	return 64;
 }
 
-static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
+static BOOL cred_decode(const char* cred, size_t len, BYTE* buf)
 {
 	size_t i = 0;
-	const WCHAR* p = cred;
+	const char* p = cred;
 
 	while (len >= 4)
 	{
-		BYTE c0 = wchar_decode(p[0]);
+		BYTE c0 = char_decode(p[0]);
 		if (c0 > 63)
 			return FALSE;
 
-		BYTE c1 = wchar_decode(p[1]);
+		BYTE c1 = char_decode(p[1]);
 		if (c1 > 63)
 			return FALSE;
 
-		BYTE c2 = wchar_decode(p[2]);
+		BYTE c2 = char_decode(p[2]);
 		if (c2 > 63)
 			return FALSE;
 
-		BYTE c3 = wchar_decode(p[3]);
+		BYTE c3 = char_decode(p[3]);
 		if (c3 > 63)
 			return FALSE;
 
@@ -82,15 +82,15 @@ static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
 
 	if (len == 3)
 	{
-		BYTE c0 = wchar_decode(p[0]);
+		BYTE c0 = char_decode(p[0]);
 		if (c0 > 63)
 			return FALSE;
 
-		BYTE c1 = wchar_decode(p[1]);
+		BYTE c1 = char_decode(p[1]);
 		if (c1 > 63)
 			return FALSE;
 
-		BYTE c2 = wchar_decode(p[2]);
+		BYTE c2 = char_decode(p[2]);
 		if (c2 > 63)
 			return FALSE;
 
@@ -99,11 +99,11 @@ static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
 	}
 	else if (len == 2)
 	{
-		BYTE c0 = wchar_decode(p[0]);
+		BYTE c0 = char_decode(p[0]);
 		if (c0 > 63)
 			return FALSE;
 
-		BYTE c1 = wchar_decode(p[1]);
+		BYTE c1 = char_decode(p[1]);
 		if (c1 > 63)
 			return FALSE;
 
@@ -117,54 +117,72 @@ static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
 	return TRUE;
 }
 
-static size_t cred_encode(const BYTE* bin, size_t len, WCHAR* cred)
+static size_t cred_encode(const BYTE* bin, size_t len, char* cred, size_t credlen)
 {
-	static const WCHAR encodingChars[] = {
-		/* ABCDEFGHIJKLMNOPQRSTUVWXYZ */
-		0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
-		0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A,
-		/* abcdefghijklmnopqrstuvwxyz */
-		0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
-		0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A,
-		/* 0123456789 */
-		0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-		/* #- */
-		0x23, 0x2d
-	};
+	static const char encodingChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	                                    "abcdefghijklmnopqrstuvwxyz"
+	                                    "0123456789"
+	                                    "#-";
 	size_t n = 0;
-
-	while (len > 0)
+	size_t offset = 0;
+	while (offset < len)
 	{
-		cred[n++] = encodingChars[bin[0] & 0x3f];
-		BYTE x = (bin[0] & 0xc0) >> 6;
-		if (len == 1)
+		if (n >= credlen)
+			break;
+
+		cred[n++] = encodingChars[bin[offset] & 0x3f];
+		BYTE x = (bin[offset] & 0xc0) >> 6;
+		offset++;
+
+		if (offset >= len)
 		{
 			cred[n++] = encodingChars[x];
 			break;
 		}
 
-		cred[n++] = encodingChars[((bin[1] & 0xf) << 2) | x];
-		x = (bin[1] & 0xf0) >> 4;
-		if (len == 2)
+		if (n >= credlen)
+			break;
+		cred[n++] = encodingChars[((bin[offset] & 0xf) << 2) | x];
+		x = (bin[offset] & 0xf0) >> 4;
+		offset++;
+
+		if (offset >= len)
 		{
 			cred[n++] = encodingChars[x];
 			break;
 		}
 
-		cred[n++] = encodingChars[((bin[2] & 0x3) << 4) | x];
-		cred[n++] = encodingChars[(bin[2] & 0xfc) >> 2];
-		bin += 3;
-		len -= 3;
+		if (n >= credlen)
+			break;
+		cred[n++] = encodingChars[((bin[offset] & 0x3) << 4) | x];
+
+		if (n >= credlen)
+			break;
+		cred[n++] = encodingChars[(bin[offset] & 0xfc) >> 2];
+		offset++;
 	}
 	return n;
 }
 
-BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID cred, LPWSTR* MarshaledCredential)
+BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID Credential,
+                            LPWSTR* MarshaledCredential)
+{
+	char* b = NULL;
+	if (!CredMarshalCredentialA(CredType, Credential, &b) || !b)
+		return FALSE;
+
+	*MarshaledCredential = ConvertUtf8ToWCharAlloc(b, NULL);
+	free(b);
+	return (*MarshaledCredential != NULL);
+}
+
+BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE CredType, PVOID Credential,
+                            LPSTR* MarshaledCredential)
 {
-	CERT_CREDENTIAL_INFO* cert = cred;
-	WCHAR* p = NULL;
+	CERT_CREDENTIAL_INFO* cert = Credential;
 
-	if (!cred || (CredType == CertCredential && cert->cbSize < sizeof(*cert)))
+	if (!cert || ((CredType == CertCredential) && (cert->cbSize < sizeof(CERT_CREDENTIAL_INFO))) ||
+	    !MarshaledCredential)
 	{
 		SetLastError(ERROR_INVALID_PARAMETER);
 		return FALSE;
@@ -174,96 +192,86 @@ BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID cred, LPWSTR* Mars
 	{
 		case CertCredential:
 		{
-			size_t size = (sizeof(cert->rgbHashOfCert) + 2) * 4 / 3;
-			if (!(p = malloc((size + 4) * sizeof(WCHAR))))
-				return FALSE;
-			p[0] = '@';
-			p[1] = '@';
-			p[2] = (WCHAR)('A' + CredType);
-			size_t len = cred_encode(cert->rgbHashOfCert, sizeof(cert->rgbHashOfCert), p + 3);
-			p[len + 3] = 0;
-			break;
+			char buffer[3ULL + (sizeof(cert->rgbHashOfCert) * 4 / 3) +
+			            1ULL /* rounding error */] = { 0 };
+
+			const char c = WINPR_ASSERTING_INT_CAST(char, 'A' + CredType);
+			(void)_snprintf(buffer, sizeof(buffer), "@@%c", c);
+			size_t len = cred_encode(cert->rgbHashOfCert, sizeof(cert->rgbHashOfCert), &buffer[3],
+			                         sizeof(buffer) - 3);
+			*MarshaledCredential = strndup(buffer, len + 3);
+			return TRUE;
 		}
 		default:
 			WLog_ERR(TAG, "unhandled type 0x%x", CredType);
 			SetLastError(ERROR_INVALID_PARAMETER);
 			return FALSE;
 	}
-
-	*MarshaledCredential = p;
-	return TRUE;
 }
 
-BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE CredType, PVOID Credential,
-                            LPSTR* MarshaledCredential)
+BOOL CredUnmarshalCredentialW(LPCWSTR cred, PCRED_MARSHAL_TYPE pcredType, PVOID* out)
 {
-	WCHAR* b = NULL;
-	if (!CredMarshalCredentialW(CredType, Credential, &b) || !b)
-		return FALSE;
-
-	*MarshaledCredential = ConvertWCharNToUtf8Alloc(b, _wcslen(b), NULL);
-	free(b);
-	return (*MarshaledCredential != NULL);
+	char* str = NULL;
+	if (cred)
+		str = ConvertWCharToUtf8Alloc(cred, NULL);
+	const BOOL rc = CredUnmarshalCredentialA(str, pcredType, out);
+	free(str);
+	return rc;
 }
 
-BOOL CredUnmarshalCredentialW(LPCWSTR cred, PCRED_MARSHAL_TYPE pcredType, PVOID* out)
+BOOL CredUnmarshalCredentialA(LPCSTR cred, PCRED_MARSHAL_TYPE CredType, PVOID* Credential)
 {
-	if (!cred || !pcredType || !out || cred[0] != '@' || cred[1] != '@')
+	if (!cred)
+	{
+		SetLastError(ERROR_INVALID_PARAMETER);
+		return FALSE;
+	}
+
+	const size_t len = strlen(cred);
+	if ((len < 3) || !CredType || !Credential || (cred[0] != '@') || (cred[1] != '@'))
 	{
 		SetLastError(ERROR_INVALID_PARAMETER);
 		return FALSE;
 	}
 
-	BYTE b = wchar_decode(cred[2]);
-	if (!b || b > BinaryBlobForSystem)
+	BYTE b = char_decode(cred[2]);
+	if (!b || (b > BinaryBlobForSystem))
 	{
 		SetLastError(ERROR_INVALID_PARAMETER);
 		return FALSE;
 	}
 
-	*pcredType = (CRED_MARSHAL_TYPE)b;
+	*CredType = (CRED_MARSHAL_TYPE)b;
 
-	size_t len = _wcslen(cred + 3);
-	switch (*pcredType)
+	switch (*CredType)
 	{
 		case CertCredential:
 		{
-			BYTE hash[CERT_HASH_LENGTH];
+			BYTE hash[CERT_HASH_LENGTH] = { 0 };
 
-			if (len != 27 || !cred_decode(cred + 3, len, hash))
+			if ((len != 30) || !cred_decode(&cred[3], len - 3, hash))
 			{
 				SetLastError(ERROR_INVALID_PARAMETER);
 				return FALSE;
 			}
 
-			CERT_CREDENTIAL_INFO* cert = malloc(sizeof(*cert));
+			CERT_CREDENTIAL_INFO* cert = calloc(1, sizeof(CERT_CREDENTIAL_INFO));
 			if (!cert)
 				return FALSE;
 
+			cert->cbSize = sizeof(CERT_CREDENTIAL_INFO);
 			memcpy(cert->rgbHashOfCert, hash, sizeof(cert->rgbHashOfCert));
-			cert->cbSize = sizeof(*cert);
-			*out = cert;
+			*Credential = cert;
 			break;
 		}
 		default:
-			WLog_ERR(TAG, "unhandled credType 0x%x", *pcredType);
+			WLog_ERR(TAG, "unhandled credType 0x%x", *CredType);
 			SetLastError(ERROR_INVALID_PARAMETER);
 			return FALSE;
 	}
 	return TRUE;
 }
 
-BOOL CredUnmarshalCredentialA(LPCSTR cred, PCRED_MARSHAL_TYPE CredType, PVOID* Credential)
-{
-	WCHAR* b = ConvertUtf8NToWCharAlloc(cred, strlen(cred), NULL);
-	if (!b)
-		return FALSE;
-
-	BOOL ret = CredUnmarshalCredentialW(b, CredType, Credential);
-	free(b);
-	return ret;
-}
-
 BOOL CredIsMarshaledCredentialW(LPCWSTR MarshaledCredential)
 {
 	CRED_MARSHAL_TYPE t = BinaryBlobForSystem;
diff --git a/winpr/libwinpr/crt/unicode_builtin.c b/winpr/libwinpr/crt/unicode_builtin.c
index 394122fdc78a..3526d84c54d3 100644
--- a/winpr/libwinpr/crt/unicode_builtin.c
+++ b/winpr/libwinpr/crt/unicode_builtin.c
@@ -124,6 +124,26 @@ static const uint32_t offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2
  */
 static const uint8_t firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
 
+/* We always need UTF-16LE, even on big endian systems! */
+static WCHAR setWcharFrom(WCHAR w)
+{
+#if defined(__BIG_ENDIAN__)
+	union
+	{
+		WCHAR w;
+		char c[2];
+	} cnv;
+
+	cnv.w = w;
+	const char c = cnv.c[0];
+	cnv.c[0] = cnv.c[1];
+	cnv.c[1] = c;
+	return cnv.w;
+#else
+	return w;
+#endif
+}
+
 /* --------------------------------------------------------------------- */
 
 /* The interface converts a whole buffer to avoid function-call overhead.
@@ -155,7 +175,7 @@ static ConversionResult winpr_ConvertUTF16toUTF8_Internal(const uint16_t** sourc
 		const uint16_t* oldSource =
 		    source; /* In case we have to back up because of target overflow. */
 
-		ch = *source++;
+		ch = setWcharFrom(*source++);
 
 		/* If we have a surrogate pair, convert to UTF32 first. */
 		if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END)
@@ -163,7 +183,7 @@ static ConversionResult winpr_ConvertUTF16toUTF8_Internal(const uint16_t** sourc
 			/* If the 16 bits following the high surrogate are in the source buffer... */
 			if (source < sourceEnd)
 			{
-				uint32_t ch2 = *source;
+				uint32_t ch2 = setWcharFrom(*source);
 
 				/* If it's a low surrogate, convert to UTF32. */
 				if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END)
@@ -472,7 +492,7 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
 				else
 				{
 					if (!computeLength)
-						*target++ = UNI_REPLACEMENT_CHAR;
+						*target++ = setWcharFrom(UNI_REPLACEMENT_CHAR);
 					else
 						target++;
 				}
@@ -480,7 +500,7 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
 			else
 			{
 				if (!computeLength)
-					*target++ = (uint16_t)ch; /* normal case */
+					*target++ = setWcharFrom((WCHAR)ch); /* normal case */
 				else
 					target++;
 			}
@@ -496,7 +516,7 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
 			else
 			{
 				if (!computeLength)
-					*target++ = UNI_REPLACEMENT_CHAR;
+					*target++ = setWcharFrom(UNI_REPLACEMENT_CHAR);
 				else
 					target++;
 			}
@@ -515,8 +535,8 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
 
 			if (!computeLength)
 			{
-				*target++ = (uint16_t)((ch >> halfShift) + UNI_SUR_HIGH_START);
-				*target++ = (uint16_t)((ch & halfMask) + UNI_SUR_LOW_START);
+				*target++ = setWcharFrom((WCHAR)((ch >> halfShift) + UNI_SUR_HIGH_START));
+				*target++ = setWcharFrom((WCHAR)((ch & halfMask) + UNI_SUR_LOW_START));
 			}
 			else
 			{