.NET 10 single-file publishing silently produces empty executables above 2 GiB due to integer overflow in BinaryUtils.WriteToStream
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 68/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- csharp
- 領域
- build-system
調査の方向性
バグは src/installer/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs の WriteToStream にあります。チャンクサイズを計算する前に long の長さが int に縮小されています。まずそこから始めて、pos と bytesRequested の型を確認してください。issue にあるリフレクションによる再現手順を実行してください。修正後は、2 GiB+1 のケースで firstWrite=16384 が出力されるはずです。そのケースが宛先への最初の書き込みに到達し、Int32.MaxValue を超える長さをユニットテストがカバーしていれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Description
Publishing a single-file executable larger than 2 GiB can produce a 0-byte executable while reporting success.
The issue appears to be a narrowing conversion in BinaryUtils.WriteToStream:
https://github.com/dotnet/runtime/blob/6aa72daa24f66c01c3d7b6a32519e9d21396deec/src/installer/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs#L186
For lengths between 2 GiB and 4 GiB, converting length to int produces a negative value. On the first iteration, bytesRequested <= 0, so the method returns without writing anything or throwing an exception.
The .NET 10 bundler creates/truncates the output file immediately before calling this method, leaving an empty executable. dotnet publish nevertheless exits successfully.
Reproduction Steps
Use .NET SDK 10.0.403 with a 64-bit process.
The following dependency-free console application invokes the actual internal SDK method through reflection. It does not substitute or patch the implementation.
To avoid generating a multi-gigabyte installer, it uses a large virtual mapping and stops at the first destination write. It only needs to read one chunk, and no large output file is created.
- Create Repro.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Include="$(NetCoreRoot)sdk\$(NETCoreSdkVersion)\Microsoft.NET.HostModel.dll"
Link="Microsoft.NET.HostModel.dll"
CopyToOutputDirectory="Always" />
</ItemGroup>
</Project>
- Create Program.cs
using System.IO.MemoryMappedFiles;
using System.Reflection;
var assembly = Assembly.LoadFrom(
Path.Combine(AppContext.BaseDirectory, "Microsoft.NET.HostModel.dll"));
var method = assembly
.GetType("Microsoft.NET.HostModel.AppHost.BinaryUtils", throwOnError: true)
.GetMethod("WriteToStream", BindingFlags.Static | BindingFlags.NonPublic);
var copy = method.CreateDelegate<
Action<MemoryMappedViewAccessor, FileStream, long>>();
bool failed = false;
foreach (long length in new[] { 16384L, 2147483649L })
{
using var map = MemoryMappedFile.CreateNew(null, length);
using var source = map.CreateViewAccessor();
using var destination = new FirstWriteStream();
try
{
copy(source, destination, length);
}
catch (CopyStartedException)
{
// Stop after the first chunk; no multi-gigabyte output file is needed.
}
Console.WriteLine(
$"length={length}, firstWrite={destination.BytesWritten}");
failed |= destination.BytesWritten == 0;
}
return failed ? 1 : 0;
sealed class FirstWriteStream : FileStream
{
public int BytesWritten { get; private set; }
public FirstWriteStream()
: base(Path.GetTempFileName(), FileMode.Open,
FileAccess.Write, FileShare.None, 1, FileOptions.DeleteOnClose)
{
}
public override void Write(byte[] buffer, int offset, int count)
=> Stop(count);
public override void Write(ReadOnlySpan<byte> buffer)
=> Stop(buffer.Length);
private void Stop(int count)
{
BytesWritten = count;
throw new CopyStartedException();
}
}
sealed class CopyStartedException : Exception
{
}
- run
dotnet run --configuration Release
Expected behavior
WriteToStream should handle valid lengths and positions larger than Int32.MaxValue without truncation.
Both reproduction cases should reach the first destination write. With the current 16 KiB buffer size, the output would be:
length=16384, firstWrite=16384
length=2147483649, firstWrite=16384
Actual behavior
The verified reproduction prints:
length=16384, firstWrite=16384
length=2147483649, firstWrite=0
The reproduction itself exits with code 1 to flag that failure. In contrast, the affected dotnet publish command exits with code 0.
Regression?
This appears to be a publishing-path regression that exposes a pre-existing helper bug.
The narrowing conversion already exists in the .NET 9 helper. However:
- The .NET 9 bundler writes the bundle directly to the output file.
- The .NET 10 bundler writes the completed memory-mapped bundle through BinaryUtils.WriteToStream.
Verified with SDK 10.0.403.
Known Workarounds
The relevant implementation is in the function:
https://github.com/dotnet/runtime/blob/6aa72daa24f66c01c3d7b6a32519e9d21396deec/src/installer/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs#L177
pos must be long, as length is long:
https://github.com/dotnet/runtime/blob/6aa72daa24f66c01c3d7b6a32519e9d21396deec/src/installer/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs#L179
-int pos = 0;
+long pos = 0;
Cast to int without overflow:
https://github.com/dotnet/runtime/blob/6aa72daa24f66c01c3d7b6a32519e9d21396deec/src/installer/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs#L186
- int bytesRequested = Math.Min((int)length - pos, bufSize);
+ int bytesRequested = (int)Math.Min(length - pos, (long)bufSize);
This narrows only the bounded chunk size, rather than the total length.
Configuration
No response
Other information
No response
- 主要言語
- C#
- スター
- 18.4k
- フォーク
- 5.8k
- 平均マージ
- 2日 14時間
- マージ済み PR(30日)
- 585
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
dotnet/runtime のほかの issue
-
ImmutableSortedSet<T>.Builder: enumerating through IEnumerable<T> doesn't throw on modification and can return wrong items対応中かも @aw0lid が今日担当しました。 オープンarea-System.Collections
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
dotnet/runtime#137167 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
DbTransaction.SupportsSavepoints docs say Release/ReleaseAsync are expected to throw, but they are no-ops by design対応中かも @alaricd が 4 日前に担当しました。 オープンarea-System.Transactions untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
dotnet/runtime#137040 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
FileSystemGlobbing: Matcher throws ArgumentOutOfRangeException when the rootDir is the same value as the file対応中かも @makazeu が 4 日前に担当しました。 オープンarea-Extensions-FileSystem
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
dotnet/runtime#136989 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Configuration binder source generator emits unqualified List<T> when binding arrays without implicit usings対応中かも @makazeu が 4 日前に担当しました。 オープンarea-Extensions-Configuration bug source-generator
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
dotnet/runtime#136972 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[wasi] .NET 10: WasmEnableSIMD with AOT fails because WasiApp.targets has -msimd128 commented outオープンarea-Build-mono untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
area/access-management Frontend onboarding candidate :handshake:
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
Altinn/altinn-auth#4941 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
aws/aws-sdk-net#4586 ·
メンテナーはふだん 1 日以内に返信
-
30-minutes algorithm any-language beginner-friendly c community cpp csharp data-structures go good first issue hacktoberfest help wanted kotlin learning leetcode php ruby rust swift
難易度 2/5 1時間未満 初心者へのやさしさ 85/100
Huerte/algo-solutions-python#211 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
QuantConnect/Lean#10047 ·
メンテナーはふだん 1 日以内に返信
-
bug bug:behavior impact:ux-release-blocker P0
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
openclaw/openclaw#196251 · コメント 2 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信