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

Dircache should handle fd == 0 as a valid file descriptor #320

Closed
slackner opened this issue Jan 5, 2019 · 25 comments
Closed

Dircache should handle fd == 0 as a valid file descriptor #320

slackner opened this issue Jan 5, 2019 · 25 comments

Comments

@slackner
Copy link
Contributor

slackner commented Jan 5, 2019

While rather unlikely in practice, a file descriptor of 0 is completely valid and should not be treated as an error [1]. Both open and Dup typically return the smallest available file descriptor and only negative values are errors. If the user starts gocryptfs with stdin (fd == 0) closed and triggers the dircache code fast enough (not sure if that is actually possible), this could lead to a panic / unexpected behavior (e.g., in the fd validation in Store).

[1] https://unix.stackexchange.com/questions/100611/aix-open-file-descriptor-is-zero

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Well, check this out: https://play.golang.org/p/gG0FOnelmzR

Compile and run it with fds 0,1,2 closed:

 ./printloop 0<&- 1>&- 2>&-

Now let's look at `strace -p $(pgrep printloop):

write(2, "2019/01/05 13:06:56 log\n", 31) = -1 EBADF (Bad file descriptor)
write(1, "fmt\n", 10)              = -1 EBADF (Bad file descriptor)

This means fmt.Printf and log.Printfs blindly write into fds 1 and 2. So if the user closes all fds, they will have a bigger problem than the panic, because log output will end up in random files that happen to get opened at fd 1 or 2.

Note that this can only happen with -nosyslog, as otherwise daemonize.go redirects 0, 1 and 2 to valid fds.

If the user only closes fd 0 and uses -nosyslog, I guess what you describe could really happen. On the other hand, assuming that the default value "0" may be a valid fd is a PITA and an opportunity to introduce bugs by forgetting to initialize to -1. Not sure what to do.

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Hmm. I guess we should check at least fds 1,2 on startup, and exit with an error if they are closed, or open /dev/null. And while we are at it, we can do the same thing for 0.

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

What about adding code early in the startup path to make sure those fds are registered? I am also using something like this in my security critical projects (and Wine uses it aswell):


    if ((null_fd = open( "/dev/null", O_RDWR )) < 0)
    {
        perror( "open error" );
        return 1;
    }

    while (null_fd >= 0 && null_fd <= 3)
        null_fd = dup( null_fd );

    close(null_fd);

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

For reference, here is what Wine does:
https://source.winehq.org/git/wine.git/blob/HEAD:/server/request.c#l824

The code relies on the fact that open and dup always allocate the smallest available fd on most systems (for Linux, it is definitely the case). If we need /dev/null later anyway, we can of course also just leave the fd open.

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

Note that in your case, checking null_fd >= 0 && null_fd <= 2 would probably be sufficient. I used limit 3 in my code above since in this particular project fd 3 also has a special meaning.

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Good idea.

rfjakob added a commit that referenced this issue Jan 5, 2019
The Go stdlib, as well as the gocryptfs code, relies on the fact
that fds 0,1,2 are always open.

See #320 for details.
@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Pushed as ad15ad9 .
Seems to work OK, the Go stdlib opens that eventpoll thing concurrently, but I think that's harmless as writes into the eventpoll fd will do nothing. What do you think?

$ ls -l /proc/$(pgrep gocryptfs)/fd
total 0
l-wx------. 1 jakob jakob 64 Jan  5 14:14 0 -> /dev/null
lrwx------. 1 jakob jakob 64 Jan  5 14:14 1 -> 'anon_inode:[eventpoll]'
lrwx------. 1 jakob jakob 64 Jan  5 14:14 2 -> /dev/null

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Hmm, unless the Stdlib decided to close that fd at some time. That would be a problem.

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

Would it make a difference when we move it to the init() function (instead of main())?

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Good idea, but looks the same. Also, I am opening /tmp/ensureStdFds instead of /dev/null, and, interestingly, somebody else seems to open /dev/null first:

 ls -l /proc/$(pgrep gocryptfs)/fd
total 0
l-wx------. 1 jakob jakob 64 Jan  5 14:39 0 -> /dev/null
lrwx------. 1 jakob jakob 64 Jan  5 14:39 1 -> 'anon_inode:[eventpoll]'
lrwx------. 1 jakob jakob 64 Jan  5 14:39 2 -> /tmp/ensureStdFds

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

can we somehow inject our own init function (e.g., as a separate package) and ensure that it is executed before anything else?

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

Do you know if there is actually any way to influence the init() order? I tried with:

http://ix.io/1xz9

but it doesn't seem to make a difference.

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

Unverfied, but https://medium.com/@markbates/go-init-order-dafa89fcef22 suggests

As you can see the init functions are loaded in order by the filename.

Does this mean we have no influence since rfjakob > hanwen?

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

http://ix.io/1xz9

Also tried that, did not make a difference, hanwen runs first (yes, maybe alphabetic)

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Another problem: when we damonize, we overwrite fds 0,1,2. This means Go loses the eventpoll fd.

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

./gocryptfs -passfile test.txt a b 0<&- 1>&- 2>&-

Aaand it's gone:

$ ls -l /proc/$(pgrep gocryptfs)/fd
total 0
lr-x------. 1 jakob jakob 64 Jan  5 15:27 0 -> /dev/null
l-wx------. 1 jakob jakob 64 Jan  5 15:27 1 -> 'pipe:[108343707]'
lrwx------. 1 jakob jakob 64 Jan  5 15:27 10 -> 'socket:[108343706]'
l-wx------. 1 jakob jakob 64 Jan  5 15:27 2 -> 'pipe:[108343707]'
l-wx------. 1 jakob jakob 64 Jan  5 15:27 3 -> /dev/null
lrwx------. 1 jakob jakob 64 Jan  5 15:27 4 -> 'anon_inode:[eventpoll]'
lrwx------. 1 jakob jakob 64 Jan  5 15:27 5 -> 'socket:[108343702]'
lrwx------. 1 jakob jakob 64 Jan  5 15:27 6 -> 'socket:[108343703]'
lrwx------. 1 jakob jakob 64 Jan  5 15:27 7 -> /dev/fuse
lrwx------. 1 jakob jakob 64 Jan  5 15:27 8 -> 'socket:[108343704]'
lrwx------. 1 jakob jakob 64 Jan  5 15:27 9 -> 'socket:[108343705]'

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Oh, correction, it's all good (4 -> 'anon_inode:[eventpoll]').
The re-exec on daemonization fixes the problem.

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

It still feels a bit error-prone, and its a bit surprising that Go doesn't offer any clean way to handle init priorities or something like that. The following patch seems to work, but I have no idea why?!

http://ix.io/1xzg

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

strace output:

(ensureStdFds logic)
openat(AT_FDCWD, "/dev/null", O_RDWR)   = 0
dup(0)                                  = 3
close(3)                                = 0
[...]
(other libs and epoll stuff)
openat(AT_FDCWD, "/proc/sys/net/core/somaxconn", O_RDONLY|O_CLOEXEC) = 3
epoll_create1(EPOLL_CLOEXEC)            = 4
[...]
(/dev/null opened by go-fuse)
openat(AT_FDCWD, "/dev/null", O_WRONLY) = 3

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

My interpretation would be, that cli_args.go is processed first (due to the filename ordering). The imports are then executed in the order they are listed. But this is just a wild guess.

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Well, awesome!

$ ls -l /proc/$(pgrep gocryptfs)/fd
total 0
lrwx------. 1 jakob jakob 64 Jan  5 15:47 0 -> /dev/null
lrwx------. 1 jakob jakob 64 Jan  5 15:47 1 -> /dev/null
lrwx------. 1 jakob jakob 64 Jan  5 15:47 2 -> /dev/null
l-wx------. 1 jakob jakob 64 Jan  5 15:47 3 -> /dev/null
lrwx------. 1 jakob jakob 64 Jan  5 15:47 4 -> 'anon_inode:[eventpoll]'

@slackner
Copy link
Contributor Author

slackner commented Jan 5, 2019

Awesome! I've created a pull request (but feel free to adjust the commit if you would like to call the package differently or change anything else).

@rfjakob
Copy link
Owner

rfjakob commented Jan 5, 2019

Fixed by 7e05e80

@rfjakob rfjakob closed this as completed Jan 5, 2019
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

No branches or pull requests

2 participants