-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Fix UseSystemd() silently failing with ProtectProc=invisible #125520
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
cincuranet
merged 20 commits into
dotnet:main
from
CybCorv:fix/systemd-protect-proc-invisible
Apr 23, 2026
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
74e3641
Fix UseSystemd() silently failing with ProtectProc=invisible
CybCorv db71c71
Remove unused variable in Test
CybCorv 87a6fd5
Merge branch 'main' into fix/systemd-protect-proc-invisible
CybCorv 83c25dd
Merge branch 'dotnet:main' into fix/systemd-protect-proc-invisible
CybCorv 8d3278f
Add SYSTEMD_EXEC_PID constant and refactor comments in SystemdHelpers
CybCorv 9e3f6c7
Fall through to legacy detection on SYSTEMD_EXEC_PID mismatch
CybCorv a0c7769
Decouple sd_notify and systemd log formatter detection
CybCorv 452bdc3
Address Copilot review feedback
CybCorv e0dc29b
Merge branch 'main' into fix/systemd-protect-proc-invisible
CybCorv d6d7269
Refactor tests: consolidate assertions and align with new detection l…
CybCorv 381df75
Address tmds review feedback
CybCorv 09da18f
Address Copilot review feedback
CybCorv 7ab282e
Update src/libraries/Microsoft.Extensions.Hosting.Systemd/src/Systemd…
CybCorv b9581d4
Update src/libraries/Microsoft.Extensions.Hosting.Systemd/tests/UseSy…
CybCorv ee82b81
Address Copilot review feedbacks
CybCorv 5441de8
Update src/libraries/Microsoft.Extensions.Hosting.Systemd/src/Systemd…
cincuranet 89fef01
Merge branch 'main' into fix/systemd-protect-proc-invisible
cincuranet 56b6551
Update src/libraries/Microsoft.Extensions.Hosting.Systemd/src/Systemd…
cincuranet 801e28c
Update src/libraries/Microsoft.Extensions.Hosting.Systemd/src/Systemd…
cincuranet 27374ef
Merge branch 'main' into fix/systemd-protect-proc-invisible
cincuranet File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdConstants.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Microsoft.Extensions.Hosting.Systemd | ||
| { | ||
| internal static class SystemdConstants | ||
| { | ||
| /// <summary> | ||
| /// Environment variable set by systemd (v229+) to the path of the notify socket for the service. If this variable is set, the service should send status notifications to systemd using this socket. | ||
| /// </summary> | ||
| /// <see href="https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#%24NOTIFY_SOCKET" /> | ||
| internal const string NotifySocket = "NOTIFY_SOCKET"; | ||
|
|
||
| /// <summary> | ||
| /// Environment variable set by systemd (v248+) to the PID of the main service process. | ||
| /// This is the most reliable way to detect if we're running as a systemd service, as it doesn't | ||
| /// require reading /proc and works even when ProtectProc=invisible is set. | ||
| /// </summary> | ||
| /// <see href="https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#%24SYSTEMD_EXEC_PID" /> | ||
| internal const string SystemdExecPid = "SYSTEMD_EXEC_PID"; | ||
|
|
||
| /// <summary> | ||
| /// Environment variable set by systemd for socket activation, indicating the PID | ||
| /// that should receive the listen file descriptors. | ||
| /// </summary> | ||
| /// <see href="https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#%24LISTEN_FDS" /> | ||
| internal const string ListenPid = "LISTEN_PID"; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 148 additions & 0 deletions
148
src/libraries/Microsoft.Extensions.Hosting.Systemd/tests/SystemdHelpersTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Globalization; | ||
| using Microsoft.DotNet.RemoteExecutor; | ||
| using Microsoft.Extensions.Hosting.Systemd; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Extensions.Hosting | ||
|
CybCorv marked this conversation as resolved.
|
||
| { | ||
| public class SystemdHelpersTests | ||
| { | ||
| public static bool IsRemoteExecutorSupportedOnLinux => PlatformDetection.IsLinux && RemoteExecutor.IsSupported; | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceReturnsTrueWhenSystemdExecPidMatchesCurrentProcessId() | ||
| { | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| string processId = Environment.ProcessId.ToString(CultureInfo.InvariantCulture); | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", processId); | ||
|
|
||
| Assert.True(SystemdHelpers.IsSystemdService()); | ||
| }); | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceReturnsFalseWhenSystemdExecPidDoesNotMatchOutsideSystemdSession() | ||
| { | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| // When SYSTEMD_EXEC_PID is set but doesn't match the current PID, the code | ||
| // falls through to legacy detection. Outside a real systemd session | ||
| // (not PID 1, parent is not named "systemd"), the legacy path returns false. | ||
| // Note: the fall-through itself cannot be directly observed in a unit test | ||
| // without being PID 1 or having a parent named "systemd". | ||
| int nonMatchingPid = int.MaxValue; // No real process will ever have this PID. | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", nonMatchingPid.ToString(CultureInfo.InvariantCulture)); | ||
| Environment.SetEnvironmentVariable("NOTIFY_SOCKET", null); | ||
| Environment.SetEnvironmentVariable("LISTEN_PID", null); | ||
|
|
||
| Assert.False(SystemdHelpers.IsSystemdService()); | ||
| }); | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceReturnsFalseWhenSystemdExecPidIsAbsent() | ||
| { | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| // When SYSTEMD_EXEC_PID is absent the code skips the v248+ path entirely | ||
| // and falls through to the legacy detection. Outside a real systemd session | ||
| // (not PID 1, parent is not systemd), the result must be false. | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", null); | ||
| Environment.SetEnvironmentVariable("NOTIFY_SOCKET", null); | ||
| Environment.SetEnvironmentVariable("LISTEN_PID", null); | ||
|
|
||
| Assert.False(SystemdHelpers.IsSystemdService()); | ||
| }); | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceReturnsFalseWhenSystemdExecPidIsMalformed() | ||
| { | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| // A malformed SYSTEMD_EXEC_PID must not be trusted: the logic falls through | ||
| // to the legacy /proc-based detection, which won't match outside a real systemd session. | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", "not-a-pid"); | ||
| Environment.SetEnvironmentVariable("NOTIFY_SOCKET", null); | ||
| Environment.SetEnvironmentVariable("LISTEN_PID", null); | ||
|
|
||
| Assert.False(SystemdHelpers.IsSystemdService()); | ||
| }); | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceCachesFirstEvaluation() | ||
| { | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| string processId = Environment.ProcessId.ToString(CultureInfo.InvariantCulture); | ||
| int nonMatchingPid = int.MaxValue; // No real process will ever have this PID. | ||
|
|
||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", processId); | ||
| bool firstEvaluation = SystemdHelpers.IsSystemdService(); | ||
|
|
||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", nonMatchingPid.ToString(CultureInfo.InvariantCulture)); | ||
| bool secondEvaluation = SystemdHelpers.IsSystemdService(); | ||
|
|
||
| Assert.True(firstEvaluation); | ||
| Assert.True(secondEvaluation); | ||
| }); | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceCachesFirstNegativeEvaluation() | ||
| { | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", null); | ||
| Environment.SetEnvironmentVariable("NOTIFY_SOCKET", null); | ||
| Environment.SetEnvironmentVariable("LISTEN_PID", null); | ||
|
|
||
| var firstEvaluation = SystemdHelpers.IsSystemdService(); | ||
|
|
||
| string processId = Environment.ProcessId.ToString(CultureInfo.InvariantCulture); | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", processId); | ||
|
|
||
| var secondEvaluation = SystemdHelpers.IsSystemdService(); | ||
| Assert.False(firstEvaluation); | ||
| Assert.False(secondEvaluation); | ||
| }); | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceReturnsFalseWhenSystemdExecPidDoesNotMatchAndNotifySocketIsSet() | ||
| { | ||
| // Child process scenario: SYSTEMD_EXEC_PID is set but doesn't match the current PID, | ||
| // while NOTIFY_SOCKET is inherited from the parent service. | ||
| // The mismatch falls through to legacy detection (not PID 1, parent not named "systemd"), | ||
| // which returns false. NOTIFY_SOCKET is only considered in the PID 1 container path. | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| int nonMatchingPid = int.MaxValue; | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", nonMatchingPid.ToString(CultureInfo.InvariantCulture)); | ||
| Environment.SetEnvironmentVariable("NOTIFY_SOCKET", "/run/systemd/notify"); | ||
| Environment.SetEnvironmentVariable("LISTEN_PID", null); | ||
|
|
||
| Assert.False(SystemdHelpers.IsSystemdService()); | ||
| }); | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(SystemdHelpersTests), nameof(IsRemoteExecutorSupportedOnLinux))] | ||
| public void IsSystemdServiceReturnsFalseWhenNotifySocketIsEmpty() | ||
| { | ||
| using var _ = RemoteExecutor.Invoke(static () => | ||
| { | ||
| Environment.SetEnvironmentVariable("SYSTEMD_EXEC_PID", null); | ||
| Environment.SetEnvironmentVariable("NOTIFY_SOCKET", ""); | ||
| Environment.SetEnvironmentVariable("LISTEN_PID", null); | ||
|
|
||
| Assert.False(SystemdHelpers.IsSystemdService()); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.