Skip to content

[libc++] Fix UB in filesystem::__copy for non-existent destination. #87615

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

ivafanas
Copy link
Contributor

@ivafanas ivafanas commented Apr 4, 2024

lstat/stat/fstat has no guarantee whether struct stat buffer is changed or not on failure. filesystem::__copy function assumes, that struct stat buffer is not updated on lstat/stat/fstat failure, which is not true.

It appears that for non-existing destination detail::posix_lstat(to, t_st, &m_ec1) returns failure indicator and overwrites struct stat buffer with garbage values, which accidentally equal to the f_st from stack internals from the previous detail::posix_lstat(from, f_st, &m_ec1) call.

file_type::not_found is a known status, so check against if (not status_known(t)) is passed and execution continues further. Then __copy function returns errc::function_not_supported, because stats are accidentally equivalent, which is a mistake.

Before checking for detail::stat_equivalent, need to make sure, that lstat/stat/fstat functions returned success.

As soon as f_st and t_st access without checking for the lstat/stat/fstat success indicator is an UB, it is not needed to zero-initialize them.

lstat/stat/fstat has no guarantee whether `struct stat` buffer is chaged or not
on failure. filesystem::__copy functon assumes, that `struct stat` buffer is not
updated on lstat/stat/fstat failure, which is not true.

It appears that for non-existing destination
`detail::posix_lstat(to, t_st, &m_ec1)` returns failure indicator and overwrites
`struct stat` buffer with a garbage values, which accidentially equal to the
`f_st` from the stack internals from the previous
`detail::posix_lstat(from, f_st, &m_ec1)` call.

`file_type::not_found` is a known status, so check against
`if (not status_known(t))` is passed and execution continues further. Then
`__copy` function returns `errc::function_not_supported`, because stats are
accidentially equivalent, which is a mistake.

Before checking for `detail::stat_equivalent`, need to make sure, that
lstat/stat/fstat functions returned success.

As soon as, `f_st` and `t_st` usage without checking for the lstat/stat/fstat
success indicator is an UB, it is not needed to zero-initialize them.
@ivafanas ivafanas requested a review from a team as a code owner April 4, 2024 10:13
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Apr 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Apr 4, 2024

@llvm/pr-subscribers-libcxx

Author: Afanasyev Ivan (ivafanas)

Changes

lstat/stat/fstat has no guarantee whether struct stat buffer is chaged or not on failure. filesystem::__copy functon assumes, that struct stat buffer is not updated on lstat/stat/fstat failure, which is not true.

It appears that for non-existing destination
detail::posix_lstat(to, t_st, &m_ec1) returns failure indicator and overwrites struct stat buffer with a garbage values, which accidentially equal to the f_st from the stack internals from the previous
detail::posix_lstat(from, f_st, &m_ec1) call.

file_type::not_found is a known status, so check against if (not status_known(t)) is passed and execution continues further. Then __copy function returns errc::function_not_supported, because stats are accidentially equivalent, which is a mistake.

Before checking for detail::stat_equivalent, need to make sure, that lstat/stat/fstat functions returned success.

As soon as, f_st and t_st usage without checking for the lstat/stat/fstat success indicator is an UB, it is not needed to zero-initialize them.


Full diff: https://github.com/llvm/llvm-project/pull/87615.diff

1 Files Affected:

  • (modified) libcxx/src/filesystem/operations.cpp (+3-3)
diff --git a/libcxx/src/filesystem/operations.cpp b/libcxx/src/filesystem/operations.cpp
index 62bb248d5eca9b..87b38fbdecf4ed 100644
--- a/libcxx/src/filesystem/operations.cpp
+++ b/libcxx/src/filesystem/operations.cpp
@@ -109,20 +109,20 @@ void __copy(const path& from, const path& to, copy_options options, error_code*
   const bool sym_status2 = bool(options & copy_options::copy_symlinks);
 
   error_code m_ec1;
-  StatT f_st = {};
+  StatT f_st;
   const file_status f =
       sym_status || sym_status2 ? detail::posix_lstat(from, f_st, &m_ec1) : detail::posix_stat(from, f_st, &m_ec1);
   if (m_ec1)
     return err.report(m_ec1);
 
-  StatT t_st          = {};
+  StatT t_st;
   const file_status t = sym_status ? detail::posix_lstat(to, t_st, &m_ec1) : detail::posix_stat(to, t_st, &m_ec1);
 
   if (not status_known(t))
     return err.report(m_ec1);
 
   if (!exists(f) || is_other(f) || is_other(t) || (is_directory(f) && is_regular_file(t)) ||
-      detail::stat_equivalent(f_st, t_st)) {
+      (exists(t) && detail::stat_equivalent(f_st, t_st))) {
     return err.report(errc::function_not_supported);
   }
 

@ivafanas
Copy link
Contributor Author

ivafanas commented Apr 4, 2024

@EricWF, could you please review the request.

I do not have commit access. If there are no objections, could you please merge the request into repo.
Thanks.

@ivafanas
Copy link
Contributor Author

ivafanas commented Apr 8, 2024

Could anybody please review the PR.

@ivafanas
Copy link
Contributor Author

Could anybody please review the PR?
Any comments?

@4-rodrigo-salazar
Copy link
Contributor

4-rodrigo-salazar commented Apr 16, 2024

I've reviewed this change and it looks good to me. I'm not an approver in this repo though :)

Relatedly, I have gone over the rest of this file to find other potentially incorrect error_code paths in #88341 and found a couple more issues (less serious than the bug fix in this diff).

@ivafanas
Copy link
Contributor Author

Ping.

Please, note that the issue is not the theoretical research.
We've actually triggered this bug on some linux distribution.

@EricWF
Copy link
Member

EricWF commented Apr 22, 2024

Are there tests for this? If so, I wonder why MSAN didn't catch it.

Could you please confirm or add a test?

@EricWF EricWF self-requested a review April 22, 2024 13:20
@ivafanas
Copy link
Contributor Author

It is existing test failure:

std/input.output/filesystems/fs.op.funcs/fs.op.copy/copy.pass.cpp
TEST_CASE(from_is_regular_file)

Issue depends on fstat/lstat/stat posix functions implementation.

At first, I thought, that problem is in linux itself, but documentation states that struct stat state is undefined if fstat/lstat/stat returned "failure" indicator. So, it happens that bug is in fs::__copy. It must not assume that struct stat state is saved after fstat/lstat/stat call.

@ivafanas
Copy link
Contributor Author

ivafanas commented May 2, 2024

Ping.

Failure depends on posix functions implementation, that's why it isn't visible in regular CI.
We've caught the issue on the already existing tests on specific Linux version.

@ivafanas
Copy link
Contributor Author

Hi.
Any comments?

We can reject the ticket if it is unclear.

@mordante
Copy link
Member

Hi. Any comments?

We can reject the ticket if it is unclear.

I'll ping Eric for you. (Missing messages in the GitHub queue is not hard :-/)

@EricWF
Copy link
Member

EricWF commented May 21, 2024

At first, I thought, that problem is in linux itself, but documentation states that struct stat state is undefined if fstat/lstat/stat returned "failure" indicator.

Which documentation are you referring to?

@EricWF
Copy link
Member

EricWF commented May 21, 2024

LGTM once the questions are answered.

What OS and version are you running specifically to trigger this?

I'll leave it to @mordante to commit.

@ivafanas
Copy link
Contributor Author

Hi!

I've searched for docs here:
https://linux.die.net/man/2/stat
https://man7.org/linux/man-pages/man2/stat.2.html

It does not state that buf parameter is not affected if error is returned. It is quite strange to update buf on failure, but, well, documentation allows it.

Problem is found on kernel 5.4.0 for the custom linux provided by our customer. Have no idea whether posix implementation of stat function is standard or customized.

@ivafanas
Copy link
Contributor Author

ivafanas commented Jun 6, 2024

Hi!

Let's close this pull request if nobody is interested.

I do not want to track it anymore. We can keep the fix in our customer fork without sharing it to community.

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through the PR explanation and the discussions since the start of the PR, and reviewed the code itself.

This seems like a real problem to me, only one that we can't realistically find in our CI testing environments because of how our OSes implement these low level functions. I'm happy to hear that our existing tests would actually catch this issue on an OS where the stat functions are implemented in that particular (but legal) way.

I think there's no reason to hold off with this patch any longer. Thanks for the report, explanation and the fix @ivafanas .

@ldionne ldionne merged commit 7e5bc71 into llvm:main Jun 12, 2024
@ivafanas
Copy link
Contributor Author

Louis, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants