boostorg / gil

Boost.GIL - Generic Image Library | Requires C++14 since Boost 1.80
https://boostorg.github.io/gil
Boost Software License 1.0
178 stars 164 forks source link

heap-buffer-overflow when using io-related functions #733

Closed Osyotr closed 3 months ago

Osyotr commented 1 year ago

Actual behavior

Writing images and views to files does not work because string conversion functions are not implemented properly: https://github.com/boostorg/gil/blob/eabd679f632be3170d98145fcc3b49e85df7cc4b/include/boost/gil/io/path_spec.hpp#L90-L97 The code above does not work for paths that contain non-ascii symbols. The string it produces does not have \0 at the end.

Expected behavior

Passing std::filesystem::path or std::wstring to io-related functions should work properly. Possible solution is to remove explicit string conversions, construct std::filesystem::path and use its .string() method. Note that it may break on windows because of usage of fopen https://github.com/boostorg/gil/blob/eabd679f632be3170d98145fcc3b49e85df7cc4b/include/boost/gil/io/device.hpp#L105 (_wfopen should be used on windows)

C++ Minimal Working Example

I've extracted broken part (linked above) to reproduce the issue: https://godbolt.org/z/rvxsPG7a4

#include <boost/gil.hpp>
#include <filesystem>
namespace gil = boost::gil;
int main
{
    std::filesystem::path path = L"/some_path/傳/привет/qwerty";
    gil::bgra8_image_t image;
    gil::write_view(path, gil::const_view(image));
}

Environment

striezel commented 4 months ago

Yes, this is a bug.

The length calculation is wrong. The code uses wcslen() to "calculate" the length of the converted string, however wcslen() is just a strlen() for wchar_t* strings. So it returns the number of wide characters in a wchar_t* string, but those do not need to be the same as the number of narrow characters (that is: char) in the converted string. It just happens to be the same, if the wide character string only uses characters of the English alphabet where each wide character is converted to exactly one narrow character.

Instead of wcslen() the code could use wcstombs()'s POSIX extension to calculate the length before doing the conversion.

POSIX specifies a common extension: if dst is a null pointer, this function returns the number of bytes that would be written to dst, if converted.

But that is an extension, so we should probably use wcsrtombs() instead, where that behaviour is not an extension but part of the function and we do not need to rely on the presence of an extension.

Long story short: I'll try to prepare a fix.