[PATCH v3] libstdc++: Fix SIGSEGV in std::print with a setvbuf'd FILE
Jonathan Wakely
jwakely@redhat.com
Wed Aug 19 14:17:31 GMT 2026
On Mon, 17 Aug 2026 at 09:58 +0200, Tomasz Kamiński wrote:
>From: Anlai Lu <agicy@qq.com>
>
>_File_sink::_M_write_buf() returns a span with a null data pointer when
>the FILE buffer was pre-allocated by setvbuf but never written (glibc
>keeps _IO_write_ptr as nullptr until the first write). The empty() check
>in the constructor then fails to force buffer initialization and the format
>engine memcpys into nullptr.
Please replace the paragraph above with the first two paragraphs
from the original [PATCH 0/1] email, and a shorter form of the third
paragraph:
This is a regression introduced by the P3107R5 implementation in
r16-4350-g8bd872f1ea7414. Before that formatting to a FILE* wrote to
_Str_sink and used fwrite, which handles this legal stream state.
>Calls __overflow when _IO_write_ptr is null in _File_sink constructor.
Let's say:
This fixes it by calling __overflow from the _File_sink constructor
when _IO_write_ptr is null.
>
>libstdc++-v3/ChangeLog:
>
> * include/bits/print.h (_File_sink::_File::_File)
> [_GLIBCXX_USE_STDIO_LOCKING && _GLIBCXX_USE_GLIBC_STDIO_EXT]:
> Move __overflow call to separte member function.
> (_File_sink::_File::_M_init_write_buf)
> [_GLIBCXX_USE_STDIO_LOCKING && _GLIBCXX_USE_GLIBC_STDIO_EXT]:
> Extracted from constructor, call __overflow for null
> _M_file->_IO_write_ptr.
> (_File_sink::_File_sink)
> [_GLIBCXX_USE_STDIO_LOCKING && _GLIBCXX_USE_GLIBC_STDIO_EXT]:
> Call _M_init_write_buf instead of _M_writebuf.
> * testsuite/27_io/print/1.cc: Add test_print_setvbuf.
>
>Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
>Signed-off-by: Anlai Lu <agicy@qq.com>
>Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
>---
>v3 introduced _M_init_write_buf that I suggested on review.
>
>Tested on x86_64-linux. OK for trunk and 16?
>
> libstdc++-v3/include/bits/print.h | 28 ++++++++++++++++---------
> libstdc++-v3/testsuite/27_io/print/1.cc | 17 +++++++++++++++
> 2 files changed, 35 insertions(+), 10 deletions(-)
>
>diff --git a/libstdc++-v3/include/bits/print.h b/libstdc++-v3/include/bits/print.h
>index 20724844726..d63267c8ecf 100644
>--- a/libstdc++-v3/include/bits/print.h
>+++ b/libstdc++-v3/include/bits/print.h
>@@ -80,20 +80,28 @@ namespace __format
> ::funlockfile(__f);
> __throw_system_error(EACCES);
> }
>- // Allocate buffer if needed:
>- if (_M_write_buf().empty())
>- if (::__overflow(__f, EOF) == EOF)
>- {
>- const int __err = errno;
>- ::funlockfile(__f);
>- __throw_system_error(__err);
>- }
> }
>
> ~_File() { ::funlockfile(_M_file); }
>
> _File(_File&&) = delete;
>
>+ // Allocate FILE's output buffer if needed, and returns span
s/returns span/return a span/
>+ // viewing unused portion of it.
>+ std::span<char>
>+ _M_init_write_buf()
>+ {
>+ // After setvbuf glibc pre-allocates the buffer but _IO_write_ptr
>+ // remains null until the first write.
The two comment lines above are indented inconsistently, the first one
uses spaces and the second uses a tab.
>+ if (!_M_file->_IO_write_ptr || _M_write_buf().empty())
>+ if (::__overflow(_M_file, EOF) == EOF)
>+ {
>+ const int __err = errno;
>+ __throw_system_error(__err);
There's no need to store errno before throwing (because we're not
calling funlockfile here) so it can be just:
__throw_system_error(errno);
OK with those changes, thanks.
>+ }
>+ return _M_write_buf();
>+ }
>+
> // A span viewing the unused portion of the stream's output buffer.
> std::span<char>
> _M_write_buf() noexcept
>@@ -158,8 +166,8 @@ namespace __format
> : _M_file(__f), _M_add_newline(__add_newline)
> {
> if (!_M_file._M_unbuffered())
>- // Write directly to the FILE's output buffer.
>- this->_M_reset(_M_file._M_write_buf());
>+ // Allocate FILE's output buffer if needed, and write directly to it.
>+ this->_M_reset(_M_file._M_init_write_buf());
> }
>
> // This calls I/O functions which are cancellation points, so they
>diff --git a/libstdc++-v3/testsuite/27_io/print/1.cc b/libstdc++-v3/testsuite/27_io/print/1.cc
>index 4fd7f5dc925..641a97c1f9c 100644
>--- a/libstdc++-v3/testsuite/27_io/print/1.cc
>+++ b/libstdc++-v3/testsuite/27_io/print/1.cc
>@@ -67,6 +67,22 @@ test_print_raw()
> VERIFY( txt == "\xa3" );
> }
>
>+void
>+test_print_setvbuf()
>+{
>+ __gnu_test::scoped_file f;
>+ FILE* strm = std::fopen(f.path.string().c_str(), "w");
>+ VERIFY( strm );
>+ VERIFY( std::setvbuf(strm, nullptr, _IOFBF, 4096) == 0 );
>+ std::string str{"Hello, World!"};
>+ std::print(strm, "{}", str);
>+ std::fclose(strm);
>+
>+ std::ifstream in(f.path);
>+ std::string txt(std::istreambuf_iterator<char>(in), {});
>+ VERIFY( txt == "Hello, World!" );
>+}
>+
> void
> test_vprint_nonunicode()
> {
>@@ -142,6 +158,7 @@ int main()
> test_print_file();
> test_println_file();
> test_print_raw();
>+ test_print_setvbuf();
> test_vprint_nonunicode();
> #ifdef __cpp_exceptions
> test_errors();
>--
>2.55.0
>
>
More information about the Libstdc++
mailing list