From 2bb5b50d6e454ed7969d2d009e059292c740e803 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:37:35 +0000 Subject: [PATCH 1/4] Initial plan From 048fd18252f176df307b96c968a907fa0911cfc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:00:30 +0000 Subject: [PATCH 2/4] Fix big-endian support for multi-byte NTLM wire fields Co-authored-by: rzikm <32671551+rzikm@users.noreply.github.com> --- .../NegotiateAuthenticationPal.ManagedNtlm.cs | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs index d8909a897dbef6..b3ad71fef4ad8c 100644 --- a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs +++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs @@ -124,11 +124,21 @@ private enum AvId } [StructLayout(LayoutKind.Sequential)] - private unsafe struct MessageField + private struct MessageField { - public ushort Length; - public ushort MaximumLength; + private ushort _length; + private ushort _maximumLength; private int _payloadOffset; + public ushort Length + { + readonly get =>BitConverter.IsLittleEndian? _length: BinaryPrimitives.ReverseEndianness(_length); + set =>_length = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + } + public ushort MaximumLength + { + readonly get =>BitConverter.IsLittleEndian? _maximumLength: BinaryPrimitives.ReverseEndianness(_maximumLength); + set =>_maximumLength = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + } public int PayloadOffset { readonly get =>BitConverter.IsLittleEndian? _payloadOffset: BinaryPrimitives.ReverseEndianness(_payloadOffset); @@ -185,11 +195,16 @@ private unsafe struct ChallengeMessage { public MessageHeader Header; public MessageField TargetName; - public Flags Flags; + private Flags _flags; public fixed byte ServerChallenge[ChallengeLength]; private ulong _unused; public MessageField TargetInfo; public Version Version; + public Flags Flags + { + readonly get =>BitConverter.IsLittleEndian? _flags: (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); + set =>_flags = BitConverter.IsLittleEndian? value: (Flags)BinaryPrimitives.ReverseEndianness((uint)value); + } } // TYPE 3 message @@ -203,9 +218,14 @@ private unsafe struct AuthenticateMessage public MessageField UserName; public MessageField Workstation; public MessageField EncryptedRandomSessionKey; - public Flags Flags; + private Flags _flags; public Version Version; public fixed byte Mic[16]; + public Flags Flags + { + readonly get =>BitConverter.IsLittleEndian? _flags: (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); + set =>_flags = BitConverter.IsLittleEndian? value: (Flags)BinaryPrimitives.ReverseEndianness((uint)value); + } } // Set temp to ConcatenationOf(Responserversion, HiResponserversion, Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4)) @@ -218,10 +238,15 @@ private unsafe struct NtChallengeResponse private byte _reserved1; private byte _reserved2; private int _reserved3; - public long Time; + private long _time; public fixed byte ClientChallenge[ChallengeLength]; private int _reserved4; public fixed byte ServerInfo[4]; // Has to be non-zero size, so set it to the Z(4) padding + public long Time + { + readonly get =>BitConverter.IsLittleEndian? _time: BinaryPrimitives.ReverseEndianness(_time); + set =>_time = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + } } public override bool IsAuthenticated => _isAuthenticated; @@ -321,17 +346,9 @@ private static unsafe void CreateNtlmNegotiateMessage(Span asBytes, Flags message.Version = s_version; } - private static unsafe int GetFieldLength(MessageField field) - { - ReadOnlySpan span = new ReadOnlySpan(&field, sizeof(MessageField)); - return BinaryPrimitives.ReadInt16LittleEndian(span); - } + private static int GetFieldLength(MessageField field) => field.Length; - private static unsafe int GetFieldOffset(MessageField field) - { - ReadOnlySpan span = new ReadOnlySpan(&field, sizeof(MessageField)); - return BinaryPrimitives.ReadInt16LittleEndian(span.Slice(4)); - } + private static int GetFieldOffset(MessageField field) => field.PayloadOffset; private static ReadOnlySpan GetField(MessageField field, ReadOnlySpan payload) { @@ -346,17 +363,16 @@ private static ReadOnlySpan GetField(MessageField field, ReadOnlySpan short.MaxValue) { throw new Win32Exception(NTE_FAIL); } - Span span = MemoryMarshal.AsBytes(new Span(ref field)); - BinaryPrimitives.WriteInt16LittleEndian(span, (short)length); - BinaryPrimitives.WriteInt16LittleEndian(span.Slice(2), (short)length); - BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), offset); + field.Length = (ushort)length; + field.MaximumLength = (ushort)length; + field.PayloadOffset = offset; } private static void AddToPayload(ref MessageField field, ReadOnlySpan data, Span payload, ref int offset) @@ -589,7 +605,7 @@ private static byte[] DeriveKey(ReadOnlySpan exportedSessionKey, ReadOnlyS return null; } - Flags flags = BitConverter.IsLittleEndian ? challengeMessage.Flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)challengeMessage.Flags); + Flags flags = challengeMessage.Flags; ReadOnlySpan targetName = GetField(challengeMessage.TargetName, blob); // Only NTLMv2 with MIC is supported From 65b6ef972075d5b5284c4c5164c47b4c077b94cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:18:15 +0000 Subject: [PATCH 3/4] Fix spacing around ternary operators in endianness properties Co-authored-by: rzikm <32671551+rzikm@users.noreply.github.com> --- .../NegotiateAuthenticationPal.ManagedNtlm.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs index b3ad71fef4ad8c..f8081aee558904 100644 --- a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs +++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs @@ -131,18 +131,18 @@ private struct MessageField private int _payloadOffset; public ushort Length { - readonly get =>BitConverter.IsLittleEndian? _length: BinaryPrimitives.ReverseEndianness(_length); - set =>_length = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + readonly get => BitConverter.IsLittleEndian ? _length : BinaryPrimitives.ReverseEndianness(_length); + set => _length = BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value); } public ushort MaximumLength { - readonly get =>BitConverter.IsLittleEndian? _maximumLength: BinaryPrimitives.ReverseEndianness(_maximumLength); - set =>_maximumLength = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + readonly get => BitConverter.IsLittleEndian ? _maximumLength : BinaryPrimitives.ReverseEndianness(_maximumLength); + set => _maximumLength = BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value); } public int PayloadOffset { - readonly get =>BitConverter.IsLittleEndian? _payloadOffset: BinaryPrimitives.ReverseEndianness(_payloadOffset); - set =>_payloadOffset = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + readonly get => BitConverter.IsLittleEndian ? _payloadOffset : BinaryPrimitives.ReverseEndianness(_payloadOffset); + set => _payloadOffset = BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value); } } @@ -168,8 +168,8 @@ private unsafe struct Version public byte CurrentRevision; public ushort ProductBuild { - readonly get =>BitConverter.IsLittleEndian? _productBuild: BinaryPrimitives.ReverseEndianness(_productBuild); - set =>_productBuild = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + readonly get => BitConverter.IsLittleEndian ? _productBuild : BinaryPrimitives.ReverseEndianness(_productBuild); + set => _productBuild = BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value); } } @@ -184,8 +184,8 @@ private unsafe struct NegotiateMessage public Version Version; public Flags Flags { - readonly get =>BitConverter.IsLittleEndian? _flags: (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); - set =>_flags = BitConverter.IsLittleEndian? value: (Flags)BinaryPrimitives.ReverseEndianness((uint)value); + readonly get => BitConverter.IsLittleEndian ? _flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); + set => _flags = BitConverter.IsLittleEndian ? value : (Flags)BinaryPrimitives.ReverseEndianness((uint)value); } } @@ -202,8 +202,8 @@ private unsafe struct ChallengeMessage public Version Version; public Flags Flags { - readonly get =>BitConverter.IsLittleEndian? _flags: (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); - set =>_flags = BitConverter.IsLittleEndian? value: (Flags)BinaryPrimitives.ReverseEndianness((uint)value); + readonly get => BitConverter.IsLittleEndian ? _flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); + set => _flags = BitConverter.IsLittleEndian ? value : (Flags)BinaryPrimitives.ReverseEndianness((uint)value); } } @@ -223,8 +223,8 @@ private unsafe struct AuthenticateMessage public fixed byte Mic[16]; public Flags Flags { - readonly get =>BitConverter.IsLittleEndian? _flags: (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); - set =>_flags = BitConverter.IsLittleEndian? value: (Flags)BinaryPrimitives.ReverseEndianness((uint)value); + readonly get => BitConverter.IsLittleEndian ? _flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags); + set => _flags = BitConverter.IsLittleEndian ? value : (Flags)BinaryPrimitives.ReverseEndianness((uint)value); } } @@ -244,8 +244,8 @@ private unsafe struct NtChallengeResponse public fixed byte ServerInfo[4]; // Has to be non-zero size, so set it to the Z(4) padding public long Time { - readonly get =>BitConverter.IsLittleEndian? _time: BinaryPrimitives.ReverseEndianness(_time); - set =>_time = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value); + readonly get => BitConverter.IsLittleEndian ? _time : BinaryPrimitives.ReverseEndianness(_time); + set => _time = BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value); } } From 751a0256eef3e52bfe0c1f819f38e3011edeffb3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:02:43 +0000 Subject: [PATCH 4/4] Remove GetFieldLength/GetFieldOffset helpers, inline direct field accesses Co-authored-by: rzikm <32671551+rzikm@users.noreply.github.com> --- .../Net/NegotiateAuthenticationPal.ManagedNtlm.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs index f8081aee558904..56dfe4a80edcce 100644 --- a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs +++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs @@ -346,21 +346,17 @@ private static unsafe void CreateNtlmNegotiateMessage(Span asBytes, Flags message.Version = s_version; } - private static int GetFieldLength(MessageField field) => field.Length; - - private static int GetFieldOffset(MessageField field) => field.PayloadOffset; - private static ReadOnlySpan GetField(MessageField field, ReadOnlySpan payload) { - int offset = GetFieldOffset(field); - int length = GetFieldLength(field); + int offset = field.PayloadOffset; + int length = field.Length; if (length == 0 || offset + length > payload.Length) { return ReadOnlySpan.Empty; } - return payload.Slice(GetFieldOffset(field), GetFieldLength(field)); + return payload.Slice(offset, length); } private static void SetField(ref MessageField field, int length, int offset)