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
This commit is contained in:
parent
6bd279c978
commit
1bdc30bdb1
17 changed files with 317 additions and 44 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue