From 79ec5b8d31d38bab3bc7322397f39212420310ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:32:11 +0000 Subject: [PATCH 1/2] Initial plan From bf491da6fe2276b9c2dc094e02a8afd1d9f54cde Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:56:00 +0000 Subject: [PATCH 2/2] Fix LDAP referral callback host marshaling Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- .../Protocols/ldap/LdapSessionOptions.cs | 4 +- .../tests/ReferralCallbackTests.cs | 133 ++++++++++++++++++ ...m.DirectoryServices.Protocols.Tests.csproj | 1 + 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs index ac3c5ec2808786..4300d6873ae5b2 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs @@ -895,7 +895,7 @@ private unsafe int ProcessQueryConnection(IntPtr PrimaryConnection, IntPtr Refer NewDN = LdapPal.PtrToString(NewDNPtr); } - string target = $"{Marshal.PtrToStringUni(HostNamePtr)}:{PortNumber}"; + string target = $"{Marshal.PtrToStringAnsi(HostNamePtr)}:{PortNumber}"; var identifier = new LdapDirectoryIdentifier(target); NetworkCredential cred = ProcessSecAuthIdentity(SecAuthIdentity); @@ -959,7 +959,7 @@ private unsafe Interop.BOOL ProcessNotifyConnection(IntPtr primaryConnection, In newDN = LdapPal.PtrToString(newDNPtr); } - string target = $"{Marshal.PtrToStringUni(hostNamePtr)}:{portNumber}"; + string target = $"{Marshal.PtrToStringAnsi(hostNamePtr)}:{portNumber}"; var identifier = new LdapDirectoryIdentifier(target); NetworkCredential cred = ProcessSecAuthIdentity(SecAuthIdentity); diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/ReferralCallbackTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/ReferralCallbackTests.cs index 98e7d548b7fb00..d31b747e86e1fd 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/ReferralCallbackTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/ReferralCallbackTests.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net; +using System.Reflection; +using System.Runtime.InteropServices; using Xunit; namespace System.DirectoryServices.Protocols.Tests @@ -38,6 +40,137 @@ public void QueryForConnection_Set_GetReturnsExpected() Assert.Equal(QueryForConnection, callback.QueryForConnection); } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void ProcessQueryConnection_MarshalsHostNameAsAnsi() + { + const string HostName = "server01"; + const int PortNumber = 389; + string[] servers = null; + + using (var connection = new LdapConnection("server")) + { + connection.SessionOptions.ReferralCallback = new ReferralCallback + { + QueryForConnection = (primaryConnection, referralFromConnection, newDistinguishedName, identifier, credential, currentUserToken) => + { + servers = identifier.Servers; + return null; + } + }; + + InvokeProcessQueryConnection(connection.SessionOptions, HostName, PortNumber); + } + + Assert.Equal(new[] { $"{HostName}:{PortNumber}" }, servers); + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void ProcessNotifyConnection_MarshalsHostNameAsAnsi() + { + const string HostName = "server01"; + const int PortNumber = 389; + string[] servers = null; + + using (var connection = new LdapConnection("server")) + using (var newConnection = new LdapConnection("server")) + { + connection.SessionOptions.ReferralCallback = new ReferralCallback + { + NotifyNewConnection = (primaryConnection, referralFromConnection, newDistinguishedName, identifier, notifiedConnection, credential, currentUserToken, errorCodeFromBind) => + { + servers = identifier.Servers; + return false; + } + }; + + InvokeProcessNotifyConnection(connection.SessionOptions, HostName, GetConnectionHandle(newConnection), PortNumber); + } + + Assert.Equal(new[] { $"{HostName}:{PortNumber}" }, servers); + } + + private static unsafe void InvokeProcessQueryConnection(LdapSessionOptions options, string hostName, int portNumber) + { + MethodInfo method = GetCallbackMethod("ProcessQueryConnection"); + ParameterInfo[] parameters = method.GetParameters(); + IntPtr hostNamePointer = AllocateAnsiString(hostName); + IntPtr currentUserPointer = Marshal.AllocHGlobal(sizeof(long)); + IntPtr connectionToUsePointer = Marshal.AllocHGlobal(IntPtr.Size); + + try + { + Marshal.WriteInt64(currentUserPointer, 0); + Marshal.WriteIntPtr(connectionToUsePointer, IntPtr.Zero); + + method.Invoke(options, new object[] + { + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + hostNamePointer, + portNumber, + null, + Pointer.Box(currentUserPointer.ToPointer(), parameters[6].ParameterType), + Pointer.Box(connectionToUsePointer.ToPointer(), parameters[7].ParameterType) + }); + } + finally + { + Marshal.FreeHGlobal(connectionToUsePointer); + Marshal.FreeHGlobal(currentUserPointer); + Marshal.FreeHGlobal(hostNamePointer); + } + } + + private static unsafe void InvokeProcessNotifyConnection(LdapSessionOptions options, string hostName, IntPtr newConnection, int portNumber) + { + MethodInfo method = GetCallbackMethod("ProcessNotifyConnection"); + ParameterInfo[] parameters = method.GetParameters(); + IntPtr hostNamePointer = AllocateAnsiString(hostName); + IntPtr currentUserPointer = Marshal.AllocHGlobal(sizeof(long)); + + try + { + Marshal.WriteInt64(currentUserPointer, 0); + + method.Invoke(options, new object[] + { + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + hostNamePointer, + newConnection, + portNumber, + null, + Pointer.Box(currentUserPointer.ToPointer(), parameters[7].ParameterType), + 0 + }); + } + finally + { + Marshal.FreeHGlobal(currentUserPointer); + Marshal.FreeHGlobal(hostNamePointer); + } + } + + private static IntPtr AllocateAnsiString(string value) + { + byte[] bytes = System.Text.Encoding.ASCII.GetBytes(value); + IntPtr pointer = Marshal.AllocHGlobal(bytes.Length + 2); + Marshal.Copy(bytes, 0, pointer, bytes.Length); + Marshal.WriteByte(pointer, bytes.Length, 0); + Marshal.WriteByte(pointer, bytes.Length + 1, 0); + return pointer; + } + + private static MethodInfo GetCallbackMethod(string name) => + typeof(LdapSessionOptions).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic); + + private static IntPtr GetConnectionHandle(LdapConnection connection) => + ((SafeHandle)typeof(LdapConnection).GetField("_ldapHandle", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(connection)).DangerousGetHandle(); + internal static void DereferenceConnection(LdapConnection primaryConnection, LdapConnection connectionToDereference) { } internal static bool NotifyNewConnection(LdapConnection primaryConnection, LdapConnection referralFromConnection, string newDistinguishedName, LdapDirectoryIdentifier identifier, LdapConnection newConnection, NetworkCredential credential, long currentUserToken, int errorCodeFromBind) => true; internal static LdapConnection QueryForConnection(LdapConnection primaryConnection, LdapConnection referralFromConnection, string newDistinguishedName, LdapDirectoryIdentifier identifier, NetworkCredential credential, long currentUserToken) => null; diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/System.DirectoryServices.Protocols.Tests.csproj b/src/libraries/System.DirectoryServices.Protocols/tests/System.DirectoryServices.Protocols.Tests.csproj index 5da24273a28f65..636f4d827bcb59 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/System.DirectoryServices.Protocols.Tests.csproj +++ b/src/libraries/System.DirectoryServices.Protocols/tests/System.DirectoryServices.Protocols.Tests.csproj @@ -1,6 +1,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetFrameworkCurrent) + true