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.
should the file be closed first or does it not matter?
There was a problem hiding this comment.
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?
|
|
||
| func awaitProcessExit(pid int) <-chan struct{} { | ||
| isDead := make(chan struct{}) | ||
| go func() { |
There was a problem hiding this comment.
What happens when the exec fifo is opened successfully? Will this go routine live forever?
There was a problem hiding this comment.
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.
same issue here, if the process dies, how do we unblock this goroutine?
There was a problem hiding this comment.
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 <cfurman@pivotal.io>
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.
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.
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 <cfurman@pivotal.io>
|
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 <mark.stemm@gmail.com>
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 <mark.stemm@gmail.com>
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 <mark.stemm@gmail.com>
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 <mark.stemm@gmail.com>
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 <mark.stemm@gmail.com>
When starting a container with
runc startorrunc 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 runandrunc deleteoperations. Hopefully someone has a better idea of how to get a more sensible test intorunc.[Fixes: #1697]
Cheers,
@williammartin & Craig