Skip to content

Fix ManagedNtlm on big-endian architectures - #124598

Merged
rzikm merged 4 commits into
dotnet:mainfrom
shreyarao4:ntlm
Feb 23, 2026
Merged

rzikm merged 4 commits into
dotnet:mainfrom
shreyarao4:ntlm

Conversation

@shreyarao4

Copy link
Copy Markdown
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
@shreyarao4

Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@wfurt wfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM in general.

@rzikm rzikm left a comment

Copy link
Copy Markdown
Member

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

@gfoidl gfoidl left a comment

Copy link
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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);

@shreyarao4

shreyarao4 commented Feb 26, 2026 •

Copy link
Copy Markdown
Contributor Author

@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

rzikm commented Feb 26, 2026

Copy link
Copy Markdown
Member

@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?

@shreyarao4

Copy link
Copy Markdown
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

rzikm commented Feb 27, 2026

Copy link
Copy Markdown
Member

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?

@shreyarao4

Copy link
Copy Markdown
Contributor Author

@rzikm

rzikm commented Mar 2, 2026

Copy link
Copy Markdown
Member

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);
}
}

@saitama951

ghost commented Mar 3, 2026 •

Copy link
Copy Markdown
Contributor

@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?

@rzikm

ghost commented Mar 3, 2026

Copy link
Copy Markdown
Member

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

@github-actions github-actions Bot locked and limited conversation to collaborators Apr 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

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