Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using Microsoft.Win32.SafeHandles;

namespace System.Diagnostics.Tests
{
public partial class ProcessHandlesTests
{
private static string GetSafeFileHandleId(SafeFileHandle handle)
{
if (Interop.Sys.FStat(handle, out Interop.Sys.FileStatus status) != 0)
{
throw new Win32Exception();
}
return FormattableString.Invariant($"{status.Dev}:{status.Ino}");
}
Comment thread
adamsitnik marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,23 @@ private unsafe int RunWithInvalidHandles(ProcessStartInfo startInfo)

[LibraryImport(Interop.Libraries.Kernel32)]
private static partial int ResumeThread(nint hThread);

private static unsafe string GetSafeFileHandleId(SafeFileHandle handle)
{
const int MaxPath = 32_767;
Comment thread
adamsitnik marked this conversation as resolved.
char[] buffer = new char[MaxPath];
uint result;
fixed (char* ptr = buffer)
{
result = Interop.Kernel32.GetFinalPathNameByHandle(handle, ptr, (uint)MaxPath, Interop.Kernel32.FILE_NAME_NORMALIZED);
}

if (result == 0)
{
throw new Win32Exception();
}

return new string(buffer, 0, (int)result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -540,5 +540,67 @@ public void InheritedHandles_ThrowsForDuplicates()

Assert.Throws<ArgumentException>(() => Process.Start(startInfo));
}

[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData(false)]
[InlineData(true)]
public void NonInheritedFileHandle_IsNotAvailableInChildProcess(bool inheritHandle)
{
string path = Path.GetTempFileName();
try
{
// Open with FileShare.None so independent opens that honor .NET/OS sharing semantics are prevented.
using SafeFileHandle fileHandle = File.OpenHandle(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None, FileOptions.None);

// Verify the handle is valid in the parent process.
Assert.False(fileHandle.IsInvalid);
Assert.Equal(FileHandleType.RegularFile, fileHandle.Type);
Comment thread
adamsitnik marked this conversation as resolved.
nint rawHandle = fileHandle.DangerousGetHandle();

// When we start a process and limit handle inheritance, it's hard to reliably check that
// a given file handle/descriptor was not inherited, because the OS can simply re-use the
// exact same number for a different pipe/file/device. The idea here is to get something
// that can help us identify the given file handle and just compare the ID that the parent
// and child processes have obtained.
string id = GetSafeFileHandleId(fileHandle);
Comment thread
adamsitnik marked this conversation as resolved.

RemoteInvokeOptions options = new() { CheckExitCode = true };
// When inheritHandle is true, explicitly add the handle to the allow-list so it gets inherited.
// When inheritHandle is false, use an empty allow-list so no handles are inherited.
options.StartInfo.InheritedHandles = inheritHandle ? [fileHandle] : [];

using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(
static (string handleStr, string fileId, string inheritHandleStr) =>
{
bool shouldBeInherited = bool.Parse(inheritHandleStr);
nint rawHandle = nint.Parse(handleStr);
using SafeFileHandle handle = new SafeFileHandle(rawHandle, ownsHandle: false);

// Check whether it points to our file (the Operating System could reuse same value for a different file).
string? childId = null;

try
{
childId = GetSafeFileHandleId(handle);
}
catch (Win32Exception) when (!shouldBeInherited)
{
// Handle is not a valid file handle in this process.
return RemoteExecutor.SuccessExitCode;
}

Assert.Equal(shouldBeInherited, childId == fileId);
return RemoteExecutor.SuccessExitCode;
},
rawHandle.ToString(),
id,
inheritHandle.ToString(),
options);
}
finally
{
File.Delete(path);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,14 @@
Link="Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SetConsoleCtrlHandler.cs"
Link="Common\Interop\Windows\Kernel32\Interop.SetConsoleCtrlHandler.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFinalPathNameByHandle.cs"
Link="Common\Interop\Windows\Kernel32\Interop.GetFinalPathNameByHandle.cs" />
<!-- Helpers -->
<Compile Include="$(CommonTestPath)TestUtilities\System\WindowsTestFileShare.cs" Link="Common\TestUtilities\System\WindowsTestFileShare.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'browser' or '$(TargetPlatformIdentifier)' == 'android' or '$(TargetPlatformIdentifier)' == 'maccatalyst'">
<Compile Include="Interop.Unix.cs" />
<Compile Include="ProcessHandlesTests.Unix.cs" />
<Compile Include="ProcessTests.Unix.cs" />
<Compile Include="ProcessThreadTests.Unix.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs"
Expand All @@ -85,6 +88,8 @@
Link="Common\Interop\Unix\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PosixSignal.cs"
Link="Common\Interop\Unix\System.Native\Interop.PosixSignal.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
Link="Common\Interop\Unix\System.Native\Interop.Stat.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'android' or '$(TargetPlatformIdentifier)' == 'maccatalyst'">
<Compile Include="ProcessTests.Mobile.cs" />
Expand Down
Loading