-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
syscall: nextfd handling for attr.Files shuffle will clobber files #61751
Labels
compiler/runtime
Issues related to the Go compiler and/or runtime.
NeedsFix
The path to resolution is known, but the work has not been done.
Milestone
Comments
I will write a pull request along the lines of the second proposal, since it would simplify the code a fair bit. EDIT: It also turns out that the BSD and Solaris versions of ForkExec use F_DUPFD_CLOEXEC-equivalents when possible. |
cyphar
changed the title
[linux] syscall: nextfd handling for attr.Files shuffle will clobber files
syscall: nextfd handling for attr.Files shuffle will clobber files
Aug 4, 2023
gopherbot
added
the
compiler/runtime
Issues related to the Go compiler and/or runtime.
label
Aug 4, 2023
cyphar
added a commit
to cyphar/go
that referenced
this issue
Aug 4, 2023
…logic The existing clobber-prevention logic can end up clobbering random file descriptors, which can cause issues on Linux if a user wants to execute a /proc/self/fd/$n handle that isn't included in attr.Files. Similar logic already exists for the BSDs and Solaris. In addition, the F_DUPFD_CLOEXEC makes the clobber-prevention logic much simpler to follow. Closes golang#61751
Change https://go.dev/cl/515799 mentions this issue: |
cyphar
added a commit
to cyphar/go
that referenced
this issue
Aug 4, 2023
…logic The existing clobber-prevention logic can end up clobbering random file descriptors, which can cause issues on Linux if a user wants to execute a /proc/self/fd/$n handle that isn't included in attr.Files. Similar logic already exists for the BSDs and Solaris. In addition, the F_DUPFD_CLOEXEC makes the clobber-prevention logic much simpler to follow. Closes golang#61751
Nice work. |
dr2chase
added
the
NeedsFix
The path to resolution is known, but the work has not been done.
label
Aug 4, 2023
cyphar
added a commit
to cyphar/go
that referenced
this issue
Aug 5, 2023
…logic The existing clobber-prevention logic can end up clobbering random file descriptors, which can cause issues on Linux if a user wants to execute a /proc/self/fd/$n handle that isn't included in attr.Files. Similar logic already exists for the BSDs and Solaris. In addition, the F_DUPFD_CLOEXEC makes the clobber-prevention logic much simpler to follow. Closes golang#61751
cyphar
added a commit
to cyphar/go
that referenced
this issue
Aug 5, 2023
…logic The existing clobber-prevention logic can end up clobbering random file descriptors, which can cause issues on Linux if a user wants to execute a /proc/self/fd/$n handle that isn't included in attr.Files. Similar logic already exists for the BSDs and Solaris. In addition, the F_DUPFD_CLOEXEC makes the clobber-prevention logic much simpler to follow. Closes golang#61751
4 tasks
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 17, 2024
In opencontainers#3987(0e9a335), we may use a memfd to copy run to start runc init, due to a Go stdlib bug, we need to add safeExe to the set of ExtraFiles otherwise it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. This is less than ideal (because the descriptor will be non-O_CLOEXEC) however we have protections in "runc init" to stop us from leaking extra file descriptors. See <golang/go#61751>. But because of we have added safeExe to the set of ExtraFiles, if the fd of safeExe is too small, go stdlib will dup3 it to another fd, then it will cause the original fd closed. (opencontainers#4294) Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 17, 2024
In opencontainers#3987(0e9a335), we may use a memfd to copy run to start runc init, due to a Go stdlib bug, we need to add safeExe to the set of ExtraFiles otherwise it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. This is less than ideal (because the descriptor will be non-O_CLOEXEC) however we have protections in "runc init" to stop us from leaking extra file descriptors. See <golang/go#61751>. There is a race situation when we are opening this memfd, if the fd 5 or 6 was closed at that time, maybe it will be reused by memfd. But because of we have added safeExe to the set of ExtraFiles, if the fd of safeExe is than stdio fds count + ExtraFiles count, go stdlib will dup3 it to another fd, then it will cause the original fd closed. (opencontainers#4294) Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 17, 2024
In opencontainers#3987(0e9a335), we may use a memfd to copy run to start runc init, due to a Go stdlib bug, we need to add safeExe to the set of ExtraFiles otherwise it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. This is less than ideal (because the descriptor will be non-O_CLOEXEC) however we have protections in "runc init" to stop us from leaking extra file descriptors. See <golang/go#61751>. There is a race situation when we are opening this memfd, if the fd 5 or 6 was closed at that time, maybe it will be reused by memfd. But because of we have added safeExe to the set of ExtraFiles, if the fd of safeExe is than stdio fds count + ExtraFiles count, go stdlib will dup3 it to another fd, then it will cause the original fd closed. (opencontainers#4294) Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 17, 2024
In opencontainers#3987(0e9a335), we may use a memfd to copy run to start runc init, due to a Go stdlib bug, we need to add safeExe to the set of ExtraFiles otherwise it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. This is less than ideal (because the descriptor will be non-O_CLOEXEC) however we have protections in "runc init" to stop us from leaking extra file descriptors. See <golang/go#61751>. There is a race situation when we are opening this memfd, if the fd 5 or 6 was closed at that time, maybe it will be reused by memfd. But because of we have added safeExe to the set of ExtraFiles, if the fd of safeExe is than stdio fds count + ExtraFiles count, go stdlib will dup3 it to another fd, then it will cause the original fd closed. (opencontainers#4294) Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 17, 2024
In opencontainers#3987(0e9a335), we may use a memfd to copy run to start runc init, due to a Go stdlib bug, we need to add safeExe to the set of ExtraFiles otherwise it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. This is less than ideal (because the descriptor will be non-O_CLOEXEC) however we have protections in "runc init" to stop us from leaking extra file descriptors. See <golang/go#61751>. There is a race situation when we are opening this memfd, if the fd 6 or 7 was closed at that time, maybe it will be reused by memfd. But because of we have added safeExe to the set of ExtraFiles, if the fd of safeExe is not bigger than stdio fds count + ExtraFiles count, go stdlib will dup3 it to another fd, then it will cause the original fd closed. (opencontainers#4294) Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 18, 2024
In opencontainers#3987(0e9a335), we may use a memfd to copy run to start runc init, due to a Go stdlib bug, we need to add safeExe to the set of ExtraFiles otherwise it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. This is less than ideal (because the descriptor will be non-O_CLOEXEC) however we have protections in "runc init" to stop us from leaking extra file descriptors. See <golang/go#61751>. There is a race situation when we are opening this memfd, if the fd 6 or 7 was closed at that time, maybe it will be reused by memfd. Because we want to add safeExe to the set of ExtraFiles, if the fd of safeExe is too small, go stdlib will dup3 it to another fd, or dup3 a other fd to this fd, then it will cause the fd type cmd.Path refers to a random path. (issue: opencontainers#4294) Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 18, 2024
In opencontainers#3987(0e9a335), we may use a memfd to copy run to start runc init, due to a Go stdlib bug, we need to add safeExe to the set of ExtraFiles otherwise it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. This is less than ideal (because the descriptor will be non-O_CLOEXEC) however we have protections in "runc init" to stop us from leaking extra file descriptors. See <golang/go#61751>. There is a race situation when we are opening this memfd, if the fd 6 or 7 was closed at that time, maybe it will be reused by memfd. Because we want to add safeExe to the set of ExtraFiles, if the fd of safeExe is too small, go stdlib will dup3 it to another fd, or dup3 a other fd to this fd, then it will cause the fd type cmd.Path refers to a random path. (issue: opencontainers#4294) Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 21, 2024
Due to a Go stdlib bug, it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. See <golang/go#61751>. It will cause runc init process can't start. (opencontainers#4294) It only occurs when we are using a fd type string, for example: proc/self/fd/7, as a cmd path to start runc init, because there is a fd reuse race, if some small fd closed, the kernel may reuse this fd to refer to runc binary. If this fd num is small than the length of `cmd.ExtraFiles`, it will hit this Go stdlib bug. If we found this situation, we can dup it as a new bigger fd num to avoid. Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 21, 2024
Due to a Go stdlib bug, it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. See <golang/go#61751>. It will cause runc init process can't start. (opencontainers#4294) It only occurs when we are using a fd type string, for example: proc/self/fd/7, as a cmd path to start runc init, because there is a fd reuse race, if some small fd closed, the kernel may reuse this fd to refer to runc binary. If this fd num is small than the length of `cmd.ExtraFiles`, it will hit this Go stdlib bug. If we found this situation, we can dup it as a new bigger fd num to avoid. Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 21, 2024
Due to a Go stdlib bug, it is possible for the stdlib to clobber the fd during forkAndExecInChild1 and replace it with some other file that might be malicious. See <golang/go#61751>. It will cause runc init process can't start. (opencontainers#4294) It only occurs when we are using a fd type string, for example: proc/self/fd/7, as a cmd path to start runc init, because there is a fd reuse race, if some small fd closed, the kernel may reuse this fd to refer to runc binary. If this fd num is small than the length of `cmd.ExtraFiles`, it will hit this Go stdlib bug. If we found this situation, we can dup it as a new bigger fd num to avoid. Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 21, 2024
There is a race situation when we are opening a file, if there is a small fd was closed at that time, maybe it will be reused by safeExe. Because of Go stdlib fds shuffling bug, if the fd of safeExe is too small, go stdlib will dup3 it to another fd, or dup3 a other fd to this fd, then it will cause the fd type cmd.Path refers to a random path, and it can lead to an error "permission denied" when starting the process. Please see opencontainers#4294 and <golang/go#61751>. So we should not use the original fd of safeExe, but use the fd after shuffled by Go stdlib. Because Go stdlib will guarantee this fd refers to the correct file. Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 21, 2024
There is a race situation when we are opening a file, if there is a small fd was closed at that time, maybe it will be reused by safeExe. Because of Go stdlib fds shuffling bug, if the fd of safeExe is too small, go stdlib will dup3 it to another fd, or dup3 a other fd to this fd, then it will cause the fd type cmd.Path refers to a random path, and it can lead to an error "permission denied" when starting the process. Please see opencontainers#4294 and <golang/go#61751>. So we should not use the original fd of safeExe, but use the fd after shuffled by Go stdlib. Because Go stdlib will guarantee this fd refers to the correct file. Signed-off-by: lfbzhm <lifubang@acmcoder.com>
lifubang
added a commit
to lifubang/runc
that referenced
this issue
Oct 21, 2024
There is a race situation when we are opening a file, if there is a small fd was closed at that time, maybe it will be reused by safeExe. Because of Go stdlib fds shuffling bug, if the fd of safeExe is too small, go stdlib will dup3 it to another fd, or dup3 a other fd to this fd, then it will cause the fd type cmd.Path refers to a random path, and it can lead to an error "permission denied" when starting the process. Please see opencontainers#4294 and <golang/go#61751>. So we should not use the original fd of safeExe, but use the fd after shuffled by Go stdlib. Because Go stdlib will guarantee this fd refers to the correct file. Signed-off-by: lfbzhm <lifubang@acmcoder.com>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
compiler/runtime
Issues related to the Go compiler and/or runtime.
NeedsFix
The path to resolution is known, but the work has not been done.
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
For a bit of background, I am working on a runc patch to move some code we currently do in C to Go. The particular issue I ran into is related to some slightly awful stuff we do in runc in order to protect against certain container attacks (such as ) where we create a copy of the running executable (from
/proc/self/exe
) and actually execute a copy of the binary (usually a memfd) when running code inside the container.I am giving this background to preempt questions about "why on earth do you need to do this" when I give you the example program. 😅
https://go.dev/play/p/EN7Bf-OThar
If you adjust binTrueFd to be any other value, you'll see the program runs without issues.
The workaround for this problem is to pass the intended executable file as an
attr.Files
, even though we don't use it, but this results in a non-O_CLOEXEC
descriptor being passed to the child which I consider a security risk (at least in the context of runc). We have many other protections against leaking file descriptors to containers, so this isn't a problem for us -- but it seems that this is an actual bug in the stdlib that should be fixed.What did you expect to see?
The
syscall.StartProcess
call should execute the file descriptor specified by/proc/self/fd/$n
without the Go stdlib overwriting said file descriptor.What did you see instead?
The execve will attempt to exec a completely incorrect file descriptor, which in the best case will fail, and in the worst case will execute a completely unexpected program (in runc's case, as root).
In the case of the runc PR I mentioned above, this issue is only triggered by a single test because there is an apparent file descriptor leak which causes the file descriptor to be large enough that it gets overwritten -- meaning that the possible security issue (
runc
runs as root and has no restrictions in this context) is non-deterministic in our testing.Analysis
The bug is caused by an assumption in
forkAndExecInChild1
that the largest file descriptor relevant to the process is always included inattr.Files
and that thus any larger file descriptors can be used as scratch space.Unfortunately there isn't a particularly pretty solution to figuring out the largest file descriptor present in a process other that doing a readdir of
/proc/self/fd
. I suspect that one of the following solutions would be more workable:O_PATH
and make surenextfd
is larger than it (or special-case it likepipe
is today), and then doexecveat(AT_EMPTY_PATH)
to exec the program through a file descriptor (as an aside, the ability to do this as a user would be really nice!). This would plug this particular hole, and I suspect that theexecve
path is the only case where this bug could be hit.nextfd
logic for file descriptor shuffling (which appears to be used purely becausedup3()
requires a target descriptor and we want the cloned descriptors to beO_CLOEXEC
), useF_DUPFD_CLOEXEC
to avoid having to manage the new file descriptor number.I think the second option would clean up the existing code the most, but as a user it would be nice to be able to use
execveat(2)
with the Go stdlib.The text was updated successfully, but these errors were encountered: