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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ private sealed class SharedServer
private readonly int _maxCount;
/// <summary>The concurrent number of concurrent streams using this instance.</summary>
private int _currentCount;
/// <summary>Whether the socket file mode has been tightened to current-user-only (0600).</summary>
private bool _isCurrentUserOnly;

internal static SharedServer Get(string path, int maxCount, PipeOptions pipeOptions)
{
Expand All @@ -252,9 +254,9 @@ internal static SharedServer Get(string path, int maxCount, PipeOptions pipeOpti

lock (s_servers)
{
SharedServer? server;
bool isFirstPipeInstance = (pipeOptions & PipeOptions.FirstPipeInstance) != 0;
if (s_servers.TryGetValue(path, out server))
bool isCurrentUserOnly = (pipeOptions & PipeOptions.CurrentUserOnly) != 0;
if (s_servers.TryGetValue(path, out SharedServer? server))
{
// On Windows, if a subsequent server stream is created for the same pipe and with a different
// max count, the subsequent count is largely ignored in that it doesn't change the number of
Expand All @@ -269,11 +271,21 @@ internal static SharedServer Get(string path, int maxCount, PipeOptions pipeOpti
{
throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
}

// Ratchet to current-user-only if requested. We never loosen, even if a later
// instance for the same path does not request CurrentUserOnly: silently downgrading
// a security choice another caller made would be worse than the order-dependent
// surprise of a non-CurrentUserOnly server inheriting a 0600 socket.
if (isCurrentUserOnly && !server._isCurrentUserOnly)
{
File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite);
server._isCurrentUserOnly = true;
}
}
else
{
// No instance exists yet for this path. Create one a new.
server = new SharedServer(path, maxCount, isFirstPipeInstance);
server = new SharedServer(path, maxCount, isFirstPipeInstance, isCurrentUserOnly);
s_servers.Add(path, server);
Comment thread
cincuranet marked this conversation as resolved.
}

Expand Down Expand Up @@ -308,7 +320,7 @@ internal void Dispose(bool disposing)
}
}

private SharedServer(string path, int maxCount, bool isFirstPipeInstance)
private SharedServer(string path, int maxCount, bool isFirstPipeInstance, bool isCurrentUserOnly)
{
if (!isFirstPipeInstance)
{
Expand All @@ -325,6 +337,13 @@ private SharedServer(string path, int maxCount, bool isFirstPipeInstance)
{
socket.Bind(new UnixDomainSocketEndPoint(path));
isSocketBound = true;

_isCurrentUserOnly = isCurrentUserOnly;
if (_isCurrentUserOnly)
{
File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite);
}

socket.Listen(int.MaxValue);
}
catch (SocketException) when (isFirstPipeInstance && !isSocketBound)
Expand All @@ -334,6 +353,10 @@ private SharedServer(string path, int maxCount, bool isFirstPipeInstance)
}
catch
{
if (isSocketBound)
{
Interop.Sys.Unlink(path); // ignore any failures
}
socket.Dispose();
throw;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,59 @@ public async Task Connection_UnderDifferentUsers_BehavesAsExpected(
}
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/77469", TestPlatforms.iOS | TestPlatforms.tvOS)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/77470", TestPlatforms.LinuxBionic)]
public void CurrentUserOnly_SetsUnixFileMode()
{
string pipeName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

using (var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly))
{
Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(pipeName));
}
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/77469", TestPlatforms.iOS | TestPlatforms.tvOS)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/77470", TestPlatforms.LinuxBionic)]
public void CurrentUserOnly_SubsequentInstance_TightensSharedMode()
{
string pipeName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

using (var first = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.None))
{
UnixFileMode initialMode = File.GetUnixFileMode(pipeName);
Assert.NotEqual(UnixFileMode.UserRead | UnixFileMode.UserWrite, initialMode);

using (var second = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly))
{
Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(pipeName));
}

// Mode is one-way: it stays restrictive even after the CurrentUserOnly instance is disposed.
Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(pipeName));
}
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/77469", TestPlatforms.iOS | TestPlatforms.tvOS)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/77470", TestPlatforms.LinuxBionic)]
public void CurrentUserOnly_SubsequentInstance_DoesNotLoosenSharedMode()
{
string pipeName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

using (var first = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly))
{
Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(pipeName));

using (var second = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.None))
{
Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(pipeName));
}
}
}

private static void ConnectClientFromRemoteInvoker(string pipeName, string isCurrentUserOnly, string isReadOnly)
{
PipeOptions pipeOptions = bool.Parse(isCurrentUserOnly) ? PipeOptions.CurrentUserOnly : PipeOptions.None;
Expand Down
Loading