[PATCH v2 1/1] libstdc++: Fix SIGSEGV in std::print with a setvbuf'd FILE

Anlai Lu <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel
Message-ID <[email protected]>
_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.

Return an empty span when the write pointer is null.

libstdc++-v3/ChangeLog:

	* include/bits/print.h (_File_sink::_File::_M_write_buf): Return
	an empty span when _IO_write_ptr is null.
	* testsuite/27_io/print/1.cc: Add test_print_setvbuf.

Signed-off-by: Anlai Lu <[email protected]>
---
Changes in v2: removed [[unlikely]]; VERIFY the setvbuf return value in the test.

 libstdc++-v3/include/bits/print.h       |  5 +++++
 libstdc++-v3/testsuite/27_io/print/1.cc | 17 +++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/libstdc++-v3/include/bits/print.h b/libstdc++-v3/include/bits/print.h
index 20724844726..7e023c79c55 100644
--- a/libstdc++-v3/include/bits/print.h
+++ b/libstdc++-v3/include/bits/print.h
@@ -95,9 +95,14 @@ namespace __format
       _File(_File&&) = delete;
 
       // A span viewing the unused portion of the stream's output buffer.
+      // Empty when the buffer is not yet initialized: after setvbuf
+      // glibc pre-allocates the buffer but keeps _IO_write_ptr as
+      // nullptr until the first write.
       std::span<char>
       _M_write_buf() noexcept
       {
+	if (_M_file->_IO_write_ptr == nullptr)
+	  return {};
 	return {_M_file->_IO_write_ptr,
 		size_t(_M_file->_IO_buf_end - _M_file->_IO_write_ptr)};
       }
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.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.