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

src: remap invalid file descriptors using dup2 #44461

Merged
merged 1 commit into from
Oct 26, 2022

Conversation

obiwac
Copy link
Contributor

@obiwac obiwac commented Aug 30, 2022

When checking for the validity of the stdio file descriptors in PlatformInit(), this was the code previously used (as in #875):

// Make sure file descriptors 0-2 are valid before we start logging anything.
for (int fd = STDIN_FILENO; fd <= STDERR_FILENO; fd += 1) {
  struct stat ignored;
  if (fstat(fd, &ignored) == 0)
    continue;
  // Anything but EBADF means something is seriously wrong.  We don't
  // have to special-case EINTR, fstat() is not interruptible.
  if (errno != EBADF)
    abort();
  if (fd != open("/dev/null", O_RDWR))
    abort();
}

As I understand it, the intention behind this is to map file descriptors to /dev/null if they don't exist, by hoping the next file descriptor to be given will be the one which doesn't exist. This is imperfect as it is very platform dependent and breaks when /dev/null has already been given a different file descriptor in the same process, but e.g. stdin has disappeared in the meantime (as is the case with this issue on FreeBSD with the daemon tool when not passing -f: nodejs/help#2411).

Instead, this patch uses the dup2(2) syscall to properly remap the missing stdio file descriptor to what /dev/null's file descriptor actually is.

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/startup

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Aug 30, 2022
@obiwac obiwac force-pushed the main branch 6 times, most recently from 69293a1 to b3c392e Compare August 31, 2022 11:02
src/node.cc Outdated Show resolved Hide resolved
@addaleax
Copy link
Member

addaleax commented Aug 31, 2022

by hoping the next file descriptor to be given will be the one which doesn't exist

“hoping” is doing a lot of work here 🙂 This was actually spec’d behavior in previous POSIX versions, and I was quite surprised to learn (through this PR) that the spec actually changed in this regard.

I’d leave a comment in the source code about this, since others might be surprised by this as well.

@obiwac
Copy link
Contributor Author

obiwac commented Aug 31, 2022

I’d leave a comment in the source code about this, since others might be surprised by this as well.

Got you. I'll do that in a little bit ✌️

@theanarkh theanarkh added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 31, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 31, 2022
@nodejs-github-bot
Copy link
Collaborator

Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

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

TBH, this change adds more failure paths and I don't think it's actually necessary.

The "open() returns the lowest available file descriptor" convention is so deeply codified in the UNIX ecosystem that it's never going away, no matter what POSIX says. There's simply too much software built around that assumption.

src/node.cc Outdated Show resolved Hide resolved
src/node.cc Outdated Show resolved Hide resolved
@obiwac
Copy link
Contributor Author

obiwac commented Sep 1, 2022

The "open() returns the lowest available file descriptor" convention is so deeply codified in the UNIX ecosystem that it's never going away, no matter what POSIX says.

While I do agree with this, there still are specific cases where "available" means something different to what's expected. Case in point:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
	system("echo && fstat | grep a.out");
	int fd = open("/dev/null", O_RDWR);
	printf("\n/dev/null: %d\n", fd);

	return 0;
}

Running this on FreeBSD in a separate session with daemon -o log.log -r /tmp/a.out and then closing said session (thus making stdin invalid):

root     a.out      89214 text [restricted]    519 -rwxr-xr-x   15424  r
root     a.out      89214   wd /        107129 drwxr-xr-x      16  r
root     a.out      89214 root /        107106 drwxr-xr-x      24  r
root     a.out      89214 jail /        107106 drwxr-xr-x      24  r
root     a.out      89214    0 -         -         bad    -
root     a.out      89214    1* pipe fffff8002e0e7d00 <-> fffff8002e0e7ba0      0 rw
root     a.out      89214    2* pipe fffff8002e0e7d00 <-> fffff8002e0e7ba0      0 rw
root     a.out      89214    3 [restricted]     24 crw-rw-rw-    null rw

/dev/null: 3

(This is actually the issue nodejs/help#2411 had.)

Here, 0 should be available, yet /dev/null is still opened as 3. Something else that works is closing 0 before opening /dev/null, but IMHO at that point it's kinda relying on flaky behaviour.

@obiwac obiwac force-pushed the main branch 2 times, most recently from da101fe to 326a490 Compare September 1, 2022 11:21
When checking for the validity of the stdio file descriptors
(nodejs#875), ones which don't exist are intended to be remapped to
/dev/null (and, if that doesn't work, we abort).

This however doesn't work on all platforms and in all cases, and is not
anymore required by POSIX; instead, use the `dup2` syscall as a more
robust solution (conforms to POSIX.1).

Fixes: nodejs/help#2411
Refs: nodejs#875
@bnoordhuis
Copy link
Member

Ah okay, fair enough. Fd 0 points to a kind of zombie file description in your example, i.e., it's technically still open but not actually usable?

@obiwac
Copy link
Contributor Author

obiwac commented Sep 4, 2022

Fd 0 points to a kind of zombie file description in your example, i.e., it's technically still open but not actually usable?

AFAIU the file descriptor is never actually closed, so it isn't ever marked as unused (fdunused()), which is why it's never removed from the file descriptor table.

@bnoordhuis
Copy link
Member

Wait, but fstat() still fails with EBADF? How does that work?

@obiwac
Copy link
Contributor Author

obiwac commented Sep 6, 2022

I don't know, there are plenty of places along the line where fstat(2) can fail with EBADF, not just because a file descriptor is not found in the file descriptor table (you can check out kern_fstat(), my quick guess is that fp->f_ops is set to &badfileops at some point before being unused).

I could investigate further if you'd like an exact answer but I don't see how it's too relevant outside of the FreeBSD kernel :P

@bnoordhuis
Copy link
Member

"Zombie file descriptor" is good enough for me. :-)

@bnoordhuis bnoordhuis added request-ci Add this label to start a Jenkins CI on a PR. and removed needs-ci PRs that need a full CI run. labels Sep 8, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Sep 8, 2022
@nodejs-github-bot
Copy link
Collaborator

@obiwac
Copy link
Contributor Author

obiwac commented Oct 25, 2022

Is there anything left for me to do here?

@addaleax addaleax added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. labels Oct 25, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 25, 2022
@nodejs-github-bot
Copy link
Collaborator

@addaleax addaleax added the commit-queue Add this label to land a pull request using GitHub Actions. label Oct 26, 2022
@nodejs-github-bot nodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Oct 26, 2022
@nodejs-github-bot nodejs-github-bot merged commit 0c8b141 into nodejs:main Oct 26, 2022
@nodejs-github-bot
Copy link
Collaborator

Landed in 0c8b141

RafaelGSS pushed a commit that referenced this pull request Nov 1, 2022
When checking for the validity of the stdio file descriptors
(#875), ones which don't exist are intended to be remapped to
/dev/null (and, if that doesn't work, we abort).

This however doesn't work on all platforms and in all cases, and is not
anymore required by POSIX; instead, use the `dup2` syscall as a more
robust solution (conforms to POSIX.1).

Fixes: nodejs/help#2411
Refs: #875
PR-URL: #44461
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
@RafaelGSS RafaelGSS mentioned this pull request Nov 1, 2022
RafaelGSS pushed a commit that referenced this pull request Nov 10, 2022
When checking for the validity of the stdio file descriptors
(#875), ones which don't exist are intended to be remapped to
/dev/null (and, if that doesn't work, we abort).

This however doesn't work on all platforms and in all cases, and is not
anymore required by POSIX; instead, use the `dup2` syscall as a more
robust solution (conforms to POSIX.1).

Fixes: nodejs/help#2411
Refs: #875
PR-URL: #44461
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
danielleadams pushed a commit that referenced this pull request Dec 30, 2022
When checking for the validity of the stdio file descriptors
(#875), ones which don't exist are intended to be remapped to
/dev/null (and, if that doesn't work, we abort).

This however doesn't work on all platforms and in all cases, and is not
anymore required by POSIX; instead, use the `dup2` syscall as a more
robust solution (conforms to POSIX.1).

Fixes: nodejs/help#2411
Refs: #875
PR-URL: #44461
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
danielleadams pushed a commit that referenced this pull request Dec 30, 2022
When checking for the validity of the stdio file descriptors
(#875), ones which don't exist are intended to be remapped to
/dev/null (and, if that doesn't work, we abort).

This however doesn't work on all platforms and in all cases, and is not
anymore required by POSIX; instead, use the `dup2` syscall as a more
robust solution (conforms to POSIX.1).

Fixes: nodejs/help#2411
Refs: #875
PR-URL: #44461
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
danielleadams pushed a commit that referenced this pull request Jan 3, 2023
When checking for the validity of the stdio file descriptors
(#875), ones which don't exist are intended to be remapped to
/dev/null (and, if that doesn't work, we abort).

This however doesn't work on all platforms and in all cases, and is not
anymore required by POSIX; instead, use the `dup2` syscall as a more
robust solution (conforms to POSIX.1).

Fixes: nodejs/help#2411
Refs: #875
PR-URL: #44461
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. c++ Issues and PRs that require attention from people who are familiar with C++.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants