镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Use overflow-checked arithmetic when sizing StringBuilder native buffers - #137012

Merged
jkotas merged 4 commits into
dotnet:mainfrom
filipnavara:claude/utf8-stringbuilder-overflow-6c4490
Nov 6, 2026
Merged

jkotas merged 4 commits into
dotnet:mainfrom
filipnavara:claude/utf8-stringbuilder-overflow-6c4490

Conversation

@filipnavara

Copy link
Copy Markdown
Member

The IL stubs for [MarshalAs(UnmanagedType.LPUTF8Str)] StringBuilder and [MarshalAs(UnmanagedType.LPWStr)] StringBuilder computed the native buffer size with unchecked int32 arithmetic. StubHelpers.CheckStringLength only caps StringBuilder.Capacity at 0x7ffffff0, so large capacities wrap:

  • UTF-8 (ILUTF8BufferMarshaler): (Capacity + 1) * 3 + 1. For example, Capacity = 0x55555555 gives a 3-byte buffer that the stub may localloc on the stack before UTF8BufferMarshaler.ConvertToNative writes the string into it.
  • UTF-16 (ILWSTRBufferMarshaler): Capacity * 2 + 4. From about 1G chars, the offset of the extra null terminator goes negative, so the stub writes it about 2 GB before the allocated buffer.

Both now use MUL_OVF/ADD_OVF, as ILCSTRBufferMarshaler already does, so the stub throws OverflowException before it allocates or writes anything. The by-value, by-ref and field cases all share this size calculation, and the native-to-managed direction has no size arithmetic.

Tests

I added one test per marshaler: UTF8Test.StringBuilderNativeBufferSizeOverflow and LPTStrTest.StringBuilderNativeBufferSizeOverflow. Each passes a StringBuilder with the smallest capacity that triggers the bug (715,827,883 and 1,073,741,823) and asserts OverflowException.

  • The StringBuilders need 1.4 to 2 GB of virtual memory, so both tests are limited to 64-bit CoreCLR and return early if the StringBuilder can't be allocated.
  • Both tests are OuterLoop, like the tests around them.

Results on osx-arm64 Release:

  • Without the fix: both tests crash the process with AccessViolationException inside the marshaling stub.
  • With the fix: both tests pass.

The native buffer size (capacity + 1) * 3 + 1 was computed with unchecked
int32 ADD/MUL. For capacities near 0x55555555 the size wraps to a tiny
value, so the stub allocated a few bytes (possibly via localloc) and then
wrote the full UTF-8 contents into it. Use ADD_OVF/MUL_OVF like the LPSTR
StringBuilder marshaler does.
Marshal a [MarshalAs(UnmanagedType.LPUTF8Str)] StringBuilder whose
Capacity (715,827,883) makes the native buffer size (Capacity + 1) * 3 + 1
overflow int32, and assert the stub throws OverflowException before the
native call. Without the overflow-checked arithmetic the stub writes the
null terminator ~2 GB before the allocated buffer and crashes with an
AccessViolationException.

The test needs a ~1.4 GB StringBuilder, so it is limited to 64-bit CoreCLR,
kept OuterLoop like the rest of this file, and returns early if the
StringBuilder cannot be allocated.
ILWSTRBufferMarshaler computed the native buffer size Capacity * 2 + 4 and
the offset of the extra null terminator (Capacity * 2 + 2) with unchecked
int32 MUL/ADD. Capacity is only validated to be <= 0x7ffffff0, so for
capacities of about 1G chars and above the offset wraps negative and the
stub writes the terminator ~2 GB before the allocated buffer. Use
MUL_OVF/ADD_OVF like the LPSTR and UTF-8 StringBuilder marshalers.

Add a regression test that marshals a CharSet.Unicode StringBuilder with
Capacity 1,073,741,823 and asserts OverflowException. Without the fix it
crashes with an AccessViolationException.
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Nov 5, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

NativeAOT sizes UTF-16 StringBuilder buffers with checked arithmetic, so
the LPTSTR test is no longer limited to CoreCLR. The UTF-8 test is disabled
on NativeAOT with the existing UTF-8 StringBuilder issue (dotnet#123529) and
skipped on Mono, which sizes the buffer from Capacity + 1 and does not
overflow.
@jkotas
jkotas merged commit bb6c354 into dotnet:main Nov 6, 2026
108 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Interop-coreclr 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.

3 participants