summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authoralaviss <alaviss@users.noreply.github.com>2020-04-20 15:09:59 +0000
committerGitHub <noreply@github.com>2020-04-20 17:09:59 +0200
commit1bdc30bdb13422bd5eff830327582a938e7b76ac (patch)
tree48c3d5e4f677042b3dd750cae5a255768a03da1a /lib
parent6bd279c97871e47c78636f3c1a8f39a7915b1402 (diff)
downloadNim-1bdc30bdb13422bd5eff830327582a938e7b76ac.tar.gz
Make file descriptors from stdlib non-inheritable by default (#13201)
* io: make file descriptors non-inheritable by default

This prevents file descriptors/handles leakage to child processes
that might cause issues like running out of file descriptors, or potential
security issues like leaking a file descriptor to a restricted file.

While this breaks backward compatibility, I'm rather certain that not
many programs (if any) actually make use of this implementation detail.
A new API `setInheritable` is provided for the few that actually want to
use this functionality.

* io: disable inheritance at file creation time for supported platforms

Some platforms provide extension to fopen-family of functions to allow
for disabling descriptor inheritance atomically during File creation.
This guards against possible leaks when a child process is spawned
before we managed to disable the file descriptor inheritance
(ie. in a multi-threaded program).

* net, nativesockets: make sockets non inheritable by default

With this commit, sockets will no longer leak to child processes when
you don't want it to. Should solves a lot of "address in use" that might
occur when your server has just restarted.

All APIs that create sockets in these modules now expose a `inheritable`
flag that allow users to toggle inheritance for the resulting sockets.
An implementation of `setInheritance()` is also provided for SocketHandle.

While atomically disabling inheritance at creation time is supported on
Windows, it's only implemented by native winsock2, which is too much for
now. This support can be implemented in a future patch.

* posix: add F_DUPFD_CLOEXEC

This command duplicates file descriptor with close-on-exec flag set.

Defined in POSIX.1-2008.

* ioselectors_kqueue: don't leak file descriptors

File descriptors internally used by ioselectors on BSD/OSX are now
shielded from leakage.

* posix: add O_CLOEXEC

This flag allows file descriptors to be open() with close-on-exec flag
set atomically.

This flag is specified in POSIX.1-2008

* tfdleak: test for selectors leakage

Also simplified the test by using handle-type agnostic APIs to test for
validity.

* ioselectors_epoll: mark all fd created close-on-exec

File descriptors from ioselectors should no longer leaks on Linux.

* tfdleak: don't check for selector leakage on Windows

The getFd proc for ioselectors_select returns a hardcoded -1

* io: add NoInheritFlag at compile time

* io: add support for ioctl-based close-on-exec

This allows for the flag to be set/unset in one syscall. While the
performance gains might be negliable, we have one less failure point
to deal with.

* tfdleak: add a test for setInheritable

* stdlib: add nimInheritHandles to restore old behaviors

* memfiles: make file handle not inheritable by default for posix

* io: setInheritable now operates on OS file handle

On Windows, the native handle is the only thing that's inheritable, thus
we can assume that users of this function will already have the handle
available to them. This also allows users to pass down file descriptors
from memfiles on Windows with ease, should that be desired.

With this, nativesockets.setInheritable can be made much simpler.

* changelog: clarify

* nativesockets: document setInheritable return value

* posix_utils: atomically disable fd inheritance for mkstemp
Diffstat (limited to 'lib')
-rw-r--r--lib/posix/posix.nim7
-rw-r--r--lib/posix/posix_linux_amd64_consts.nim3
-rw-r--r--lib/posix/posix_macos_amd64.nim3
-rw-r--r--lib/posix/posix_openbsd_amd64.nim2
-rw-r--r--lib/posix/posix_other.nim3
-rw-r--r--lib/posix/posix_other_consts.nim2
-rw-r--r--lib/posix/posix_utils.nim6
-rw-r--r--lib/pure/ioselects/ioselectors_epoll.nim10
-rw-r--r--lib/pure/ioselects/ioselectors_kqueue.nim8
-rw-r--r--lib/pure/memfiles.nim2
-rw-r--r--lib/pure/nativesockets.nim76
-rw-r--r--lib/pure/net.nim37
-rw-r--r--lib/system/io.nim90
-rw-r--r--lib/windows/winlean.nim5
14 files changed, 210 insertions, 44 deletions
diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim
index d1f2faca5..3e423c64b 100644
--- a/lib/posix/posix.nim
+++ b/lib/posix/posix.nim
@@ -896,6 +896,10 @@ proc `==`*(x, y: SocketHandle): bool {.borrow.}
 proc accept*(a1: SocketHandle, a2: ptr SockAddr, a3: ptr SockLen): SocketHandle {.
   importc, header: "<sys/socket.h>", sideEffect.}
 
+when defined(linux) or defined(bsd):
+  proc accept4*(a1: SocketHandle, a2: ptr SockAddr, a3: ptr SockLen,
+                flags: cint): SocketHandle {.importc, header: "<sys/socket.h>".}
+
 proc bindSocket*(a1: SocketHandle, a2: ptr SockAddr, a3: SockLen): cint {.
   importc: "bind", header: "<sys/socket.h>".}
   ## is Posix's ``bind``, because ``bind`` is a reserved word
@@ -1036,6 +1040,9 @@ proc mkstemp*(tmpl: cstring): cint {.importc, header: "<stdlib.h>", sideEffect.}
 
 proc mkdtemp*(tmpl: cstring): pointer {.importc, header: "<stdlib.h>", sideEffect.}
 
+when defined(linux) or defined(bsd):
+  proc mkostemp*(tmpl: cstring, oflags: cint): cint {.importc, header: "<stdlib.h>", sideEffect.}
+
 proc utimes*(path: cstring, times: ptr array[2, Timeval]): int {.
   importc: "utimes", header: "<sys/time.h>", sideEffect.}
   ## Sets file access and modification times.
diff --git a/lib/posix/posix_linux_amd64_consts.nim b/lib/posix/posix_linux_amd64_consts.nim
index 7352e8e35..84296eb9b 100644
--- a/lib/posix/posix_linux_amd64_consts.nim
+++ b/lib/posix/posix_linux_amd64_consts.nim
@@ -100,6 +100,7 @@ const EXDEV* = cint(18)
 
 # <fcntl.h>
 const F_DUPFD* = cint(0)
+const F_DUPFD_CLOEXEC* = cint(1030)
 const F_GETFD* = cint(1)
 const F_SETFD* = cint(2)
 const F_GETFL* = cint(3)
@@ -126,6 +127,7 @@ const O_ACCMODE* = cint(3)
 const O_RDONLY* = cint(0)
 const O_RDWR* = cint(2)
 const O_WRONLY* = cint(1)
+const O_CLOEXEC* = cint(524288)
 const POSIX_FADV_NORMAL* = cint(0)
 const POSIX_FADV_SEQUENTIAL* = cint(2)
 const POSIX_FADV_RANDOM* = cint(1)
@@ -469,6 +471,7 @@ const SOCK_DGRAM* = cint(2)
 const SOCK_RAW* = cint(3)
 const SOCK_SEQPACKET* = cint(5)
 const SOCK_STREAM* = cint(1)
+const SOCK_CLOEXEC* = cint(524288)
 const SOL_SOCKET* = cint(1)
 const SOMAXCONN* = cint(128)
 const SO_REUSEPORT* = cint(15)
diff --git a/lib/posix/posix_macos_amd64.nim b/lib/posix/posix_macos_amd64.nim
index 536d51be5..304993dcb 100644
--- a/lib/posix/posix_macos_amd64.nim
+++ b/lib/posix/posix_macos_amd64.nim
@@ -557,6 +557,9 @@ when defined(linux) or defined(nimdoc):
 else:
   var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint
 
+when defined(linux) or defined(bsd):
+  var SOCK_CLOEXEC* {.importc, header: "<sys/socket.h>".}: cint
+
 when defined(macosx):
   # We can't use the NOSIGNAL flag in the ``send`` function, it has no effect
   # Instead we should use SO_NOSIGPIPE in setsockopt
diff --git a/lib/posix/posix_openbsd_amd64.nim b/lib/posix/posix_openbsd_amd64.nim
index 408e42255..1ff636517 100644
--- a/lib/posix/posix_openbsd_amd64.nim
+++ b/lib/posix/posix_openbsd_amd64.nim
@@ -532,6 +532,8 @@ when defined(nimdoc):
 else:
   var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint
 
+var SOCK_CLOEXEC* {.importc, header: "<sys/socket.h>".}: cint
+
 var MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint
 
 when hasSpawnH:
diff --git a/lib/posix/posix_other.nim b/lib/posix/posix_other.nim
index 8213f796c..204cc3d9a 100644
--- a/lib/posix/posix_other.nim
+++ b/lib/posix/posix_other.nim
@@ -562,6 +562,9 @@ when defined(linux) or defined(nimdoc):
 else:
   var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint
 
+when defined(linux) or defined(bsd):
+  var SOCK_CLOEXEC* {.importc, header: "<sys/socket.h>".}: cint
+
 when defined(macosx):
   # We can't use the NOSIGNAL flag in the ``send`` function, it has no effect
   # Instead we should use SO_NOSIGPIPE in setsockopt
diff --git a/lib/posix/posix_other_consts.nim b/lib/posix/posix_other_consts.nim
index 9fcbe425d..f43407b40 100644
--- a/lib/posix/posix_other_consts.nim
+++ b/lib/posix/posix_other_consts.nim
@@ -99,6 +99,7 @@ var EXDEV* {.importc: "EXDEV", header: "<errno.h>".}: cint
 
 # <fcntl.h>
 var F_DUPFD* {.importc: "F_DUPFD", header: "<fcntl.h>".}: cint
+var F_DUPFD_CLOEXEC* {.importc: "F_DUPFD", header: "<fcntl.h>".}: cint
 var F_GETFD* {.importc: "F_GETFD", header: "<fcntl.h>".}: cint
 var F_SETFD* {.importc: "F_SETFD", header: "<fcntl.h>".}: cint
 var F_GETFL* {.importc: "F_GETFL", header: "<fcntl.h>".}: cint
@@ -125,6 +126,7 @@ var O_ACCMODE* {.importc: "O_ACCMODE", header: "<fcntl.h>".}: cint
 var O_RDONLY* {.importc: "O_RDONLY", header: "<fcntl.h>".}: cint
 var O_RDWR* {.importc: "O_RDWR", header: "<fcntl.h>".}: cint
 var O_WRONLY* {.importc: "O_WRONLY", header: "<fcntl.h>".}: cint
+var O_CLOEXEC* {.importc: "O_CLOEXEC", header: "<fcntl.h>".}: cint
 var POSIX_FADV_NORMAL* {.importc: "POSIX_FADV_NORMAL", header: "<fcntl.h>".}: cint
 var POSIX_FADV_SEQUENTIAL* {.importc: "POSIX_FADV_SEQUENTIAL", header: "<fcntl.h>".}: cint
 var POSIX_FADV_RANDOM* {.importc: "POSIX_FADV_RANDOM", header: "<fcntl.h>".}: cint
diff --git a/lib/posix/posix_utils.nim b/lib/posix/posix_utils.nim
index 5dbb163ca..2d0288187 100644
--- a/lib/posix/posix_utils.nim
+++ b/lib/posix/posix_utils.nim
@@ -83,7 +83,11 @@ proc mkstemp*(prefix: string): (string, File) =
   ## The file is created with perms 0600.
   ## Returns the filename and a file opened in r/w mode.
   var tmpl = cstring(prefix & "XXXXXX")
-  let fd = mkstemp(tmpl)
+  let fd =
+    when declared(mkostemp):
+      mkostemp(tmpl, O_CLOEXEC)
+    else:
+      mkstemp(tmpl)
   var f: File
   if open(f, fd, fmReadWrite):
     return ($tmpl, f)
diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim
index bf13cc83e..83f6001ea 100644
--- a/lib/pure/ioselects/ioselectors_epoll.nim
+++ b/lib/pure/ioselects/ioselectors_epoll.nim
@@ -81,7 +81,7 @@ proc newSelector*[T](): Selector[T] =
   # Start with a reasonable size, checkFd() will grow this on demand
   const numFD = 1024
 
-  var epollFD = epoll_create(MAX_EPOLL_EVENTS)
+  var epollFD = epoll_create1(O_CLOEXEC)
   if epollFD < 0:
     raiseOSError(osLastError())
 
@@ -110,7 +110,7 @@ proc close*[T](s: Selector[T]) =
     raiseIOSelectorsError(osLastError())
 
 proc newSelectEvent*(): SelectEvent =
-  let fdci = eventfd(0, 0)
+  let fdci = eventfd(0, O_CLOEXEC)
   if fdci == -1:
     raiseIOSelectorsError(osLastError())
   setNonBlocking(fdci)
@@ -269,7 +269,7 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
   var
     newTs: Itimerspec
     oldTs: Itimerspec
-  let fdi = timerfd_create(CLOCK_MONOTONIC, 0).int
+  let fdi = timerfd_create(CLOCK_MONOTONIC, O_CLOEXEC).int
   if fdi == -1:
     raiseIOSelectorsError(osLastError())
   setNonBlocking(fdi.cint)
@@ -314,7 +314,7 @@ when not defined(android):
     discard sigaddset(nmask, cint(signal))
     blockSignals(nmask, omask)
 
-    let fdi = signalfd(-1, nmask, 0).int
+    let fdi = signalfd(-1, nmask, O_CLOEXEC).int
     if fdi == -1:
       raiseIOSelectorsError(osLastError())
     setNonBlocking(fdi.cint)
@@ -341,7 +341,7 @@ when not defined(android):
     discard sigaddset(nmask, posix.SIGCHLD)
     blockSignals(nmask, omask)
 
-    let fdi = signalfd(-1, nmask, 0).int
+    let fdi = signalfd(-1, nmask, O_CLOEXEC).int
     if fdi == -1:
       raiseIOSelectorsError(osLastError())
     setNonBlocking(fdi.cint)
diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim
index 83e15d479..7635a04d5 100644
--- a/lib/pure/ioselects/ioselectors_kqueue.nim
+++ b/lib/pure/ioselects/ioselectors_kqueue.nim
@@ -9,7 +9,7 @@
 
 #  This module implements BSD kqueue().
 
-import posix, times, kqueue
+import posix, times, kqueue, nativesockets
 
 const
   # Maximum number of events that can be returned.
@@ -76,7 +76,7 @@ type
 
 proc getUnique[T](s: Selector[T]): int {.inline.} =
   # we create duplicated handles to get unique indexes for our `fds` array.
-  result = posix.fcntl(s.sock, F_DUPFD, s.sock)
+  result = posix.fcntl(s.sock, F_DUPFD_CLOEXEC, s.sock)
   if result == -1:
     raiseIOSelectorsError(osLastError())
 
@@ -96,8 +96,8 @@ proc newSelector*[T](): owned(Selector[T]) =
   # we allocating empty socket to duplicate it handle in future, to get unique
   # indexes for `fds` array. This is needed to properly identify
   # {Event.Timer, Event.Signal, Event.Process} events.
-  let usock = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
-                             posix.IPPROTO_TCP).cint
+  let usock = createNativeSocket(posix.AF_INET, posix.SOCK_STREAM,
+                                 posix.IPPROTO_TCP).cint
   if usock == -1:
     let err = osLastError()
     discard posix.close(kqFD)
diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim
index 152b03ec6..dab5483e4 100644
--- a/lib/pure/memfiles.nim
+++ b/lib/pure/memfiles.nim
@@ -228,7 +228,7 @@ proc open*(filename: string, mode: FileMode = fmRead,
       if result.handle != -1: discard close(result.handle)
       raiseOSError(errCode)
 
-    var flags = if readonly: O_RDONLY else: O_RDWR
+    var flags = (if readonly: O_RDONLY else: O_RDWR) or O_CLOEXEC
 
     if newFileSize != -1:
       flags = flags or O_CREAT or O_TRUNC
diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim
index d9256a921..8d0d6426d 100644
--- a/lib/pure/nativesockets.nim
+++ b/lib/pure/nativesockets.nim
@@ -185,19 +185,53 @@ proc toSockType*(protocol: Protocol): SockType =
   of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP, IPPROTO_ICMPV6:
     SOCK_RAW
 
-proc createNativeSocket*(domain: Domain = AF_INET,
-                      sockType: SockType = SOCK_STREAM,
-                      protocol: Protocol = IPPROTO_TCP): SocketHandle =
-  ## Creates a new socket; returns `osInvalidSocket` if an error occurs.
-  socket(toInt(domain), toInt(sockType), toInt(protocol))
+proc close*(socket: SocketHandle) =
+  ## closes a socket.
+  when useWinVersion:
+    discard winlean.closesocket(socket)
+  else:
+    discard posix.close(socket)
+  # TODO: These values should not be discarded. An OSError should be raised.
+  # http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
 
-proc createNativeSocket*(domain: cint, sockType: cint,
-                      protocol: cint): SocketHandle =
+when declared(setInheritable) or defined(nimdoc):
+  proc setInheritable*(s: SocketHandle, inheritable: bool): bool {.inline.} =
+    ## Set whether a socket is inheritable by child processes. Returns `true`
+    ## on success.
+    ##
+    ## This function is not implemented on all platform, test for availability
+    ## with `declared() <system.html#declared,untyped>`.
+    setInheritable(FileHandle s, inheritable)
+
+proc createNativeSocket*(domain: cint, sockType: cint, protocol: cint,
+                         inheritable: bool = defined(nimInheritHandles)): SocketHandle =
   ## Creates a new socket; returns `osInvalidSocket` if an error occurs.
   ##
+  ## `inheritable` decides if the resulting SocketHandle can be inherited
+  ## by child processes.
+  ##
   ## Use this overload if one of the enums specified above does
   ## not contain what you need.
-  socket(domain, sockType, protocol)
+  let sockType =
+    when (defined(linux) or defined(bsd)) and not defined(nimdoc):
+      if inheritable: sockType and not SOCK_CLOEXEC else: sockType or SOCK_CLOEXEC
+    else:
+      sockType
+  result = socket(domain, sockType, protocol)
+  when declared(setInheritable) and not (defined(linux) or defined(bsd)):
+    if not setInheritable(result, inheritable):
+      close result
+      return osInvalidSocket
+
+proc createNativeSocket*(domain: Domain = AF_INET,
+                         sockType: SockType = SOCK_STREAM,
+                         protocol: Protocol = IPPROTO_TCP,
+                         inheritable: bool = defined(nimInheritHandles)): SocketHandle =
+  ## Creates a new socket; returns `osInvalidSocket` if an error occurs.
+  ##
+  ## `inheritable` decides if the resulting SocketHandle can be inherited
+  ## by child processes.
+  createNativeSocket(toInt(domain), toInt(sockType), toInt(protocol))
 
 proc newNativeSocket*(domain: Domain = AF_INET,
                       sockType: SockType = SOCK_STREAM,
@@ -215,15 +249,6 @@ proc newNativeSocket*(domain: cint, sockType: cint,
   ## not contain what you need.
   createNativeSocket(domain, sockType, protocol)
 
-proc close*(socket: SocketHandle) =
-  ## closes a socket.
-  when useWinVersion:
-    discard winlean.closesocket(socket)
-  else:
-    discard posix.close(socket)
-  # TODO: These values should not be discarded. An OSError should be raised.
-  # http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
-
 proc bindAddr*(socket: SocketHandle, name: ptr SockAddr,
     namelen: SockLen): cint =
   result = bindSocket(socket, name, namelen)
@@ -652,14 +677,25 @@ proc selectWrite*(writefds: var seq[SocketHandle],
 
   pruneSocketSet(writefds, (wr))
 
-proc accept*(fd: SocketHandle): (SocketHandle, string) =
+proc accept*(fd: SocketHandle, inheritable = defined(nimInheritHandles)): (SocketHandle, string) =
   ## Accepts a new client connection.
   ##
+  ## `inheritable` decides if the resulting SocketHandle can be inherited by
+  ## child processes.
+  ##
   ## Returns (osInvalidSocket, "") if an error occurred.
   var sockAddress: Sockaddr_in
   var addrLen = sizeof(sockAddress).SockLen
-  var sock = accept(fd, cast[ptr SockAddr](addr(sockAddress)),
-                    addr(addrLen))
+  var sock =
+    when (defined(linux) or defined(bsd)) and not defined(nimdoc):
+      accept4(fd, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen),
+              if inheritable: 0 else: SOCK_CLOEXEC)
+    else:
+      accept(fd, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen))
+  when declared(setInheritable) and not (defined(linux) or defined(bsd)):
+    if not setInheritable(sock, inheritable):
+      close sock
+      sock = osInvalidSocket
   if sock == osInvalidSocket:
     return (osInvalidSocket, "")
   else:
diff --git a/lib/pure/net.nim b/lib/pure/net.nim
index 593e5c76e..30355226c 100644
--- a/lib/pure/net.nim
+++ b/lib/pure/net.nim
@@ -212,22 +212,32 @@ proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
   when defined(macosx) and not defined(nimdoc):
     setSockOptInt(fd, SOL_SOCKET, SO_NOSIGPIPE, 1)
 
-proc newSocket*(domain, sockType, protocol: cint, buffered = true): owned(Socket) =
+proc newSocket*(domain, sockType, protocol: cint, buffered = true,
+                inheritable = defined(nimInheritHandles)): owned(Socket) =
   ## Creates a new socket.
   ##
+  ## The SocketHandle associated with the resulting Socket will not be
+  ## inheritable by child processes by default. This can be changed via
+  ## the `inheritable` parameter.
+  ##
   ## If an error occurs OSError will be raised.
-  let fd = createNativeSocket(domain, sockType, protocol)
+  let fd = createNativeSocket(domain, sockType, protocol, inheritable)
   if fd == osInvalidSocket:
     raiseOSError(osLastError())
   result = newSocket(fd, domain.Domain, sockType.SockType, protocol.Protocol,
                      buffered)
 
 proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
-                protocol: Protocol = IPPROTO_TCP, buffered = true): owned(Socket) =
+                protocol: Protocol = IPPROTO_TCP, buffered = true,
+                inheritable = defined(nimInheritHandles)): owned(Socket) =
   ## Creates a new socket.
   ##
+  ## The SocketHandle associated with the resulting Socket will not be
+  ## inheritable by child processes by default. This can be changed via
+  ## the `inheritable` parameter.
+  ##
   ## If an error occurs OSError will be raised.
-  let fd = createNativeSocket(domain, sockType, protocol)
+  let fd = createNativeSocket(domain, sockType, protocol, inheritable)
   if fd == osInvalidSocket:
     raiseOSError(osLastError())
   result = newSocket(fd, domain, sockType, protocol, buffered)
@@ -872,7 +882,8 @@ proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
   freeaddrinfo(aiList)
 
 proc acceptAddr*(server: Socket, client: var owned(Socket), address: var string,
-                 flags = {SocketFlag.SafeDisconn}) {.
+                 flags = {SocketFlag.SafeDisconn},
+                 inheritable = defined(nimInheritHandles)) {.
                  tags: [ReadIOEffect], gcsafe, locks: 0.} =
   ## Blocks until a connection is being made from a client. When a connection
   ## is made sets ``client`` to the client socket and ``address`` to the address
@@ -882,19 +893,23 @@ proc acceptAddr*(server: Socket, client: var owned(Socket), address: var string,
   ## The resulting client will inherit any properties of the server socket. For
   ## example: whether the socket is buffered or not.
   ##
+  ## The SocketHandle associated with the resulting client will not be
+  ## inheritable by child processes by default. This can be changed via
+  ## the `inheritable` parameter.
+  ##
   ## The ``accept`` call may result in an error if the connecting socket
   ## disconnects during the duration of the ``accept``. If the ``SafeDisconn``
   ## flag is specified then this error will not be raised and instead
   ## accept will be called again.
   if client.isNil:
     new(client)
-  let ret = accept(server.fd)
+  let ret = accept(server.fd, inheritable)
   let sock = ret[0]
 
   if sock == osInvalidSocket:
     let err = osLastError()
     if flags.isDisconnectionError(err):
-      acceptAddr(server, client, address, flags)
+      acceptAddr(server, client, address, flags, inheritable)
     raiseOSError(err)
   else:
     address = ret[1]
@@ -964,10 +979,16 @@ when false: #defineSsl:
         doHandshake()
 
 proc accept*(server: Socket, client: var owned(Socket),
-             flags = {SocketFlag.SafeDisconn}) {.tags: [ReadIOEffect].} =
+             flags = {SocketFlag.SafeDisconn},
+             inheritable = defined(nimInheritHandles))
+            {.tags: [ReadIOEffect].} =
   ## Equivalent to ``acceptAddr`` but doesn't return the address, only the
   ## socket.
   ##
+  ## The SocketHandle associated with the resulting client will not be
+  ## inheritable by child processes by default. This can be changed via
+  ## the `inheritable` parameter.
+  ##
   ## The ``accept`` call may result in an error if the connecting socket
   ## disconnects during the duration of the ``accept``. If the ``SafeDisconn``
   ## flag is specified then this error will not be raised and instead
diff --git a/lib/system/io.nim b/lib/system/io.nim
index 9a4502cba..f8358962f 100644
--- a/lib/system/io.nim
+++ b/lib/system/io.nim
@@ -259,6 +259,31 @@ else:
     IOFBF {.importc: "_IOFBF", nodecl.}: cint
     IONBF {.importc: "_IONBF", nodecl.}: cint
 
+const SupportIoctlInheritCtl = (defined(linux) or defined(bsd)) and
+                              not defined(nimscript)
+when SupportIoctlInheritCtl:
+  var
+    FIOCLEX {.importc, header: "<sys/ioctl.h>".}: cint
+    FIONCLEX {.importc, header: "<sys/ioctl.h>".}: cint
+
+  proc c_ioctl(fd: cint, request: cint): cint {.
+    importc: "ioctl", header: "<sys/ioctl.h>", varargs.}
+elif defined(posix) and not defined(nimscript):
+  var
+    F_GETFD {.importc, header: "<fcntl.h>".}: cint
+    F_SETFD {.importc, header: "<fcntl.h>".}: cint
+    FD_CLOEXEC {.importc, header: "<fcntl.h>".}: cint
+
+  proc c_fcntl(fd: cint, cmd: cint): cint {.
+    importc: "fcntl", header: "<fcntl.h>", varargs.}
+elif defined(windows):
+  const HANDLE_FLAG_INHERIT = culong 0x1
+  proc getOsfhandle(fd: cint): FileHandle {.
+    importc: "_get_osfhandle", header: "<io.h>".}
+
+  proc setHandleInformation(handle: FileHandle, mask, flags: culong): cint {.
+    importc: "SetHandleInformation", header: "<handleapi.h>".}
+
 const
   BufSize = 4000
 
@@ -292,12 +317,29 @@ proc getOsFileHandle*(f: File): FileHandle =
   ## returns the OS file handle of the file ``f``. This is only useful for
   ## platform specific programming.
   when defined(windows):
-    proc getOsfhandle(fd: cint): FileHandle {.
-      importc: "_get_osfhandle", header: "<io.h>".}
     result = getOsfhandle(getFileHandle(f))
   else:
     result = c_fileno(f)
 
+when defined(nimdoc) or (defined(posix) and not defined(nimscript)) or defined(windows):
+  proc setInheritable*(f: FileHandle, inheritable: bool): bool =
+    ## control whether a file handle can be inherited by child processes. Returns
+    ## ``true`` on success. This requires the OS file handle, which can be
+    ## retrieved via `getOsFileHandle <#getOsFileHandle,File>`_.
+    ##
+    ## This procedure is not guaranteed to be available for all platforms. Test for
+    ## availability with `declared() <system.html#declared,untyped>`.
+    when SupportIoctlInheritCtl:
+      result = c_ioctl(f, if inheritable: FIONCLEX else: FIOCLEX) != -1
+    elif defined(posix):
+      var flags = c_fcntl(f, F_GETFD)
+      if flags == -1:
+        return false
+      flags = if inheritable: flags and not FD_CLOEXEC else: flags or FD_CLOEXEC
+      result = c_fcntl(f, F_SETFD, flags) != -1
+    else:
+      result = setHandleInformation(f, HANDLE_FLAG_INHERIT, culong inheritable) != 0
+
 proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect],
               benign.} =
   ## reads a line of text from the file `f` into `line`. May throw an IO
@@ -501,7 +543,21 @@ else:
     importc: "freopen", nodecl.}
 
 const
-  FormatOpen: array[FileMode, string] = ["rb", "wb", "w+b", "r+b", "ab"]
+  NoInheritFlag =
+    # Platform specific flag for creating a File without inheritance.
+    when not defined(nimInheritHandles):
+      when defined(windows):
+        "N"
+      elif defined(linux) or defined(bsd):
+        "e"
+      else:
+        ""
+    else:
+      ""
+  FormatOpen: array[FileMode, string] = [
+    "rb" & NoInheritFlag, "wb" & NoInheritFlag, "w+b" & NoInheritFlag,
+    "r+b" & NoInheritFlag, "ab" & NoInheritFlag
+  ]
     #"rt", "wt", "w+t", "r+t", "at"
     # we always use binary here as for Nim the OS line ending
     # should not be translated.
@@ -544,17 +600,25 @@ proc open*(f: var File, filename: string,
   ##
   ## Default mode is readonly. Returns true iff the file could be opened.
   ## This throws no exception if the file could not be opened.
+  ##
+  ## The file handle associated with the resulting ``File`` is not inheritable.
   var p = fopen(filename, FormatOpen[mode])
   if p != nil:
+    var f2 = cast[File](p)
     when defined(posix) and not defined(nimscript):
       # How `fopen` handles opening a directory is not specified in ISO C and
       # POSIX. We do not want to handle directories as regular files that can
       # be opened.
-      var f2 = cast[File](p)
       var res: Stat
       if c_fstat(getFileHandle(f2), res) >= 0'i32 and modeIsDir(res.st_mode):
         close(f2)
         return false
+    when not defined(nimInheritHandles) and declared(setInheritable) and
+         NoInheritFlag.len == 0:
+      if not setInheritable(getOsFileHandle(f2), false):
+        close(f2)
+        return false
+
     result = true
     f = cast[File](p)
     if bufSize > 0 and bufSize <= high(cint).int:
@@ -569,13 +633,27 @@ proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {.
   ## file variables.
   ##
   ## Default mode is readonly. Returns true iff the file could be reopened.
-  result = freopen(filename, FormatOpen[mode], f) != nil
+  ##
+  ## The file handle associated with `f` won't be inheritable.
+  if freopen(filename, FormatOpen[mode], f) != nil:
+    when not defined(nimInheritHandles) and declared(setInheritable) and
+         NoInheritFlag.len == 0:
+      if not setInheritable(getOsFileHandle(f), false):
+        close(f)
+        return false
+    result = true
 
 proc open*(f: var File, filehandle: FileHandle,
            mode: FileMode = fmRead): bool {.tags: [], raises: [], benign.} =
   ## Creates a ``File`` from a `filehandle` with given `mode`.
   ##
   ## Default mode is readonly. Returns true iff the file could be opened.
+  ##
+  ## The passed file handle will no longer be inheritable.
+  when not defined(nimInheritHandles) and declared(setInheritable):
+    let oshandle = when defined(windows): getOsfhandle(filehandle) else: filehandle
+    if not setInheritable(oshandle, false):
+      return false
   f = c_fdopen(filehandle, FormatOpen[mode])
   result = f != nil
 
@@ -585,6 +663,8 @@ proc open*(filename: string,
   ##
   ## Default mode is readonly. Raises an ``IOError`` if the file
   ## could not be opened.
+  ##
+  ## The file handle associated with the resulting ``File`` is not inheritable.
   if not open(result, filename, mode, bufSize):
     sysFatal(IOError, "cannot open: " & filename)
 
diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim
index ee8c2343a..f749c508d 100644
--- a/lib/windows/winlean.nim
+++ b/lib/windows/winlean.nim
@@ -126,6 +126,8 @@ const
 
   CREATE_NO_WINDOW* = 0x08000000'i32
 
+  HANDLE_FLAG_INHERIT* = 0x00000001'i32
+
 proc getVersionExW*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
     stdcall, dynlib: "kernel32", importc: "GetVersionExW", sideEffect.}
 proc getVersionExA*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
@@ -708,6 +710,9 @@ proc duplicateHandle*(hSourceProcessHandle: Handle, hSourceHandle: Handle,
                       dwOptions: DWORD): WINBOOL{.stdcall, dynlib: "kernel32",
     importc: "DuplicateHandle".}
 
+proc getHandleInformation*(hObject: Handle, lpdwFlags: ptr DWORD): WINBOOL {.
+    stdcall, dynlib: "kernel32", importc: "GetHandleInformation".}
+
 proc setHandleInformation*(hObject: Handle, dwMask: DWORD,
                            dwFlags: DWORD): WINBOOL {.stdcall,
     dynlib: "kernel32", importc: "SetHandleInformation".}