From d3f2ca9c9aafc8ce1c76c688eaa82be2f9a69a51 Mon Sep 17 00:00:00 2001 From: "Chris Ross (ASP.NET)" Date: Tue, 18 Sep 2018 16:27:03 -0700 Subject: [PATCH] Do not inherit socket handles #2789 --- .../Internal/NativeMethods.cs | 31 ++++++++++ .../SocketTransport.cs | 1 + .../HandleInheritanceTests.cs | 60 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src/Kestrel.Transport.Sockets/Internal/NativeMethods.cs create mode 100644 test/Kestrel.Transport.FunctionalTests/HandleInheritanceTests.cs diff --git a/src/Kestrel.Transport.Sockets/Internal/NativeMethods.cs b/src/Kestrel.Transport.Sockets/Internal/NativeMethods.cs new file mode 100644 index 0000000000..a77efc2c35 --- /dev/null +++ b/src/Kestrel.Transport.Sockets/Internal/NativeMethods.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal +{ + internal static class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetHandleInformation(IntPtr hObject, HANDLE_FLAGS dwMask, HANDLE_FLAGS dwFlags); + + [Flags] + private enum HANDLE_FLAGS : uint + { + None = 0, + INHERIT = 1, + PROTECT_FROM_CLOSE = 2 + } + + internal static void DisableHandleInheritance(Socket socket) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + SetHandleInformation(socket.Handle, HANDLE_FLAGS.INHERIT, 0); + } + } + } +} diff --git a/src/Kestrel.Transport.Sockets/SocketTransport.cs b/src/Kestrel.Transport.Sockets/SocketTransport.cs index 730c36c2d1..bc61f1a97f 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransport.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransport.cs @@ -80,6 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets IPEndPoint endPoint = _endPointInformation.IPEndPoint; var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + NativeMethods.DisableHandleInheritance(listenSocket); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 if (endPoint.Address == IPAddress.IPv6Any) diff --git a/test/Kestrel.Transport.FunctionalTests/HandleInheritanceTests.cs b/test/Kestrel.Transport.FunctionalTests/HandleInheritanceTests.cs new file mode 100644 index 0000000000..cbbcb8b454 --- /dev/null +++ b/test/Kestrel.Transport.FunctionalTests/HandleInheritanceTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public class HandleInheritanceTests : TestApplicationErrorLoggerLoggedTest + { + [ConditionalFact] + [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "No fix available for Mac https://github.com/aspnet/KestrelHttpServer/pull/2944#issuecomment-426397600")] + public async Task SpawnChildProcess_DoesNotInheritListenHandle() + { + var hostBuilder = TransportSelector.GetWebHostBuilder() + .UseKestrel() + .ConfigureServices(AddTestLogging) + .UseUrls("http://127.0.0.1:0") + .Configure(app => + { + app.Run(context => + { + return context.Response.WriteAsync("Hello World"); + }); + }); + + using (var host = hostBuilder.Build()) + { + await host.StartAsync(); + + var processInfo = new ProcessStartInfo + { + FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "vi", + CreateNoWindow = true, + }; + using (var process = Process.Start(processInfo)) + { + var port = host.GetPort(); + await host.StopAsync(); + + // We should not be able to connect if the handle was correctly closed and not inherited by the child process. + using (var client = new TcpClient()) + { + await Assert.ThrowsAnyAsync(() => client.ConnectAsync("127.0.0.1", port)); + } + + process.Kill(); + } + } + } + } +}