-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[System.DirectoryServices.Protocols] Fix ProtocolVersion Overflow on Big Endian Systems #112391
Conversation
9acf76e
to
7a4ba9e
Compare
@@ -57,8 +58,9 @@ public bool SecureSocketLayer | |||
|
|||
public int ProtocolVersion | |||
{ | |||
get => GetPtrValueHelper(LdapOption.LDAP_OPT_VERSION).ToInt32(); | |||
set => SetPtrValueHelper(LdapOption.LDAP_OPT_VERSION, new IntPtr(value)); | |||
get => BinaryPrimitives.ReadInt32LittleEndian(BitConverter.GetBytes(BinaryPrimitives.ReadIntPtrLittleEndian(BitConverter.GetBytes(GetPtrValueHelper(LdapOption.LDAP_OPT_VERSION))).ToInt32())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's suboptimal to use byte arrays for endianness correction.
The bug seems to be in the library OpenLdap
, which is invoked by ldap_get_option_ptr
.
To workaround the issue in managed code, BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value)
is the optimal approach, but there may be other endianness-related bug not covered by test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@huoyaoyuan Thanks for your suggestion! However, using BinaryPrimitives.ReverseEndianness(value)
directly won't work in this scenario because IntPtr represents a memory address, not an integer value.
If we apply ReverseEndianness
on IntPtr
, we are swapping the bytes of the memory address, not the stored integer. This leads to garbage values rather than correctly handling the endianness of the stored integer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://linux.die.net/man/3/ldap_get_option says:
LDAP_OPT_PROTOCOL_VERSION
Sets/gets the protocol version. outvalue and invalue must be int *.
Perhaps this should use GetIntValueHelper
/SetIntValueHelper
instead of GetPtrValueHelper
/SetPtrValueHelper
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tmds
Thanks for the suggestion! Since LDAP_OPT_PROTOCOL_VERSION
is expected to be an int*
rather than a generic void*
, using GetIntValueHelper/SetIntValueHelper instead of GetPtrValueHelper/SetPtrValueHelper makes sense. I'll try this approach and verify whether it resolves the issue correctly without introducing any regressions. If everything works as expected, I'll update the PR accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@huoyaoyuan To my understading the IntPtr uses a nint hence that would be a System.Int64 (i.e long) for s390x machines. when doing a ldap_get_option (link) it expects an int* as correctly pointed out by @tmds.
I have tried the following sample OpenLdap program with int and long
// gcc a.c -lldap
int main() {
LDAP *ld;
//long version; //output: -1685089024
int version; //output: 2
int ret = ldap_initialize(&ld, "ldap://localhost");
if (ret != LDAP_SUCCESS) {
fprintf(stderr, "ldap_initialize() failed: %s\n", ldap_err2string(ret));
return EXIT_FAILURE;
}
ret = ldap_get_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
if (ret != LDAP_SUCCESS) {
ldap_unbind_ext(ld, NULL, NULL);
return EXIT_FAILURE;
}
printf("LDAP Protocol Version: %d\n", version);
ldap_unbind_ext(ld, NULL, NULL);
return EXIT_SUCCESS;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the original implementation for Linux used IntPtr
and GetPtrValueHelper
as a native int since the C APIs use native ints, meaning the int value could be 32- or 64-bits. This was before we had the nint
alias to IntPtr
. In this case, IntPtr
is a number (e.g. "3" for protocol version 3), not an address.
So if the native value is big-endian I believe we need to convert that to little-endian plus only use 32 bits signed since the return value here is Int32.
Note the helpers for Get\SetIntValueHelper and Get\SetPtrValueHelper
do the conversion from the value being get\set to a pointer referencing the value which the underlying LDAP expects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tannergooding what are the latest helpers for converting a native big endian int (e.g. ref IntPtr
) to Int32?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The native library API expects a pointer to an int
(see #112391 (comment)). By calling GetIntValueHelper
that is what we're giving it. There is no need to deal with endianness. The endianness for the native library and .NET is the same (for int
, IntPtr
, ...).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a C API where I assume the native int
can be either big- or little-endian and either 32- or 64-bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The native int
is 32-bit in the machine endianness. The machine endianness is big or little endian, but whatever it is, it is the same for the C library and .NET.
Suppose the int
is 1
On a little endian system in memory that is 01 00 00 00
On a big endian system in memory that is 00 00 00 01
Let's assume the next 4 bytes in memory are also zero.
If we're reading this as a 64-bit value (which is what the code was doing before).
On little endian 01 00 00 00 00 00 00 00
is still 1
.
On big endian 00 00 00 01 00 00 00 00
is 1 << 32
.
The latter is resulting in OverflowException
in IntPtr.ToInt32()
.
@@ -4,6 +4,7 @@ | |||
using System.ComponentModel; | |||
using System.IO; | |||
using System.Runtime.Versioning; | |||
using System.Buffers.Binary; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This using is no longer needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's done!
Thanks @tmds
get => GetPtrValueHelper(LdapOption.LDAP_OPT_VERSION).ToInt32(); | ||
set => SetPtrValueHelper(LdapOption.LDAP_OPT_VERSION, new IntPtr(value)); | ||
get => GetIntValueHelper(LdapOption.LDAP_OPT_VERSION); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: no spacing between property accessors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: no spacing between property accessors.
removed the extra spaces
...ryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
Outdated
Show resolved
Hide resolved
...ryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
Outdated
Show resolved
Hide resolved
@dotnet-policy-service agree company="IBM" |
@medhatiwari can you update your branch please - there are lots of test failures that don't seem to be occurring in more recent PRs. I re-ran the failed tests earlier and still encountering a bunch of failures (see Build Analysis). Thanks |
@steveharter I have merged the latest |
If everything looks good, can this PR be merged? |
Fixes #112390
This PR fixes an
OverflowException in LdapSessionOptions.ProtocolVersion
on Big Endian systems (e.g., s390x). The issue occurs because IntPtr.ToInt32() is used to convert a 64-bit pointer to 32-bit, but on Big Endian architectures, the upper 32 bits are nonzero, causing an overflow.