-
Notifications
You must be signed in to change notification settings - Fork 141
posix/executor: Fix uninitialized memory access in case of child process error #191
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
Conversation
Background: In order to communicate errors from the child to the parent, a pipe is used. The child writes 2 ints (errno value, length of error message) followed by N bytes containing the error message. The parent didn't validate the amount of bytes received, so it sometimes accessed uninitialized memory. Specifically, _read_error()'s first read() of length 8 sometimes only returned 4 bytes, leaving data[1] uninitialized. This caused further problems since data[1] is the error message length used for the next memory allocation and read(). This probably only happened when the child used write_error() (writing 4 + 4 + N bytes) instead of _write_error() (writing 8 + N bytes). Either way, read() on a pipe is not necessarily guaranteed to return the requested amount of bytes, so we have to loop and call it again in order to get the rest. A potential danger now is that read_all() waits indefinitely, if the child process doesn't send the expected/announced amount of bytes. However, given the current code, I think that can't happen, plus end-of-file is (still) explicitly handled as early abort.
|
I don't understand why this would cause an error? Wouldn't the buffer of the pipe avoid that? |
|
It comes down to this: What I saw here was the parent process wanting to read 8 bytes from the pipe, but (sometimes) receiving only 4 bytes in the first read() call. In that case it required a second read() call to read the remaining 4 bytes. Since it didn't do that, it used uninitialized memory in the buffer instead. It seemed to happen only if the child process hit that one case which uses To reproduce both cases, the following demo program using pipe/fork/read/write seems to do the trick here. It has a child process doing the two write() calls writing 4 bytes each, and there is a Without the sleep, the parent reads 8 bytes immediately here (lucky timing I guess): Different behaviour with the sleep: |
|
Thanks for the fix, I think this one was important. Of course I'd prefer to check the read() return value, but I think your solution will solve the issue in practice, too. Time to test it a bit. |
Squash after invalid branch & merge conflict. * Fixed file_descriptor move assignment operator to return a reference to 'this'. Issue # 219 * Returning *this instead of erroneous *this. Issue # 219 * Removed unneeded WNOHANG. * Closes #190 * Closes #121 * Attempting to fix wchar_t build error on circle. * Closes #197. * Changed child(pid_t) signature. * Multiple fixes. * Closes #189. * Closes #191. * Added missing work guard on windows. * Trying to catch windows early complete. * Increased log level on windows. * Multiple windows test fixes * Removed overly constraint tests. * fix missing headers * Closes klemens-morgenstern/boost-process#218 * Update executor.hpp explicit cast to int to silence this: `error: non-constant-expression cannot be narrowed from type 'unsigned long' to 'int' in initializer list [-Wc++11-narrowing]` * Fix posix implementation of move constructor/assignment in file_descriptor * Adjust docs `@boost` relative paths * Fixed UB for large environment names. * Closes #207. * Drone setup * Added include for filesystem::fstream. * Disabled useless tests. * Fixed environment length checks. * Pipe test & warning fixes. * Disabled warnings & added windows include fix. * More test fixes. * Removed some tests from apple build. * Removed some tests from apple build. * Disabled OSX tests via build script & fixed windows examples. * TSA fix attempt. Co-authored-by: James Baker <[email protected]> Co-authored-by: silent <[email protected]> Co-authored-by: ikrijan <[email protected]> Co-authored-by: Shauren <[email protected]> Co-authored-by: alandefreitas <[email protected]>
The parent process didn't validate the amount of bytes received from the child process, so it sometimes accessed uninitialized memory. This could be triggered by error cases using the old write_error() function, which sends 4 + 4 + N bytes instead of 8 + N as assumed by _read_error().
For example: Giving an invalid file name for stdout/stdin redirection (or if the file cannot be opened for some other reason):