Skip to content

Commit

Permalink
Updated protection of remove_all against CVE-2022-21658 on Windows.
Browse files Browse the repository at this point in the history
This follows up the previous update for POSIX.

The new implementation of remove_all on Windows Vista and later uses
NtCreateFile internal function in order to open files relative to
a previously opened directory handle, similar to POSIX openat.
Furthermore, querying file status and removing the file is now also
done through file handles to avoid performing path resolutions.

Closes #224.
  • Loading branch information
Lastique committed Jul 23, 2022
1 parent 36cf9aa commit ea22e76
Show file tree
Hide file tree
Showing 6 changed files with 551 additions and 191 deletions.
2 changes: 1 addition & 1 deletion doc/release_history.html
Expand Up @@ -45,7 +45,7 @@ <h2>1.80.0</h2>
<li>On Windows, added a workaround for FAT/exFAT filesystems that produce <code>ERROR_INVALID_PARAMETER</code> when querying file attributes. This affected <code>status</code> and <code>symlink_status</code>, which reported that files do not exist, and directory iterators, which failed to construct, as well as other dependent operations. (<a href="https://github.com/boostorg/filesystem/issues/236">#236</a>, <a href="https://github.com/boostorg/filesystem/issues/237">#237</a>)</li>
<li>Worked around a compilation problem on <a href="https://www.rtems.org/">RTEMS</a>. (<a href="https://github.com/boostorg/filesystem/pull/240">#240</a>)</li>
<li>On Linux, corrected switching to <code>sendfile</code> <code>copy_file</code> implementation if <code>copy_file_range</code> failed with <code>ENOSYS</code> in runtime. The <code>sendfile</code> fallback implementation used to skip the filesystem type check and could fail for some filesystems.</li>
<li>On POSIX systems supporting <code>openat</code> and related APIs defined in POSIX.1-2008, improved protection of <code>remove_all</code> against <a href="https://www.cve.org/CVERecord?id=CVE-2022-21658">CVE-2022-21658</a> that was implemented in the previous release. The previous fix could still result in removing unintended files in <a href="https://github.com/boostorg/filesystem/issues/224#issuecomment-1183738097">certain conditions</a>. Other systems, including Windows, remain vulnerable.</li>
<li>On POSIX systems supporting <code>openat</code> and related APIs defined in POSIX.1-2008 and on Windows Vista and later, improved protection of <code>remove_all</code> against <a href="https://www.cve.org/CVERecord?id=CVE-2022-21658">CVE-2022-21658</a> that was implemented in the previous release. The previous fix could still result in removing unintended files in <a href="https://github.com/boostorg/filesystem/issues/224#issuecomment-1183738097">certain conditions</a>. Other systems remain vulnerable.</li>
</ul>

<h2>1.79.0</h2>
Expand Down
2 changes: 2 additions & 0 deletions include/boost/filesystem/directory.hpp
Expand Up @@ -263,6 +263,7 @@ struct dir_itr_imp :
public boost::intrusive_ref_counter< dir_itr_imp >
{
#ifdef BOOST_WINDOWS_API
bool close_handle;
unsigned char extra_data_format;
std::size_t current_offset;
#endif
Expand All @@ -271,6 +272,7 @@ struct dir_itr_imp :

dir_itr_imp() BOOST_NOEXCEPT :
#ifdef BOOST_WINDOWS_API
close_handle(false),
extra_data_format(0u),
current_offset(0u),
#endif
Expand Down
112 changes: 66 additions & 46 deletions src/directory.cpp
Expand Up @@ -610,7 +610,8 @@ inline system::error_code dir_itr_close(dir_itr_imp& imp) BOOST_NOEXCEPT

if (imp.handle != NULL)
{
::CloseHandle(imp.handle);
if (BOOST_LIKELY(imp.close_handle))
::CloseHandle(imp.handle);
imp.handle = NULL;
}

Expand Down Expand Up @@ -762,62 +763,76 @@ error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_stat
return error_code();
}

error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, unsigned int opts, directory_iterator_params*, fs::path& first_filename, fs::file_status& sf, fs::file_status& symlink_sf)
error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, unsigned int opts, directory_iterator_params* params, fs::path& first_filename, fs::file_status& sf, fs::file_status& symlink_sf)
{
boost::intrusive_ptr< detail::dir_itr_imp > pimpl(new (dir_itr_extra_size) detail::dir_itr_imp());
if (BOOST_UNLIKELY(!pimpl))
return make_error_code(system::errc::not_enough_memory);

DWORD flags = FILE_FLAG_BACKUP_SEMANTICS;
if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u)
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
handle_wrapper h(create_file_handle(dir, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flags));
if (BOOST_UNLIKELY(h.handle == INVALID_HANDLE_VALUE))
GetFileInformationByHandleEx_t* get_file_information_by_handle_ex = filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api);

handle_wrapper h;
HANDLE iterator_handle;
bool close_handle = true;
if (params != NULL && params->use_handle != INVALID_HANDLE_VALUE)
{
return_last_error:
DWORD error = ::GetLastError();
return error_code(error, system_category());
// Operate on externally provided handle, which must be a directory handle
iterator_handle = params->use_handle;
close_handle = params->close_handle;
}

GetFileInformationByHandleEx_t* get_file_information_by_handle_ex = filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api);
if (BOOST_LIKELY(get_file_information_by_handle_ex != NULL))
else
{
file_attribute_tag_info info;
BOOL res = get_file_information_by_handle_ex(h.handle, file_attribute_tag_info_class, &info, sizeof(info));
if (BOOST_UNLIKELY(!res))
DWORD flags = FILE_FLAG_BACKUP_SEMANTICS;
if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u)
flags |= FILE_FLAG_OPEN_REPARSE_POINT;

iterator_handle = h.handle = create_file_handle(dir, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flags);
if (BOOST_UNLIKELY(iterator_handle == INVALID_HANDLE_VALUE))
{
// On FAT/exFAT filesystems requesting FILE_ATTRIBUTE_TAG_INFO returns ERROR_INVALID_PARAMETER. See the comment in symlink_status.
return_last_error:
DWORD error = ::GetLastError();
if (error == ERROR_INVALID_PARAMETER || error == ERROR_NOT_SUPPORTED)
goto use_get_file_information_by_handle;

return error_code(error, system_category());
}

if (BOOST_UNLIKELY((info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0u))
return make_error_code(system::errc::not_a_directory);

if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u)
if (BOOST_LIKELY(get_file_information_by_handle_ex != NULL))
{
if ((info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u && is_reparse_point_tag_a_symlink(info.ReparseTag))
return make_error_code(system::errc::too_many_symbolic_link_levels);
}
}
else
{
use_get_file_information_by_handle:
BY_HANDLE_FILE_INFORMATION info;
BOOL res = ::GetFileInformationByHandle(h.handle, &info);
if (BOOST_UNLIKELY(!res))
goto return_last_error;
file_attribute_tag_info info;
BOOL res = get_file_information_by_handle_ex(iterator_handle, file_attribute_tag_info_class, &info, sizeof(info));
if (BOOST_UNLIKELY(!res))
{
// On FAT/exFAT filesystems requesting FILE_ATTRIBUTE_TAG_INFO returns ERROR_INVALID_PARAMETER. See the comment in symlink_status.
DWORD error = ::GetLastError();
if (error == ERROR_INVALID_PARAMETER || error == ERROR_NOT_SUPPORTED)
goto use_get_file_information_by_handle;

if (BOOST_UNLIKELY((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0u))
return make_error_code(system::errc::not_a_directory);
return error_code(error, system_category());
}

if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u)
if (BOOST_UNLIKELY((info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0u))
return make_error_code(system::errc::not_a_directory);

if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u)
{
if ((info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u && is_reparse_point_tag_a_symlink(info.ReparseTag))
return make_error_code(system::errc::too_many_symbolic_link_levels);
}
}
else
{
if ((info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u && is_reparse_point_a_symlink_ioctl(h.handle))
return make_error_code(system::errc::too_many_symbolic_link_levels);
use_get_file_information_by_handle:
BY_HANDLE_FILE_INFORMATION info;
BOOL res = ::GetFileInformationByHandle(iterator_handle, &info);
if (BOOST_UNLIKELY(!res))
goto return_last_error;

if (BOOST_UNLIKELY((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0u))
return make_error_code(system::errc::not_a_directory);

if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u)
{
if ((info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u && is_reparse_point_a_symlink_ioctl(h.handle))
return make_error_code(system::errc::too_many_symbolic_link_levels);
}
}
}

Expand All @@ -826,7 +841,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
{
case file_id_extd_dir_info_format:
{
if (!get_file_information_by_handle_ex(h.handle, file_id_extd_directory_restart_info_class, extra_data, dir_itr_extra_size))
if (!get_file_information_by_handle_ex(iterator_handle, file_id_extd_directory_restart_info_class, extra_data, dir_itr_extra_size))
{
DWORD error = ::GetLastError();

Expand Down Expand Up @@ -860,7 +875,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
case file_full_dir_info_format:
fallback_to_file_full_dir_info_format:
{
if (!get_file_information_by_handle_ex(h.handle, file_full_directory_restart_info_class, extra_data, dir_itr_extra_size))
if (!get_file_information_by_handle_ex(iterator_handle, file_full_directory_restart_info_class, extra_data, dir_itr_extra_size))
{
DWORD error = ::GetLastError();

Expand Down Expand Up @@ -889,7 +904,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
case file_id_both_dir_info_format:
fallback_to_file_id_both_dir_info:
{
if (!get_file_information_by_handle_ex(h.handle, file_id_both_directory_restart_info_class, extra_data, dir_itr_extra_size))
if (!get_file_information_by_handle_ex(iterator_handle, file_id_both_directory_restart_info_class, extra_data, dir_itr_extra_size))
{
DWORD error = ::GetLastError();

Expand Down Expand Up @@ -917,7 +932,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
io_status_block iosb;
boost::winapi::NTSTATUS_ status = nt_query_directory_file
(
h.handle,
iterator_handle,
NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
Expand Down Expand Up @@ -952,8 +967,10 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
break;
}

pimpl->handle = h.handle;

pimpl->handle = iterator_handle;
h.handle = INVALID_HANDLE_VALUE;
pimpl->close_handle = close_handle;

done:
imp.swap(pimpl);
Expand All @@ -966,7 +983,8 @@ inline system::error_code dir_itr_close(dir_itr_imp& imp) BOOST_NOEXCEPT
{
if (imp.handle != NULL)
{
::FindClose(imp.handle);
if (BOOST_LIKELY(imp.close_handle))
::FindClose(imp.handle);
imp.handle = NULL;
}

Expand Down Expand Up @@ -1020,6 +1038,8 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
return error_code(error, system_category());
}

pimpl->close_handle = true;

first_filename = data.cFileName;
set_file_statuses(data.dwFileAttributes, NULL, first_filename, sf, symlink_sf);

Expand Down
40 changes: 40 additions & 0 deletions src/error_handling.hpp
Expand Up @@ -88,6 +88,33 @@ typedef boost::winapi::DWORD_ err_t;
#if !defined(STATUS_ACCESS_DENIED)
#define STATUS_ACCESS_DENIED ((boost::winapi::NTSTATUS_)0xC0000022l)
#endif
#if !defined(STATUS_OBJECT_NAME_NOT_FOUND)
#define STATUS_OBJECT_NAME_NOT_FOUND ((boost::winapi::NTSTATUS_)0xC0000034l)
#endif
#if !defined(STATUS_OBJECT_PATH_NOT_FOUND)
#define STATUS_OBJECT_PATH_NOT_FOUND ((boost::winapi::NTSTATUS_)0xC000003Al)
#endif
#if !defined(STATUS_NOT_SUPPORTED)
#define STATUS_NOT_SUPPORTED ((boost::winapi::NTSTATUS_)0xC00000BBl)
#endif
#if !defined(STATUS_BAD_NETWORK_PATH)
#define STATUS_BAD_NETWORK_PATH ((boost::winapi::NTSTATUS_)0xC00000BEl)
#endif
#if !defined(STATUS_DEVICE_DOES_NOT_EXIST)
#define STATUS_DEVICE_DOES_NOT_EXIST ((boost::winapi::NTSTATUS_)0xC00000C0l)
#endif
#if !defined(STATUS_BAD_NETWORK_NAME)
#define STATUS_BAD_NETWORK_NAME ((boost::winapi::NTSTATUS_)0xC00000CCl)
#endif
#if !defined(STATUS_DIRECTORY_NOT_EMPTY)
#define STATUS_DIRECTORY_NOT_EMPTY ((boost::winapi::NTSTATUS_)0xC0000101l)
#endif
#if !defined(STATUS_NOT_A_DIRECTORY)
#define STATUS_NOT_A_DIRECTORY ((boost::winapi::NTSTATUS_)0xC0000103l)
#endif
#if !defined(STATUS_NOT_FOUND)
#define STATUS_NOT_FOUND ((boost::winapi::NTSTATUS_)0xC0000225l)
#endif

//! Converts NTSTATUS error codes to Win32 error codes for reporting
inline boost::winapi::DWORD_ translate_ntstatus(boost::winapi::NTSTATUS_ status)
Expand All @@ -106,11 +133,24 @@ inline boost::winapi::DWORD_ translate_ntstatus(boost::winapi::NTSTATUS_ status)
case static_cast< boost::winapi::ULONG_ >(STATUS_NO_MORE_FILES):
return boost::winapi::ERROR_NO_MORE_FILES_;
case static_cast< boost::winapi::ULONG_ >(STATUS_NO_SUCH_DEVICE):
case static_cast< boost::winapi::ULONG_ >(STATUS_DEVICE_DOES_NOT_EXIST):
return boost::winapi::ERROR_DEV_NOT_EXIST_;
case static_cast< boost::winapi::ULONG_ >(STATUS_NO_SUCH_FILE):
case static_cast< boost::winapi::ULONG_ >(STATUS_OBJECT_NAME_NOT_FOUND):
case static_cast< boost::winapi::ULONG_ >(STATUS_OBJECT_PATH_NOT_FOUND):
return boost::winapi::ERROR_FILE_NOT_FOUND_;
case static_cast< boost::winapi::ULONG_ >(STATUS_ACCESS_DENIED):
return boost::winapi::ERROR_ACCESS_DENIED_;
case static_cast< boost::winapi::ULONG_ >(STATUS_BAD_NETWORK_PATH):
return boost::winapi::ERROR_BAD_NETPATH_;
case static_cast< boost::winapi::ULONG_ >(STATUS_BAD_NETWORK_NAME):
return boost::winapi::ERROR_BAD_NET_NAME_;
case static_cast< boost::winapi::ULONG_ >(STATUS_DIRECTORY_NOT_EMPTY):
return boost::winapi::ERROR_DIR_NOT_EMPTY_;
case static_cast< boost::winapi::ULONG_ >(STATUS_NOT_A_DIRECTORY):
return boost::winapi::ERROR_DIRECTORY_; // The directory name is invalid
case static_cast< boost::winapi::ULONG_ >(STATUS_NOT_FOUND):
return boost::winapi::ERROR_NOT_FOUND_;
// map "invalid info class" to "not supported" as this error likely indicates that the kernel does not support what we request
case static_cast< boost::winapi::ULONG_ >(STATUS_INVALID_INFO_CLASS):
default:
Expand Down

0 comments on commit ea22e76

Please sign in to comment.