Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unix: spawn should protect its input fds #226

Closed
wants to merge 1 commit into from
Closed

unix: spawn should protect its input fds #226

wants to merge 1 commit into from

Conversation

sam-github
Copy link
Contributor

spawn allows copying descriptor numbers during exec, but it wasn't
protecting them. Under some conditions, such as swapping stdout and
stderr, it would do this:

dup 2 -> 1
dup 1 -> 2

With end result that both 1 and 2 would be stderr, rather than being
swapped.

With this change, the above sequence gets rewritten to:

dup 1 -> 3
dup 2 -> 1
dup 3 -> 2

This fix can be verified against io.js. Using fd-dup.js:

var cp = require('child_process');
var fs = require('fs');

if (process.env.CHILD) {
  console.log('STDOUT');
  console.error('STDERR');
  return;
}

var options = {
  stdio: [0, 2, 1],
  env: { CHILD: true },
};
var c = cp.spawn(process.execPath, [__filename], options);

Without this patch both fd 1 and 2 in child end up in parent's stderr:

% ./iojs fd-dup.js 1>_1.txt 2>_2.txt; more _*.txt|cat
::::::::::::::
_1.txt
::::::::::::::
::::::::::::::
_2.txt
::::::::::::::
STDOUT
STDERR

(Above assumes an io.js that has libuv bug #224 fixed, otherwise it will output nothing, because fd 2 will be closed).

After this patch, they are swapped, as intended:

% ./iojs fd-dup.js 1>_1.txt 2>_2.txt; more _*.txt|cat
::::::::::::::
_1.txt
::::::::::::::
STDERR
::::::::::::::
_2.txt
::::::::::::::
STDOUT

/to @indutny, this fixes joyent/libuv#1084

spawn allows copying descriptor numbers during exec, but it wasn't
protecting them. Under some conditions, such as swapping stdout and
stderr, it would do this:

 dup 2 -> 1
 dup 1 -> 2

With end result that both 1 and 2 would be stderr, rather than being
swapped.

With this change, the above sequence gets rewritten to:

 dup 1 -> 3
 dup 2 -> 1
 dup 3 -> 2
@saghul
Copy link
Member

saghul commented Feb 25, 2015

I'm not sure it works in all Unices, but how about using this to get duplicated fds beyond stdio_count?

new_fd = fcntl(use_fd, F_DUPFD, stdio_count);

It's simpler, assuming it works in all platforms we need it.

@saghul
Copy link
Member

saghul commented Feb 25, 2015

Also, a test! :-)

@sam-github
Copy link
Contributor Author

F_DUPFD fcntl: I wasn't aware of that fcntl. It looks perfect. I see it on Linux and OS X's man pages, but no idea how wide-spread it is. I could do an #ifdef F_DUPFD... but then I would be maintaining two copies of the same code.

I considered changing the dup2() to be something like:

int dupAbove(int fd, int above) {
  int copy;
  while((copy = dup(fd)) < above && copy != -1)
    ;
  return copy;
}

Basically, repeatedly calling dup() until the result is > use_fd. That loop would normally never run more than once, so have zero-overhead in the normal case. It might be the best "portable" alternative to F_DUPFD.

What to do, rewrite to the "portable" F_DUPFD, or just use F_DUPFD, and see if builds fail anywhere?

As for tests, I'm sorry, but I can find no tests for spawn-with-stdio at all, anywhere in uv, that I can use as a base to add this case to. Did I fail to find the right code?

Writing spawn() tests from scratch seems like it would take days (that I won't have in the next weeks).

Can you point me to a test I can use as a base? Or do you have any ideas how to write the test?

@saghul
Copy link
Member

saghul commented Mar 4, 2015

AFAIS, F_DUPFD is part of POSIX (http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html) so we should be fine going that road. If any of the supported platforms doesn't have it (which I doubt) we can reconsider.

As for tests, spawn_stdout uses a spawn_helper2 process which just prints something. You could use it as a base and make it print to both stdout and stderr, which are redirected to 2 files, then synchronously read and compare the content.

@sam-github
Copy link
Contributor Author

OK, @saghul I'll rewrite to use F_DUPFD, and take a shot at a test.

@saghul saghul added this to the 1.5.0 milestone Mar 26, 2015
@saghul
Copy link
Member

saghul commented Mar 26, 2015

@sam-github will you have time to work on this? If possible it would be nice to include it on the next release. The test can probably be written similar to #224.

I can try to finish it if you're short on time.

saghul added a commit to saghul/libuv that referenced this pull request Apr 7, 2015
saghul added a commit to saghul/libuv that referenced this pull request Apr 10, 2015
Alternative implementation (and test) to
libuv#226

Fixes: joyent/libuv#1084
PR-URL: libuv#309
Reviewed-By: Ben Noordhuis <[email protected]>
@saghul
Copy link
Member

saghul commented Apr 10, 2015

#309 landed, closing!

@saghul saghul closed this Apr 10, 2015
@sam-github sam-github deleted the generalize-fd-mapping branch November 23, 2016 15:07
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.

uv_spawn(): do parallel move
2 participants