Skip to content

Fix OS-blind apphost probe in MTP bridge on Unix (regression from #16201) - #16336

Merged
Azat Mukhametshin (azat-msft) merged 1 commit into
microsoft:mainfrom
azat-msft:fix/mtp-apphost-os-blind-probe
Aug 6, 2026
Merged

Fix OS-blind apphost probe in MTP bridge on Unix (regression from #16201)#16336
Azat Mukhametshin (azat-msft) merged 1 commit into
microsoft:mainfrom
azat-msft:fix/mtp-apphost-os-blind-probe

Conversation

@azat-msft

Copy link
Copy Markdown
Member

Problem

MtpServerConnection.BuildLaunch (added in #16201) probes for a test app's native apphost by unconditionally swapping the source extension to .exe and gating on File.Exists:

string apphost = Path.ChangeExtension(source, ".exe");
return File.Exists(apphost) ? (apphost, ...) : ("dotnet", ...);

The apphost file name is OS-specific: Foo.exe on Windows, but extension-less Foo on Unix. On a normal Linux box the .exe doesn't exist and this harmlessly falls back to dotnet <dll>. But when a test payload is built on Windows and run on Unix (e.g. tests built on a Windows agent, zipped, and executed on a Linux Helix machine), the Windows PE Foo.exe travels alongside the dll — and zip carries no Unix permission bits. File.Exists then finds it and Process.Start fails:

Microsoft.Testing.Platform run failed for '.../Foo.dll':
An error occurred trying to start process '.../Foo.exe' ... Permission denied

(chmod +x would only turn this into Exec format error — it's a Windows binary.)

This surfaced in dotnet/aspnetcore's CI (Windows build agents + Linux Helix queues) on an xunit.v3 (MTP-capable) test assembly, but it affects any repo that builds MTP tests (xunit.v3 / MSTest-on-MTP / TUnit) on Windows and runs them on Linux.

Fix

Make the apphost probe OS-aware, and on Unix require the candidate to actually be executable before launching it (otherwise fall back to dotnet <dll>):

  • Windows → Foo.exe; Unix → extension-less Foo.
  • New IsUsableApphost: file must exist, and on Unix must carry an execute bit (File.GetUnixFileMode), so a stray non-native sibling (e.g. a Windows PE copied onto Unix) is never selected.

The OS check is #if NETFRAMEWORK-guarded (net462 has no RuntimeInformation and is Windows-only); the Unix-mode check is #if NET-guarded (File.GetUnixFileMode is .NET 7+). On netstandard2.0 it degrades to existence-only, which is still correct because the OS-aware path selection already resolves the bug.

Tests

Adds MtpServerConnectionTests covering: .exe source passes through; dll with a sibling Windows-only .exe on Unix is not selected (falls back to dotnet <dll>); dll with no apphost falls back; and IsUsableApphost execute-bit semantics on Unix.

Verification

Reproduced end-to-end with a source-built runner, A/B-swapping only CrossPlatEngine.dll against a real xunit.v3 app plus a dummy sibling .exe, under the same runtime:

  • Before: ... start process '.../MtpRepro.exe' ... Permission denied → Test Run Aborted.
  • After: Passed! 1 — the Windows .exe is ignored. A genuine executable Unix apphost is still selected and launched, and a classic (non-MTP) xUnit v2 project is unaffected.

Regression from #16201.

Copilot AI lite review requested due to automatic review settings August 3, 2026 10:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a cross-platform regression in the CrossPlatEngine MTP bridge where apphost probing was OS-blind, potentially selecting a Windows *.exe apphost when running on Unix and causing Process.Start failures. The update makes apphost selection OS-aware and adds unit tests around BuildLaunch/apphost usability semantics.

Changes:

  • Update MtpServerConnection.BuildLaunch to probe Foo.exe on Windows and extension-less Foo on Unix, and gate selection via IsUsableApphost.
  • Add IsUsableApphost (Unix execute-bit check under #if NET) to avoid selecting non-executable apphost candidates on Unix when supported.
  • Add new unit tests validating fallback behavior and Unix execute-bit semantics.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpServerConnection.cs Makes apphost probing OS-aware and introduces IsUsableApphost to avoid launching unusable apphost candidates.
test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/MTP/MtpServerConnectionTests.cs Adds unit tests for BuildLaunch and Unix execute-bit behavior (with OS-conditional coverage).

Comment thread src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpServerConnection.cs Outdated
…6201)

MtpServerConnection.BuildLaunch probed for a sibling apphost by swapping the
source extension to .exe unconditionally and gating on File.Exists. On Unix the
apphost is extension-less, so the .exe probe is wrong; worse, a payload built on
Windows and run on Unix (e.g. aspnetcore tests built on a Windows agent and
executed on a Linux Helix machine) drags a Windows PE Foo.exe next to the dll,
which File.Exists happily finds and Process.Start then fails to launch with
'Permission denied' (or 'Exec format error' once +x).

Make the probe OS-aware (extension-less apphost on Unix) and, on Unix, require
an execute bit before treating the candidate as a usable apphost; otherwise fall
back to 'dotnet <dll>'. Adds unit tests for BuildLaunch/IsUsableApphost.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 6, 2026 10:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpServerConnection.cs:12

  • using System.Runtime.InteropServices; is only needed for the non-NETFRAMEWORK builds (where RuntimeInformation/OSPlatform are referenced). In the net462 target the #if NETFRAMEWORK branch removes all uses, so this using becomes unused and can trigger IDE0005 (this repo commonly wraps such usings with TFM guards; e.g. src/Microsoft.TestPlatform.PlatformAbstractions/net462/System/ProcessHelper.cs). Consider conditionally including the using to avoid build breaks in multi-target builds.
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

@azat-msft
Azat Mukhametshin (azat-msft) merged commit 384428e into microsoft:main Aug 6, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants