udev: prevent interruption by thread cancellation#24
Conversation
Using mutexes only guarantees no concurrency during normal runtime. However, if a thread is cancelled mid-execution, a libudev API call might be interrupted halfway (partially executed), leaving memory values in an inconsistent or abnormal state. Furthermore, this change aligns with the requirement that libudev interfaces are not thread-safe and must not be called from multiple threads. Signed-off-by: Wu Guanghao <wuguanghao3@huawei.com>
|
Thanks! I have applied your patch to my |
|
@wuguanghao3, @bmarzins, may I ask you to review 58519b1 ? |
LGTM But I have a question: can't we just use (call) directly instead of udev_xxx(arg1, arg2, ...)? Do we need to use different macros based on the parameters? What is the advantage of doing it this way? |
In principle we could. We wouldn't need the Let me try. |
|
I remember the rationale now. @wuguanghao3, your suggestion works only with preprocessor macros. For wrapper functions it's either impossible or really clumsy, because we need to insert the argument types in the wrapper function's argument list, while omitting them in the actual libudev call. (If you can conceive of an elegant way to do this, let me know). What we can do is just use preprocessor macros: This works, and we don't even need any However, we have the wrapper functions for a reason. By introducing the wrapper functions, we achieve clean separation from libudev, and we can make sure that we don't ever call libudev directly. We just don't include Footnotes
|
|
I realize what I did previously here was wrong. By applying my commit on top of yours, I called I've pushed a new version to my tip branch (b2d35f7). @wuguanghao3, please review test it in your environment. And again, if you can conceive of a simpler way to write these wrappers, let me know. |
|
I had to rebase and re-push again, current commit ID is 3019b25 |
OK, LGTM |
Have you run your test with this code? |
Yes, currently testing. The issue has not recurred yet. |
The UAF issue could not be reproduced. However, a new reference issue occurred, which appears to be exclusive to version 0.14.3 and was not present in 0.9.5. I am currently running tests on 0.14.3 with the current patch applied. |
|
Sigh. This indicates a refcount underflow on the udev_device. Which is basically the same thing as a UAF. Could you perhaps apply the current commit to 0.9.5 and see if it's really a regression between 0.95 and 0.14.3? Given that the issue has apparently occured after 4h of testing, you proabably need to run the test for a couple of hours to verify that the problem does not occur. |
The UAF and the reference issue are distinct. The UAF involves memory inconsistency resulting from thread cancellation, whereas the reference issue may stem from a redundant execution of an upper-level call. |
It has been confirmed that this issue was introduced in a release between 0.9.5 and 0.14.3. I conducted a 7-hour test running version 0.9.5 with the latest commit, and the issue could not be reproduced. |
A refcount underflow means that an object is being unref'd after having reached refcount zero. IOW, this object was still in use after it should have been freed already. Maybe the How often have you observed this refcount underflow so far?
There has been a major cleanup of the code that frees maps and paths in 0.14.0 (commit edf2bac ff.) So if you want to to investigate further, you may want to start with comparing 0.13.0 and 0.14.0, both with the latest wrapper patch applied. When you apply the patch, make sure that no direct libudev calls remain. Given that you observe the problem in the Can you share your test program, so that others can try to reproduce the issue and find a solution? I have also done some extensive testing, but apparently the way you restart multipathd during the test can trigger error conditions that I was not seeing. |
Below is a simplified test case that includes the main operations. One additional point to note: we identify local disks as multipath by default. |
Using mutexes only guarantees no concurrency during normal runtime. However, if a thread is cancelled mid-execution, a libudev API call might be interrupted halfway (partially executed), leaving memory values in an inconsistent or abnormal state.
Furthermore, this change aligns with the requirement that libudev interfaces are not thread-safe and must not be called from multiple threads.