Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Require an execute bit before selecting a Unix apphost - #10641

Merged
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho
Aug 26, 2026
Merged

Require an execute bit before selecting a Unix apphost#10641
Amaury Levé (Evangelink) merged 78 commits into
microsoft:mainfrom
nohwnd:nohwnd-restore-unix-execute-bit-guard-mtp-appho

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Aug 18, 2026

Copy link
Copy Markdown
Member

When launching a managed .dll, the server-mode client prefers a sibling apphost and decides with File.Exists, which says yes to a file that cannot be started. A test payload built on a Windows agent and executed on a Linux machine arrives with its extensionless apphost stripped of POSIX permission bits, because a zip written on Windows records none. Process.Start then throws Permission denied and aborts the run rather than falling back.

BuildLaunch now gates the choice on IsUsableApphost, which additionally requires an execute bit on Unix, so an unusable candidate falls back to dotnet <dll>. File.GetUnixFileMode is .NET 7+, so the check is fenced on NET7_0_OR_GREATER and net462, netstandard2.0, net5.0 and net6.0 consumers keep the existence-only check. That stays correct because the apphost path probe is already OS-aware and never offers a Windows .exe on Unix.

The fence is the target framework rather than the package's modern-.NET symbol, which records which JSON slice a consumer compiles and is defined only for net8.0+. NuGet serves the net5.0 slice to net5.0, net6.0 and net7.0 alike, so fencing on it would leave a Linux net7.0 consumer on the existence-only path even though it has the API. An anti-drift test asserts the call sits inside a NET7_0_OR_GREATER block in every packed slice.

This restores the second half of the fix in microsoft/vstest#16336, which was lost when vstest deleted its own client in favour of this package in microsoft/vstest#16300.

Reproduced end to end before fixing: dotnet publish -r linux-x64 on Windows emits an extensionless apphost beside the .dll; a Windows-written zip stores ExternalAttributes = 0; extracting on Linux yields mode 0644; Process.Start throws Permission denied. The four alternatives were all checked and none holds — the check is absent from main, the layout is reachable, Process.Start does not degrade gracefully, and the .dll branch is live for consumers even though testfx's own callers pass an apphost.

Verified: build.cmd -pack passes; the new tests pass on Windows net8.0 and net462 and on Linux net8.0, and reverting either guard makes its test fail.

Follow-up in microsoft/vstest, once a package with this ships: bump MicrosoftTestingPlatformServerModeClientSourcesVersion in eng/Versions.props and add an acceptance test for the layout.

🤖

The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 18, 2026 14:08

CopilotAI 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

Restores Unix apphost validation in the source-only server-mode client after vstest adopted it.

Changes:

  • Requires Unix sibling apphosts to have an execute bit.
  • Falls back to dotnet <dll> for unusable apphosts.
  • Adds cross-platform launch-selection tests.

Reviewed changes

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

FileDescription
MtpServerProcess.csAdds apphost usability validation and fallback logic.
MtpServerProcessTests.csCovers Windows and Unix apphost selection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 20, 2026 10:03

CopilotAI 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 3 out of 3 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6402.102 to 5.10.0-1.26423.1 (microsoft#10675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f616dbd-edac-4ae9-b58a-b0d7b4739297
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#10657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
…microsoft#10581)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t/arcade (microsoft#10565)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3044575 (microsoft#10551)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…: Build ID 3055708 (microsoft#10679)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
…icrosoft#10656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: cdaa219d-8e65-402b-acb4-ff9be4486eee
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c661d151-50e8-4112-be24-c3144a02580e
Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
Copilot-Session: 05d06302-12f7-4db0-a3a7-8923510820f9
…icrosoft#10731)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…icrosoft#10640)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Copilot-Session: fc14aba1-535f-404f-b2c2-56475e3ace7c
Copilot-Session: 99c59e2a-5c76-4bf2-bdc7-20992c477981
Copilot-Session: 86687a11-fb1e-4b64-933a-7fbd6f5bcba3
…ft#10735)
Copilot-Session: d952f592-db2e-4a30-b46d-312c105eca7c
Copilot-Session: 469109f6-5c6d-4ebb-af75-24785643dc56
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…soft#10749)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
…: Build ID 3057636 (microsoft#10750)
Co-authored-by: dotnet-oneloc-localization[bot] <310689744+dotnet-oneloc-localization[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
…26276-01 to 18.8.0-release-26326-101 (microsoft#10758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…icrosoft#10759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.1.3-beta1.24423.1 to 1.1.3-beta1.26255.1 (microsoft#10753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The server-mode client picks a sibling apphost for a managed .dll by asking
File.Exists, which says yes to a file that cannot be launched. A payload built
on a Windows agent and run on a Linux machine arrives with its extensionless
apphost stripped of POSIX permission bits, because a zip written on Windows
records none. Process.Start then throws Permission denied and aborts the run
instead of falling back.
Gate the choice on IsUsableApphost, which additionally requires an execute bit
on Unix. File.GetUnixFileMode is .NET 7+, so net462 and netstandard2.0
consumers keep the existence-only check, which stays correct because the
apphost path probe is already OS-aware.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check was fenced on NETCOREAPP, which the pack transform rewrites to
MTP_CLIENT_USE_MODERN_DOTNET. That symbol records which JSON slice a consumer
compiles and is defined only for net8.0+, but NuGet serves the net5.0 slice to
net5.0, net6.0 and net7.0 consumers alike. A Linux net7.0 consumer therefore
stayed on the existence-only path, selected a non-executable apphost and failed
with Permission denied, even though File.GetUnixFileMode is available to it.
Fence on the target framework instead, which is what actually tracks the API and
which the pack transform leaves alone. Add an anti-drift test asserting the call
sits inside a NET7_0_OR_GREATER block in every packed slice.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle EACCES from sibling apphosts across all consumer target frameworks and add package-level net6 regression coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 09:14

CopilotAI 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.

Review details

  • Files reviewed: 43/461 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Evangelink
Amaury Levé (Evangelink) merged commit c6be1fa into microsoft:mainAug 26, 2026
53 checks passed
Sign up for freeto 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.

5 participants

@nohwnd@Evangelink@vaibhav8a@nohwnd-bot