.NET 10 single-file publishing silently produces empty executables above 2 GiB due to integer overflow in BinaryUtils.WriteToStream
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 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 小时
- 30 天内合并 PR
- 585
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 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 于 5 天前认领。 未关闭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 于 5 天前认领。 未关闭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
-
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
enhancement
难度 2/5 1-3 小时 新手友好度 73/100
KnifeLemon/Filee#1344 ·
维护者通常 1 天内回复
-
Issue-Bug Needs-Triage
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 2 天内回复
-
难度 2/5 1-3 小时 新手友好度 66/100
JiaFeiMiao-K-Cat/DiscImageStudio#281 ·
维护者通常 1 天内回复
-
bug triage
难度 2/5 1-3 小时 新手友好度 76/100