-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Socket unexpectedly shared by multiple rootless tests #12155
Comments
Maybe just switch to |
So it's not a very "pretty" fix, but one simple thing that could be done is to check if there is an existing socket file right before We "should" be running the |
Obtaining "real" entropy from the system can be extremely slow if the pool is low/empty. |
According to the math/rand package doc it is not safe for concurrent use. Edit: Nevermind, the default |
@cdoern It could easily be both. I looked at the latest logs and it seems there are some actual flakes mixed in. In one case, it's a flake we were waiting to reproduce for another issue I opened...so...err...thanks 😁 @baude or @mheon I'm not 100% in understanding how the remote testing works, would you mind sharing your wisdom/observations here? |
This problem isn't just affecting Ubuntu, here's an example in Fedora-land: Search the log for More examples in fedora 34 (log): Search for |
Update: I took a look at the VMs, and it seems I was mistaken. The Ubuntu images do not have rngd installed/enabled, but the Fedora VMs do. Even so, |
@cevich I am not familiar with the different time complexities of |
Looking at the code, it first tries to get a "true" random number (backed by the kernel's entropy pool), if that fails it gets a number based on the current system time (in nanoseconds when supported). So I think what's happening in CI is kernel-entropy is exhausted, and two (or more) tests executing in parallel obtain the same random number ID. It's a mystery to me how/why the manage to execute at the exact same time (down to the micro or nanosecond). WRT kernel entropy, it's re-filled by hardware random-number-generators, and other "random" events on the system, like disk-access, network latency, and similar. However in VMs, we don't have access to a hardware RNG, so must rely on disk/network entropy sources which are pretty slow. When the pool is exhausted and a process does a blocking-read for more entropy, it will 'hang' until sufficient entropy is available. The problem is, it's highly likely some-or-another process/operation is concurrently responsible for exhausting the entropy in the first place, so there is likely some competition. Thus, things can hang for a VERY long time waiting for entropy, sometimes seconds, sometimes years! So the workaround to seed from the time (when an entropy read would block) is a sensible compromise...until it isn't. There's very little I can do about increasing kernel's entropy, since we're in a VM and making use of the paravirt-RNG (hardware) driver is a QEMU host-side option (which we have no control over). The best I can do is run the 'rngd' service (which we do on Fedora) but this isn't a perfect solution (and obviously MUCH slower than a hardware RNG). But there is _some_hope, because we know the kernel MUST use monotonically increasing IDs for both processes and threads. Since it's physically impossible for the same thread to request a random ID at the exact same time, adding the thread-ID into the fall-back time-based random ID will solve this problem. It's not perfect, there are still clashes possible where the combination of time + thread ID happen to cause a clash. However it is an improvement in an overall bad situation. |
Possible fix for containers/podman#12155 The golang docs for `math/rand` specifically mention that `rand.Seed()` should never ever be used in conjunction with any other rand method. Fix this with a simple/local mutex to protect critical code sections. This could be made more safe by exposing the mutex to downstream callers. This is left up to a future commit as/if needed. Also, in entropy-exhaustion situations it's possible for multiple concurrent *processes* to obtain the same fallback seed value, where the lock will not provide any protection. Clashes here are especially bad given the large number of downstream users of `GenerateNonCryptoID()`. Since the Linux kernel guarantees process ID uniqueness within a reasonable timespan, include this value into the fallback seed (along with the time). Note, the thread ID == process ID when there is only a single thread. However if there are multiple threads, the thread ID will provide additional entropy over the process ID. This is *not* a perfect solution, it is still possible for two processes to generate the same fallback seed value, given extremely unlucky timing. However, this is an improvement versus simplistic reliance on the clock. Signed-off-by: Chris Evich <[email protected]>
Possible fix for containers/podman#12155 The golang docs for `math/rand` specifically mention that `rand.Seed()` should never ever be used in conjunction with any other rand method. Fix this with a simple/local mutex to protect critical code sections. This could be made more safe by exposing the mutex to downstream callers. This is left up to a future commit as/if needed. Also, in entropy-exhaustion situations it's possible for multiple concurrent *processes* to obtain the same fallback seed value, where the lock will not provide any protection. Clashes here are especially bad given the large number of downstream users of `GenerateNonCryptoID()`. Since the Linux kernel guarantees process ID uniqueness within a reasonable timespan, include this value into the fallback seed (along with the time). This is *not* a perfect solution, it is still possible for two processes to generate the same fallback seed value, given extremely unlucky timing. However, this is an improvement versus simplistic reliance on the clock. Signed-off-by: Chris Evich <[email protected]>
So @mtrmac doesn't like my fix-the-seed PR (justifiably and understandably). He also pointed out there are inherent socket-creation races along with a missing-need (in the tests) to properly assert ID collection-avoidance. At this point I think what's really missing is an understanding how this issue is even possible (are the logs lying?) |
Possible fix for containers/podman#12155 The golang docs for `math/rand` specifically mention that `rand.Seed()` should never ever be used in conjunction with any other rand method. Fix this with a simple/local mutex to protect critical code sections. This could be made more safe by exposing the mutex to downstream callers. This is left up to a future commit as/if needed. Also, in entropy-exhaustion situations it's possible for multiple concurrent *processes* to obtain the same fallback seed value, where the lock will not provide any protection. Clashes here are especially bad given the large number of downstream users of `GenerateNonCryptoID()`. Since the Linux kernel guarantees process ID uniqueness within a reasonable timespan, include this value into the fallback seed (along with the time). This is *not* a perfect solution, it is still possible for two processes to generate the same fallback seed value, given extremely unlucky timing. However, this is an improvement versus simplistic reliance on the clock. Signed-off-by: Chris Evich <[email protected]>
#12168 should make sure we never accept randomly-generated ID collisions (so if we still see collisions afterwards, it’s something about how the RemoteSocket value is used/propagated later), and add a bit more logging if the collisions do come from the randomness mechanism. |
I have another hypothesis: See all of this, and weep: Line 217 in 4105b02
podman/test/e2e/common_test.go Line 380 in 4105b02
Line 476 in 4105b02
Which one wins? No idea. (I just have to conclude that providing process-wide singleton RNG with a publicly-accessible Now, if the Even without that, I think that every caller of |
Oof! What a good find! And `GinkgoRandomSeed()' is particularly aweful because IIRC there's even CLI option to override it to be the same value every time 😭
Agreed, I was thinking this too. Would it be hard to wrap all the calls such that the underlying implementation only sets the seed exactly once and ignores all subsequent attempts? Still, this is a great find and cleaning up all these blunders will undoubtedly improve our overall well-being 😁 |
Ref:
(I'm assuming this is GinkgoRandomSeed) |
Run that again, and the default will change with
We can’t reasonably patch One obviously safe approach is to keep the current And then, later, if we ever audit all of the Actually there are only a very small number of direct |
Should we remove the extra rand.Seed within kube.go? |
Just removing it might break things. |
Well we have CI Tests for this and as long as the Seed has been called in storage, we should be all set. Another option would to be to create a storage function to seed once, and replace this call with a call to storage seed. |
No, I’m saying that the |
Since we cannot control what vendor code does, It almost sounds as if we need to independently maintain some kind of local RNG state, recovering before and storing after every random-number request. Understandably that adds a LOT of complexity, but it seems like otherwise callers can never trust the state is non-deterministic 😞 |
On second thought...the golang community is pretty large...maybe somebody else has already solved this problem? |
Yes
that’s neither necessary nor quite possible: just keep a private
Reader like
|
Great. Though I still want us to take a step back, and look at the tests more deeply to confirm we're simply not leaking data: Your changes in #12168 when paired with the code that originally exposed the collisions (#11958) in my test PR allow the remote tests to pass. However, searching the logs shows we still have collisions (based on the new log output). I searched for duplicate seeds and timestamp, but found none: |
My fight with
So that’s interesting:
I.e. I think the above is an explanation of how the tests fail; and #12168 is a good fix for that.
Another thing we now know, from the logs, is that the Still, I think it would be healthy for all Podman callers to And, possibly, for all the shared users of |
@mtrmac thanks so much for all the experimenting and deep analysis. So it sounds like we need two additional issues:
I'm happy to open those issues (assuming you agree it's a good idea). As for #12168, I'll make some comments in the diff so we can at least move that forward for now. |
On second thought, that shouldn’t be possible; (I’m not sure I’ll be able to dig into that further this week.)
I think the above are a good idea regardless of what we find as the cause of the test socket collisions — but I wouldn’t argue there are the top top top priority. |
Yeah, that's most definitely strange. Perhaps there's some clash with |
As an experiment, I tried using In any case, once remove all the extra debugging/logs, I think we should move forward with these changes since they seem to address the primary concern (socket collisions). |
@mtrmac any updates on this? What's needed to move forward? |
Doh. % go test -mod=vendor -v -v ./test/e2e --ginkgo.seed=42 --ginkgo.parallel.node=1 --ginkgo.parallel.total=5
time="2021-11-17T22:25:41+01:00" level=warning msg="rand.Seed @ test/utils/utils.go:init 1637184341"
time="2021-11-17T22:25:41+01:00" level=warning msg="rand.Seed @ vendor/github.com/containers/storage/pkg/stringid/stringid.go:init 1747684635463093460"
=== RUN TestLibpod OTOH that disproves the But then again, https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/math/rand/rng.go;l=208-211;drc=refs%2Ftags%2Fgo1.17.3 ; and that’s actually documented in https://pkg.go.dev/math/rand#Seed :
despite the So, *shrug*, I don’t know how the seeds end up being the same but at 2^-31 probability floor I’m willing to live with this just being bad luck or something. |
IMHO, this is the really bad one. I think that ginkgo seed is the same value across all the threads. So that's just asking for a module-initialization-order change to give us a bad day. We should not use the ginkgo seed anywhere in our code. |
Per
OTOH, I could argue that making collisions more likely would help make the code more robust against collisions, which we should do anyway. It’s almost tempting to add a universal |
Where I think we’re at:
I’m personally more a bit more worried about the unclear math/rng seeding/state, which I think we now have fixes throughout. For collisions, the 256-bit ID space just might be good enough, now that the likelihood of seeding it from the crypto RNG is higher? (OTOH the size of the RNG seed is only 31 bits.) |
Great work @mtrmac and thanks for the summary. (All IMHO) I think there are some very rare and special cases where deterministic RNGs are required|useful. One example is the randomized test-order, which can be forced as needed by |
A friendly reminder that this issue had no activity for 30 days. |
At the very least #12168 needs reviewing/merging, to fix the test flakes we encounter from time to time. As for actually making the whole codebase robust against RNG collisions, #12155 (comment) overall seems pretty hard. |
Is this a BUG REPORT or FEATURE REQUEST? (leave only one on its own line)
/kind bug
Description
On an assumed unrelated PR, several 'remote' tests ran on an Ubuntu 21.04 VM and clashed over the same
/var/run/podman/podman-<ID>
which should never ever happen.Steps to reproduce the issue:
Execute CI testing
If failure doesn't occur, go back to step 1
Describe the results you received:
The
podman create pod with doubled name
test during setup, tried to create a podman service with the same socket as used by the previous test (podman create pod with name
) [Annotated Log]Error: unable to create socket: listen unix /run/podman/podman-67f86292cce183cb624e0205c8bb8361ec130c3aabb11be86a20987e15b234c8.sock: bind: address already in use
Searching through the log, there are multiple subsequent examples of this occurring as well.
Describe the results you expected:
Despite running in parallel, remote tests should never (NEVER) use the same service socket name (unless relevant to the specific test)
Additional information you deem important (e.g. issue happens only occasionally):
Tracing up the code-path from
uuid := stringid.GenerateNonCryptoID()
, into containers/common stringid package, if the system lacks entropy we base the random seed on the current time. Therefor if two tests happen to start at the exact same time, they could obtain clashing IDs. Note: This function is used liberally, so the ID clash needn't occur in a socket name.Output of
podman version
:4.0.0-dev
built from source (553c0bd2a2a12739702cc408c3be6b93a91e7012) PR referenced above.Output of
podman info --debug
:See test log in description
Package info (e.g. output of
rpm -q podman
orapt list podman
):See test log in description
Have you tested with the latest version of Podman and have you checked the Podman Troubleshooting Guide? (https://github.com/containers/podman/blob/master/troubleshooting.md)
Yes
Additional environment details (AWS, VirtualBox, physical, etc.):
GCP VM
ubuntu-c6431352024203264
The text was updated successfully, but these errors were encountered: