Skip to content

Unix library: better API for "close-on-exec" over file descriptors#650

Merged
damiendoligez merged 16 commits intotrunkfrom
cloexec
Feb 13, 2017
Merged

Unix library: better API for "close-on-exec" over file descriptors#650
damiendoligez merged 16 commits intotrunkfrom
cloexec

Conversation

@xavierleroy
Copy link
Copy Markdown
Contributor

This is one possible answer to the long-standing problem described in MPR#5256.

Background

An historical accident of the Unix API is that, by default, all file descriptors remain open when a program is started via exec: the new program inherits those file descriptors. This is often a security issue, making it possible for the new program to access files and sockets it could not have otherwise, just because the original program forgot to close the corresponding file descriptors before exec.

To mitigate this problem, a file descriptor can be marked "close on exec" (e.g. with Unix.set_close_on_exec). Then, the kernel will automatically close it during the exec call, and it will not leak to the new program.

However, in multithreaded programs, a race condition exists: if a concurrently-executing thread does fork and exec after the file descriptor is created but before set_close_on_exec is applied to it, the descriptor still leaks. To address this issue, the open system call has a O_CLOEXEC flag that atomically sets "close on exec" on the file descriptor as it is created. Linux adds variants of other system calls (pipe2, dup3, accept4) that do the same for other cases of file descriptor creation. So far these syscalls are Linux-specific, but there is hope that similar functionality will be standardized one day (see http://www.austingroupbugs.net/view.php?id=411).

Proposed solution

Several approaches to the problem above were discussed in MPR#5256. This pull request implements a solution that still relies on programmer's discipline but should not introduce major incompatibilities:

  • All Unix functions that create file descriptors now have a way to specify if the descriptor is to be close-on-exec or keep-on-exec:
    • either via an optional argument ~cloexec:bool
    • or, in the case of Unix.openfile, via flags O_CLOEXEC and O_KEEPEXEC.
  • For backward compatibility, keep-on-exec is the default, but the implementation makes it trivially easy to switch to close-on-exec by default in the future.
  • The implementation of the functions honors the close-on-exec specification, using the system calls that set it atomically as the file descriptor is created, if available, and falling back on a non-atomic implementation otherwise.

Plans for the future

At some future point, it would make a lot of sense to switch the default behavior to "close-on-exec" instead of "keep-on-exec", as it is more secure. Other languages have already made that switch, e.g. Python and Ruby. If we do this now in OCaml it would break too much code. However, with the new API proposed in this pull request, programmers can start religiously annotating their Unix system calls with the appropriate ~cloexec argument. If they do this consistently on all the code that relies on file descriptor inheritance across exec, we will then be able to switch the default to "close-on-exec" and their code will keep working.

Current status

The implementation is at the prototype stage and needs more testing. (The Windows implementation wasn't even compiled, let alone tested.) I'm putting it as a pull request early so as to have a discussion on the API and on whether it's a good solution to the problem.

@toots
Copy link
Copy Markdown
Contributor

toots commented Jun 30, 2016

Very happy to see this being worked on again. The proposed solution and PR look fine to me! I was wondering: would it be interesting to add a global switch or maybe a module functor that would allow the programmer to have a Unix module where all calls default to cloexec:true?

@xavierleroy
Copy link
Copy Markdown
Contributor Author

@toots: yes, it makes a lot of sense to have programmer's control on the default value. (For example, the SecureOCaml people should probably have "close on exec" as the default.) We did something like that for hashtable randomization in OCaml 4.00. Maybe a similar API could be used here.

int ret;
#ifdef F_DUPFD_CLOEXEC
if (unix_cloexec_p(cloexec))
ret = fcntl(F_DUPFD_CLOEXEC, Int_val(fd));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the arguments to this fcntl call are the wrong way round.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted, thanks! Pushing a fix...

@mshinwell
Copy link
Copy Markdown
Contributor

I am in favour. I spotted one bug, but can have a closer look once it's been tested.

@mrvn
Copy link
Copy Markdown
Contributor

mrvn commented Jul 4, 2016

I saw a recent proposal to reorganize the stdlib into a new "namespace" Std, including putting the channel functions into Std.IO. Wouldn't that also be a good way to make close_on_exec the default? As in all the functions in Std.IO will default to close_on_exec while the old functions use keep_on_exec.

@xavierleroy
Copy link
Copy Markdown
Contributor Author

I fixed the Win32 implementation and added some tests. This PR is ready for review.

Concerning the API, the implementation now makes it trivial to change the default for close-on-exec. I haven't added an API function to do this yet.

let fd3 = dup ~cloexec:false fd3 in
let res = win_create_process prog args optenv fd1 fd2 fd3 in
close fd1; close fd2; close fd3;
res
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't a race condition can make the new fd1,fd2,fd3 go into another sub-process, since contrary to the unix version we are not inside a fork?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If dup, win_create_process or close releases the runtime lock or invokes the GC then another thread could run before the final close. If that thread then also calls create_process_aux the duplicated FDs will leak.

I'm assuming dup and close will be save here but win_create_process probably allocates something. So I agree with @bobot about the race condition. I think the dup should be moved into the win_create_process C stub and run with the runtime lock held. This would still race with non-ocaml threads forking at the same time but it's the best you can do.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming dup

I don't think so. Under Windows, handles are wrapped into head-allocated custom blocks, so dup can easily trigger the GC.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xavierleroy What about fd leaks when win_create_process (or dup) raises an exception?

@xavierleroy
Copy link
Copy Markdown
Contributor Author

@alainfrisch : Here is an explanation of the change in create_process for Win32. I think it makes it work in cases where the previous implementation would misbehave, and I don't think it can break. Assume you call Unix.create_process with redirections to three new file descriptors fd1, fd2, fd3. Further assume that fd1, fd2, fd3 are close-on-exec, like they should be so that they won't be inherited by the new process.

  • With the Unix implementation, fd1, fd2, fd3 are dup2-ed to the standard descriptors 0, 1, 2, the latter being (re-)set to keep-on-exec, so everything is fine.
  • With the old Win32 implementation, the handles fd1, fd2, fd3 are passed "as is" to the new process, which uses them as stdin, stdout and stderr. Since the handles are close-on-exec, they are actually closed (invalid) in the new process! So the new process misbehaves.
  • With the new Win32 implementation, fd1, fd2, fd3 are duplicated to inheritable handles (keep-on-exec), and the new process works as expected.

The reason why I don't think this change can break code that works today is that, in order for that code to work in Win32, the fds passed to create_process must be keep-on-exec. In this case, the new implementation behaves pretty much like the old one.

@bobot
Copy link
Copy Markdown
Contributor

bobot commented Jul 5, 2016

I propose to add to the creating process functions a way to specify which fd must be passed to the created process. Perhaps with the destination file descriptor, in the same way that perform_redirections does for standard file-descriptors : (file_descr * int) list.

However it seems that there is never enough provided after fork actions (resource limitation, user change, namespace) so perhaps it should go in external library (even Core/Async_extended create_process functions were not enough in my case)

@xavierleroy
Copy link
Copy Markdown
Contributor Author

@bobot : yes, we (still) have a race condition. At this point I don't see how to avoid it based on the CreateProcess Win32 API, but I'll keep searching on StackOverflow...

@xavierleroy
Copy link
Copy Markdown
Contributor Author

More info on CreateProcess, redirections and handle inheritance: https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873. It looks like there is a drastic solution where no handle is ever inherited, except those that become the new standard handles of the created process. This is very secure indeed but would cause problems with hypothetical applications that pass extra inherited handles through side channels (handle number as command-line parameter, for example).

@xavierleroy
Copy link
Copy Markdown
Contributor Author

@mrvn @alainfrisch : yes, the current Win32 implementation is a proof-of-concept and I'll improve it along the lines you suggest. But a race condition will remain with concurrent CreateProcess calls made directly from C. Is this good enough or should we look into the more drastic solution mentioned in my previous comment?

@alainfrisch
Copy link
Copy Markdown
Contributor

I'm not sure that the risk of breaking existing code with the more drastic solution is so high, esp. since our Unix module does not expose handle directly. To be on the safe side, this could always be controlled by a global switch, although this becomes more and more ugly. Ideally, there would be a way to emulate the strict mode on Unix and add an optional parameter to process-creation function to control it. But my understanding is that this is precisely what is difficult to achieve on Unix, right?

@mrvn
Copy link
Copy Markdown
Contributor

mrvn commented Jul 6, 2016

I don't think this can ever be fully race free unless there is kernel support for actually atomically passing a specific set of FDs to the new process. Oh wait, there is since Vista.

@xavierleroy I would totaly go with the solution from the page you mentioned with an insecure fallback if the syscall isn't present. It was made for exactly this case after all.

@xavierleroy
Copy link
Copy Markdown
Contributor Author

I know it's my time to move. Will try to improve createprocess, at least by moving the dup dance into the C code, so that it is more atomic and errors are handled better, and will move that we merge then.

@mshinwell
Copy link
Copy Markdown
Contributor

The new Jane Street "shexp" package has a carefully-written implementation for spawning Unix processes that may (or may not) be of use for reference too. I think there may be a newer version available than the current version on Jane Street's github (please ask @diml if this is of interest).

This is one possible answer to the long-standing problem described in MPR#5256:
http://caml.inria.fr/mantis/view.php?id=5256

All Unix functions that create file descriptors now have a way to specify if the descriptor is to be close-on-exec or keep-on-exec:
- either via an optional argument ~cloexec:bool
- or, in the case of Unix.openfile, via flags O_CLOEXEC and O_KEEPEXEC.

For backward compatibility, keep-on-exec is the default, but the implementation makes it trivially easy to switch to close-on-exec by default in the future.

To avoid race conditions (that can occur when a descriptor is created in keep-on-exec mode then switched to close-on-exec), the implementation uses the new system calls pipe2(), dup3() and accept4() when they are available.  So far these syscalls are Linux-specific, but there is hope that similar functionality will be standardized one day (see http://www.austingroupbugs.net/view.php?id=411).

The win32unix implementation hasn't been updated yet.
Tentative implementation: not tested, not even compiled.
Also: refactor the code between the cloexec/keepexec cases.
This is a first step towards making the default controllable by the user.

Also: in Unix.openfile, make sure that if O_CLOEXEC and O_KEEPEXEC are given, O_CLOEXEC wins.
win32unix/open.c: wrong interpretation of default close-on-exec
win32unix/unix.ml: duplicate handles in create_process, for consistency with the unix implementation
tests/lib-unix: problems with deleting a file while it is open
The "dup" operations are done in C for better atomicity.
FDs are properly closed in case of error.
Use ~cloexec:true where appropriate.
Cherry-pick bb96c0b from unix/unix.ml (Fix #5421: do not leak fds in various open_proc* functions).
close out_read; close in_write; close err_write;
begin
try
open_proc cmd (Some env) (Process_full(inchan, outchan, errchan))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be (Some(make_process_env env))

Note: the test lib-unix/redirections.ml sometimes fails (mingw32 port).  To be investigated.
Closing the standard input causes errors in open_process_out
because the Win32 CreateProcess syscall demands that all standard handles
are valid.

Strangely, the error is semi-random and occurs about once every 3 runs.
MisterDA added a commit to MisterDA/lwt that referenced this pull request Nov 9, 2021
In the Lwt_unix module, add `?cloexec:bool` optional arguments to
functions that create file descriptors (`dup`, `dup2`, `pipe`,
`pipe_in`, `pipe_out`, `socket`, `socketpair`, `accept`, `accept_n`).
The `?cloexec` argument is simply forwarded to the wrapped Unix
function (with OCaml >= 4.05, see [ocaml/ocaml#650][650]), or emulated
as best-effort with `Unix.set_close_on_exec` on older OCaml versions.

Fix ocsigen#327. Fix ocsigen#847. See also ocsigen#872.

[650]: ocaml/ocaml#650
smorimoto pushed a commit to raphael-proust/lwt that referenced this pull request Dec 2, 2021
In the Lwt_unix module, add `?cloexec:bool` optional arguments to
functions that create file descriptors (`dup`, `dup2`, `pipe`,
`pipe_in`, `pipe_out`, `socket`, `socketpair`, `accept`, `accept_n`).
The `?cloexec` argument is simply forwarded to the wrapped Unix
function (with OCaml >= 4.05, see [ocaml/ocaml#650][650]), or emulated
as best-effort with `Unix.set_close_on_exec` on older OCaml versions.

Fix ocsigen#327. Fix ocsigen#847. See also ocsigen#872.

[650]: ocaml/ocaml#650
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Dec 2, 2021
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and leak the original handle in the
child process.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/lwt that referenced this pull request Dec 6, 2021
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Dec 17, 2021
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
EduardoRFS pushed a commit to esy-ocaml/ocaml that referenced this pull request Dec 17, 2021
…s_for_effect_functions

Add primitives necessary for exposing effect handlers as functions
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Jan 11, 2022
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/lwt that referenced this pull request Feb 1, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Feb 4, 2022
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/lwt that referenced this pull request Feb 8, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Feb 15, 2022
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

Consider the following pipeline: `writer | child | reader`.
The problem with the child process having duplicated "leaked" stdin
and stdout handles is that while it is running, it cannot signal to
the writer that it should stop writing: the writer will not see a
broken pipe as the child still has the leaked duplicated handle
open. The same thing happens to the reader with the stdout of the
child. Usually stdin/stdout are bound to the lifetime of the process,
so this is not too much of a problem, but if the child itself spawns
processes that should outlive it, the whole thing can become messy.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Feb 15, 2022
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

Consider the following pipeline: `writer | child | reader`.
The problem with the child process having duplicated "leaked" stdin
and stdout handles is that while it is running, it cannot signal to
the writer that it should stop writing: the writer will not see a
broken pipe as the child still has the leaked duplicated handle
open. The same thing happens to the reader with the stdout of the
child. Usually stdin/stdout are bound to the lifetime of the process,
so this is not too much of a problem, but if the child itself spawns
processes that should outlive it, the whole thing can become messy.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/lwt that referenced this pull request Feb 25, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
MisterDA added a commit to MisterDA/lwt that referenced this pull request Feb 28, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
MisterDA added a commit to MisterDA/lwt that referenced this pull request Feb 28, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Feb 28, 2022
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

Consider the following pipeline: `writer | child | reader`.
The problem with the child process having duplicated "leaked" stdin
and stdout handles is that while it is running, it cannot signal to
the writer that it should stop writing: the writer will not see a
broken pipe as the child still has the leaked duplicated handle
open. The same thing happens to the reader with the stdout of the
child. Usually stdin/stdout are bound to the lifetime of the process,
so this is not too much of a problem, but if the child itself spawns
processes that should outlive it, the whole thing can become messy.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Feb 28, 2022
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

Consider the following pipeline: `writer | child | reader`.
The problem with the child process having duplicated "leaked" stdin
and stdout handles is that while it is running, it cannot signal to
the writer that it should stop writing: the writer will not see a
broken pipe as the child still has the leaked duplicated handle
open. The same thing happens to the reader with the stdout of the
child. Usually stdin/stdout are bound to the lifetime of the process,
so this is not too much of a problem, but if the child itself spawns
processes that should outlive it, the whole thing can become messy.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/lwt that referenced this pull request Feb 28, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
MisterDA added a commit to MisterDA/ocaml that referenced this pull request Mar 17, 2022
The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in ocaml#650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

Consider the following pipeline: `writer | child | reader`.
The problem with the child process having duplicated "leaked" stdin
and stdout handles is that while it is running, it cannot signal to
the writer that it should stop writing: the writer will not see a
broken pipe as the child still has the leaked duplicated handle
open. The same thing happens to the reader with the stdout of the
child. Usually stdin/stdout are bound to the lifetime of the process,
so this is not too much of a problem, but if the child itself spawns
processes that should outlive it, the whole thing can become messy.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
nojb pushed a commit that referenced this pull request Mar 18, 2022
… iff they're not inheritable (#10807)

* win32unix: test if create_process leaks std handles
* win32unix: duplicate handle in create_process iff not inheritable

The current code makes the assumption that handles are not inheritable
(close-on-exec), and duplicates handles to mark them inheritable
(keep-on-exec) for the child process. That assumption is only
mentioned in #650, and not documented in the manual.

It's possible however that the calling code gives to create_process
handles that are already inheritable: either because the programmer
isn't aware of the security implications, or because the programmer
expects create_process to be a "simple" wrapper around WinAPI's
CreateProcess; in which case setting the handle inheritable would be
the responsibility of the calling code.

Converting the canonical example from Microsoft documentation [1]
without looking at win32unix's create_process would lead to this
problem.

In either case, the current create_process would needlessly duplicate
an already inheritable handle and the child process would inherit both
the original and the duplicated handles.

Consider the following pipeline: `writer | child | reader`.
The problem with the child process having duplicated "leaked" stdin
and stdout handles is that while it is running, it cannot signal to
the writer that it should stop writing: the writer will not see a
broken pipe as the child still has the leaked duplicated handle
open. The same thing happens to the reader with the stdout of the
child. Usually stdin/stdout are bound to the lifetime of the process,
so this is not too much of a problem, but if the child itself spawns
processes that should outlive it, the whole thing can become messy.

We can instead check whether the given handles are inheritable, and
only duplicate them if they're not. The calling code is still
responsible for closing the original handle.

[1]: https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
MisterDA added a commit to MisterDA/lwt that referenced this pull request Mar 18, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
smorimoto pushed a commit to MisterDA/lwt that referenced this pull request Apr 3, 2022
Unix.create_process duplicates the handles given to the child process
if they're not inheritable. Mimic this behaviour in Lwt for
consistency.

See the following PRs:
- [Unix library: better API for "close-on-exec" over file descriptors ocsigen#650](ocaml/ocaml#650);
- [win32unix create_process: duplicate std handles in create_process iff they're not inheritable #10807](ocaml/ocaml#650).
stedolan pushed a commit to stedolan/ocaml that referenced this pull request Sep 21, 2022
The `git clean -dfx` command suggested in `HACKING.md` deletes any file that's
untracked but not ignored, which is unnecessary and possibly a nasty surprise.
`git clean -dfX` instead *only* deletes ignored files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants