Skip to content

Fix ManagedNtlm on big-endian architectures#124598

Merged
rzikm merged 4 commits intodotnet:mainfrom
ShreyaLaxminarayan:ntlm
Feb 23, 2026
Merged

Fix ManagedNtlm on big-endian architectures#124598
rzikm merged 4 commits intodotnet:mainfrom
ShreyaLaxminarayan:ntlm

Conversation

@ShreyaLaxminarayan
Copy link
Contributor

Fixes multiple endianness bugs in ManagedNtlm that caused incorrect behavior on big-endian platforms.
Validated via existing Ntlm test cases.

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Feb 19, 2026
@ShreyaLaxminarayan
Copy link
Contributor Author

@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

Copy link
Member

@wfurt wfurt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM in general.

Copy link
Member

@rzikm rzikm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Thanks!

@rzikm rzikm merged commit 84057bb into dotnet:main Feb 23, 2026
86 of 90 checks passed
Copy link
Member

@gfoidl gfoidl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: spaces for code-style.

Comment on lines +134 to +135
readonly get =>BitConverter.IsLittleEndian? _payloadOffset: BinaryPrimitives.ReverseEndianness(_payloadOffset);
set =>_payloadOffset = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
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);

Comment on lines +161 to +162
readonly get =>BitConverter.IsLittleEndian? _productBuild: BinaryPrimitives.ReverseEndianness(_productBuild);
set =>_productBuild = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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);

Comment on lines +177 to +178
readonly get =>BitConverter.IsLittleEndian? _flags: (Flags)BinaryPrimitives.ReverseEndianness((uint)_flags);
set =>_flags = BitConverter.IsLittleEndian? value: (Flags)BinaryPrimitives.ReverseEndianness((uint)value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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);

@ShreyaLaxminarayan
Copy link
Contributor Author

ShreyaLaxminarayan commented Feb 26, 2026

@rzikm @wfurt Unfortunately, the getters and setters method doesn't resolve the endianness issues because of MemoryMarshal.AsRef. We would have to go with the previous commit 91fc6d6. What can be done?

@rzikm
Copy link
Member

rzikm commented Feb 26, 2026

@rzikm @wfurt Unfortunately, the getters and setters method doesn't resolve the endianness issues because of MemoryMarshal.AsRef. We would have to go with the previous commit 91fc6d6. What can be done?

Can you be more specific, what about the MemoryMarshal.AsRef is making the changes broken?

@ShreyaLaxminarayan
Copy link
Contributor Author

MemoryMarshal.AsRef is writing the fields in little-endian, not in the correct byte order overwriting the struct fields with little-endian data.

@rzikm
Copy link
Member

rzikm commented Feb 27, 2026

MemoryMarshal.AsRef is not writing anything, if you cast Span<byte> to e.g. ref MessageField, the getters/setters added in the PR should take care of endianness conversion on read/write. Similarly, if you create MessageField variable on it's own and copy it to a ref MessageField variable, the backing data should already have the correct endianness. Same if you cast the source MessageField to Span<byte> and copy the raw bytes to the target buffer.

Can you point to the specific piece of code where you see the problem, or give a code example where the pattern does not work?

@ShreyaLaxminarayan
Copy link
Contributor Author

@rzikm
Copy link
Member

rzikm commented Mar 2, 2026

I believe that is because the Length and MaximumLength fields are not corrected for endianness when reading

[StructLayout(LayoutKind.Sequential)]
private unsafe struct MessageField
{
public ushort Length;
public ushort MaximumLength;
private int _payloadOffset;
public int PayloadOffset
{
readonly get =>BitConverter.IsLittleEndian? _payloadOffset: BinaryPrimitives.ReverseEndianness(_payloadOffset);
set =>_payloadOffset = BitConverter.IsLittleEndian? value: BinaryPrimitives.ReverseEndianness(value);
}
}

iremyux pushed a commit to iremyux/dotnet-runtime that referenced this pull request Mar 2, 2026
Fixes multiple endianness bugs in ManagedNtlm that caused incorrect
behavior on big-endian platforms.
Validated via existing Ntlm test cases.
@saitama951
Copy link
Contributor

saitama951 commented Mar 3, 2026

@rzikm I think
the assertion happens here (http://148.100.85.217:8080/job/daily-builds/211/consoleFull)

Flags flags = (Flags)BinaryPrimitives.ReadUInt32LittleEndian(incomingBlob.AsSpan(60));
Assert.Equal(_requiredFlags, (flags & _requiredFlags));

we don't write in little endian order here, since response is of type AuthenticateMessage I don't see a getter / setter for the endianess here.

response.Header.MessageType = MessageType.Authenticate;
response.Flags = s_requiredFlags | (flags & Flags.NegotiateSeal);

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs#L206

 while the original commit did handle this (the case where the test passed)
91fc6d6#diff-07ef1e3bc82826f3e53cb6d9d9f4efef56b93b428ebef12c806ca209063ce74bR632
 
 The file already consists of various getters and setter, which take care of the endinanness, i.e GetField and SetField ,
 
 

private static unsafe int GetFieldLength(MessageField field)
{
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(&field, sizeof(MessageField));
return BinaryPrimitives.ReadInt16LittleEndian(span);
}
private static unsafe int GetFieldOffset(MessageField field)
{
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(&field, sizeof(MessageField));
return BinaryPrimitives.ReadInt16LittleEndian(span.Slice(4));
}
private static ReadOnlySpan<byte> GetField(MessageField field, ReadOnlySpan<byte> payload)
{
int offset = GetFieldOffset(field);
int length = GetFieldLength(field);
if (length == 0 || offset + length > payload.Length)
{
return ReadOnlySpan<byte>.Empty;
}
return payload.Slice(GetFieldOffset(field), GetFieldLength(field));
}
private static unsafe void SetField(ref MessageField field, int length, int offset)
{
if (length is < 0 or > short.MaxValue)
{
throw new Win32Exception(NTE_FAIL);
}
Span<byte> span = MemoryMarshal.AsBytes(new Span<MessageField>(ref field));
BinaryPrimitives.WriteInt16LittleEndian(span, (short)length);
BinaryPrimitives.WriteInt16LittleEndian(span.Slice(2), (short)length);
BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), offset);
}

 can't we reuse those?

ShreyaLaxminarayan added a commit to ShreyaLaxminarayan/runtime that referenced this pull request Mar 3, 2026
- Fixed several endian bugs in ManagedNTLM implementation.
- This is a follow-up to PR dotnet#124598
@ShreyaLaxminarayan ShreyaLaxminarayan deleted the ntlm branch March 3, 2026 07:27
@rzikm
Copy link
Member

rzikm commented Mar 3, 2026

#125039 should be addressing these, let's move conversation there if necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Net.Security community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants