-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Avoid race when opening exec fifo #1698
Avoid race when opening exec fifo #1698
Conversation
626258e
to
6b7ed4e
Compare
if err := readFromExecFifo(f); err != nil { | ||
return err | ||
} | ||
return os.Remove(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should the file be closed first or does it not matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this should matter. I suppose the fd will point to an inaccessible location on the filesystem for some amount of time, but in terms of code cleanliness, the defer
seems better?
libcontainer/container_linux.go
Outdated
|
||
func awaitProcessExit(pid int) <-chan struct{} { | ||
isDead := make(chan struct{}) | ||
go func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when the exec fifo is opened successfully? Will this go routine live forever?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot, this is definitely a problem in the attach case. Think we've fixed it.
func awaitFifoOpen(path string) <-chan openResult { | ||
fifoOpened := make(chan openResult) | ||
go func() { | ||
f, err := os.OpenFile(path, os.O_RDONLY, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same issue here, if the process dies, how do we unblock this goroutine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this is in an issue like the other one, if the process dies we error out https://github.com/cloudfoundry-incubator/runc/blob/exec-fifo-race/libcontainer/container_linux.go#L275 and then cleanup happens as it would in any other case.
When starting a container with `runc start` or `runc run`, the stub process (runc[2:INIT]) opens a fifo for writing. Its parent runc process will open the same fifo for reading. In this way, they synchronize. If the stub process exits at the wrong time, the parent runc process will block forever. This can happen when racing 2 runc operations against each other: `runc run/start`, and `runc delete`. It could also happen for other reasons, e.g. the kernel's OOM killer may select the stub process. This commit resolves this race by racing the opening of the exec fifo from the runc parent process against the stub process exiting. If the stub process exits before we open the fifo, we return an error. Another solution is to wait on the stub process. However, it seems it would require more refactoring to avoid calling wait multiple times on the same process, which is an error. Signed-off-by: Craig Furman <[email protected]>
6b7ed4e
to
8d3e6c9
Compare
Looking |
go func() { | ||
f, err := os.OpenFile(path, os.O_RDONLY, 0) | ||
if err != nil { | ||
fifoOpened <- openResult{err: newSystemErrorWithCause(err, "open exec fifo for reading")} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not affect cleanup, but I think we better return here, because consumer only read it once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We agree from a code cleanliness perspective, although you're right that it doesn't affect cleanup. We've added a return after that line.
One minor tip, otherwise LGTM to me. |
I was able to test this patch, and have updated moby/moby#36010 with my finding so far. In short, it does appear to resolve the issue I was having. |
Signed-off-by: Craig Furman <[email protected]>
We added another commit to address a comment. If you'd like us to rebase and squash let us know. |
Sample Falco alert: ``` File below / or /root opened for writing (user=<NA> command=runc:[1:CHILD] init parent=docker-runc-cur file=/exec.fifo program=runc:[1:CHILD] CID1 image=<NA>) ``` This github issue provides some context: opencontainers/runc#1698 Signed-off-by: Mark Stemm <[email protected]>
Sample Falco alert: ``` File below / or /root opened for writing (user=<NA> command=runc:[1:CHILD] init parent=docker-runc-cur file=/exec.fifo program=runc:[1:CHILD] CID1 image=<NA>) ``` This github issue provides some context: opencontainers/runc#1698 Signed-off-by: Mark Stemm <[email protected]>
Sample Falco alert: ``` File below / or /root opened for writing (user=<NA> command=runc:[1:CHILD] init parent=docker-runc-cur file=/exec.fifo program=runc:[1:CHILD] CID1 image=<NA>) ``` This github issue provides some context: opencontainers/runc#1698 Signed-off-by: Mark Stemm <[email protected]>
Sample Falco alert: ``` File below / or /root opened for writing (user=<NA> command=runc:[1:CHILD] init parent=docker-runc-cur file=/exec.fifo program=runc:[1:CHILD] CID1 image=<NA>) ``` This github issue provides some context: opencontainers/runc#1698 Signed-off-by: Mark Stemm <[email protected]>
Sample Falco alert: ``` File below / or /root opened for writing (user=<NA> command=runc:[1:CHILD] init parent=docker-runc-cur file=/exec.fifo program=runc:[1:CHILD] CID1 image=<NA>) ``` This github issue provides some context: opencontainers/runc#1698 Signed-off-by: Mark Stemm <[email protected]>
When starting a container with
runc start
orrunc run
, the stubprocess (runc[2:INIT]) opens a fifo for writing. Its parent runc process
will open the same fifo for reading. In this way, they synchronize.
If the stub process exits at the wrong time, the parent runc process
will block forever.
This can happen when racing 2 runc operations against each other:
runc run/start
, andrunc delete
. It could also happen for other reasons,e.g. the kernel's OOM killer may select the stub process.
This commit resolves this race by racing the opening of the exec fifo
from the runc parent process against the stub process exiting. If the
stub process exits before we open the fifo, we return an error.
Another solution is to wait on the stub process. However, it seems it
would require more refactoring to avoid calling wait multiple times on
the same process, which is an error.
Note: We aren't really sure how to integration test this in a sane way. In Garden, we wrote a test but it involves patching in:
to expose the race condition, and then performing
runc run
andrunc delete
operations. Hopefully someone has a better idea of how to get a more sensible test intorunc
.[Fixes: #1697]
Cheers,
@williammartin & Craig