<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/57964>57964</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
libc++ 15.0.1 segfaults in `std::__1::ios_base::~ios_base()` when object was not yet properly constructed yet
</td>
</tr>
<tr>
<th>Labels</th>
<td>
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
marv
</td>
</tr>
</table>
<pre>
When building a project what works perfectly fine with GCC/libstdc++ with Clang/libc++ I'm getting a segfault in `std::__1::ios_base::~ios_base()`. I've boiled the code that causes the crash down to this reproducer:
```
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <iostream>
#include <istream>
#include <sys/stat.h>
#include <sys/types.h>
#include <system_error>
#include <unistd.h>
namespace fs = std::filesystem;
class SafeIFStreamBuf : public std::streambuf {
public:
SafeIFStreamBuf(const int fd);
int fd;
protected:
static const int lookbehind_size = 16;
static const int buffer_size = 512 + lookbehind_size;
char buffer[buffer_size];
};
class SafeIFStreamBase {
protected:
SafeIFStreamBuf buf;
public:
SafeIFStreamBase(const int fd);
};
class SafeIFStream : protected SafeIFStreamBase, public std::istream {
private:
const bool _close;
public:
explicit SafeIFStream(const int fd);
explicit SafeIFStream(const fs::path &);
~SafeIFStream() override;
};
SafeIFStreamBuf::SafeIFStreamBuf(const int f) : fd(f) {
int n_read =
read(fd, buffer + lookbehind_size, buffer_size - lookbehind_size);
if (-1 == n_read)
throw std::system_error(errno, std::generic_category(),
"Error reading from fd " + std::to_string(fd));
}
SafeIFStreamBase::SafeIFStreamBase(const int f) : buf(f) {}
SafeIFStream::SafeIFStream(const int f)
: SafeIFStreamBase(f), std::istream(&buf), _close(false) {}
namespace {
int open_path(const fs::path &e) {
int result = open(e.string().c_str(), O_RDONLY | O_CLOEXEC);
if (-1 == result)
throw std::system_error(errno, std::generic_category(),
"Could not open '" + e.string() + "'");
return result;
}
} // namespace
SafeIFStream::SafeIFStream(const fs::path &e)
: SafeIFStreamBase(open_path(e)), std::istream(&buf), _close(true) {}
SafeIFStream::~SafeIFStream() {
if (_close)
::close(buf.fd);
}
int main() {
fs::path test_dir = fs::current_path() / "test_directory";
fs::create_directory(test_dir);
try {
SafeIFStream ifs(test_dir);
std::string t;
ifs >> t;
} catch (const std::system_error &ex) {
std::cout << "expected exception thrown" << std::endl;
return 0;
}
std::cout << "expected exception not thrown" << std::endl;
return 1;
}
```
Building with Clang/libc++ 15.0.1 vs. GCC/libstdc++ 12.2.0:
```
$ clang++-15 -std=c++17 -stdlib=libc++ -o reproducer reproducer.cc
$ ./reproducer
Segmentation fault (core dumped)
$ g++-12 -std=c++17 -o reproducer_gcc reproducer.cc
$ ./reproducer_gcc
expected exception thrown
```
The important part is the inheritance from `std::istream` and the exception that's thrown during the construction of the `SafeIFStreamBuf` class. In this situation `ios_base`'s member variables are not properly initialized and `~ios_base` tries to emit its callbacks. But since `__event_size_` contains garbage it segfaults trying to access `__fn_[i]`
The following patch fixes the crash:
```
diff --git a/libcxx/include/ios b/libcxx/include/ios
index 7140e00b406a..b1a796418ba3 100644
--- a/libcxx/include/ios
+++ b/libcxx/include/ios
@@ -350,8 +350,11 @@ public:
protected:
_LIBCPP_INLINE_VISIBILITY
- ios_base() {// purposefully does no initialization
- }
+ ios_base() :
+ __event_size_{0}
+ {
+ // purposefully does no initialization
+ }
void init(void* __sb);
_LIBCPP_INLINE_VISIBILITY void* rdbuf() const {return __rdbuf_;}
@@ -381,12 +384,12 @@ private:
streamsize __width_;
iostate __rdstate_;
iostate __exceptions_;
- void* __rdbuf_;
- void* __loc_;
- event_callback* __fn_;
- int* __index_;
- size_t __event_size_;
- size_t __event_cap_;
+ void* __rdbuf_ = nullptr;
+ void* __loc_ = nullptr;
+ event_callback* __fn_ = nullptr;
+ int* __index_ = nullptr;
+ size_t __event_size_ = 0;
+ size_t __event_cap_ = 0;
// TODO(EricWF): Enable this for both Clang and GCC. Currently it is only
// enabled with clang.
#if defined(_LIBCPP_HAS_C_ATOMIC_IMP) && !defined(_LIBCPP_HAS_NO_THREADS)
@@ -394,12 +397,12 @@ private:
#else
static int __xindex_;
#endif
- long* __iarray_;
- size_t __iarray_size_;
- size_t __iarray_cap_;
- void** __parray_;
- size_t __parray_size_;
- size_t __parray_cap_;
+ long* __iarray_ = nullptr;
+ size_t __iarray_size_ = 0;
+ size_t __iarray_cap_ = 0;
+ void** __parray_ = nullptr;
+ size_t __parray_size_ = 0;
+ size_t __parray_cap_ = 0;
};
//enum class io_errc
diff --git a/libcxx/src/ios.cpp b/libcxx/src/ios.cpp
index 218b27f1a6b5..40ddbe53052d 100644
--- a/libcxx/src/ios.cpp
+++ b/libcxx/src/ios.cpp
@@ -233,8 +233,10 @@ ios_base::register_callback(event_callback fn, int index)
ios_base::~ios_base()
{
__call_callbacks(erase_event);
- locale& loc_storage = *reinterpret_cast<locale*>(&__loc_);
- loc_storage.~locale();
+ if (__loc_ != nullptr) {
+ locale& loc_storage = *reinterpret_cast<locale*>(&__loc_);
+ loc_storage.~locale();
+ }
free(__fn_);
free(__index_);
free(__iarray_);
```
The first hunk is a left-over, because I started with an initializer list but noticed that more variables than `__event_size_` needed to be initialized and switched to default-member-initialization. This is more of an FYI/RFC instead of a real attempt to fix it, because I'm not familiar with the code at all.
In libstdc++'s code the `ios_base` constructor mentions the need for proper initialization of the member variables:
```
ios_base::ios_base() throw()
: _M_precision(), _M_width(), _M_flags(), _M_exception(),
_M_streambuf_state(), _M_callbacks(0), _M_word_zero(),
_M_word_size(_S_local_word_size), _M_word(_M_local_word), _M_ios_locale()
{
// Do nothing: basic_ios::init() does it.
// NB: _M_callbacks and _M_word must be zero for non-initialized
// ios_base to go through ~ios_base gracefully.
}
```
Best regards,
Marvin
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzFGltvozz216QvVhCQ-0MfmqTdL9LMdDSt9tt5QgZM4h2CkW162V-_59hATAJpZ6TVVqhN7XOOz_1iEov0_fbvAytIXPE85cWeUFJK8W-WaPJ6oPBLyF-KlExmsJS_k4wXjLxyfSD_2GxG4UPOY6XTZBSu4bEbm5wWe7vVrO9G4eJI9kxre4Ri-4xWuSa8IKO5DxRGkzt4oiiwH7hQUUwVs_-NFvftQrgchSvA8QzRF0ZiwXOWEn1gJBEpgw_AdkIrxZRdlFQdSCpeC6IFrHBFJAMZ0yphEon725F_BxTrx_4bTniR5BXQG002ACgLMZrc924qLUGqgd0sKXTuHYZ2gXX1rjQ7DgCA2FoyOrh9dRcogxmUpnqQAQui30umrsEAgxHoQMgBkKoATlKXgvld0CNTJU0YyRSAbUlraFfwtYuS5FQp8kQztnt4MsKtqwxw70hZxTlPTiSs6DHuLmoSFqS1KTmnA86TiEKh22mSpehI3dNJs9NdBm_R4P4sdUijWoGdE71ciF8xO_AijRT_DzPyBvOWUg8G8J4xeYKeBSHBaDmj5JBIDlTWaKPZ2sEfzbYnnhfbj5UKoeTorUe-cxPAYedauaptG6uD6v4Ej9bqDWs91DcXPlHHgysZf6GaOUxajmIhchIluVDsI6nYWwkLXHcYuCLaRxiZsryWFHLlKJx3cSHVnWHBPhEvEH08ZVf0d-7q5oyr_o-EUcXI_tL-t1i7cVBEgJeiazar-INriJCiAawL9nptu2sdfHwJ4MrNIYzD5Tgwp0Es2LMRpj1aH6R4deLfTUvh0qZoOLQF2LOCSZ5ECTjAXsj3unSEG1ea_p9RGN4jXSMslqxMiiMoCjeMsO0hWkR1_rcqWZ17-ZCJ2uJ2PWoaI8XGeq2VhglfEr0geJIfKfecn1k9XcSV0eDcsGL26wACBJrj3z7eTjWg9S7kQ5SsiDAEBuOC9XikZAq7BnQQJIBm91r1A7yXoDVaQ5PH6Mf28duXn0AH_9l8ebz_1_3muufZM_6PnrcRVZ6SQlglwcqi8bqusGYJtixAXzGTTFeyaCS69MrFFvAf4CGtlf7ArfoM96GLufZnddj8jsdpWQ043CXX_TnVdS3jAg3tLvPwNGcCG95lGXOORhc9Ul5cntDRkWZKRymXxo-bnaSSkhW60Ym17wPatwGHMmh8KXSct8UGwTRzoZYNWp9jaPnuctct3aAONYCPoG4DhqlRd3a56fTu4elsoKtBNCToILXb9IaT8Z-3M-U5ZyaiwvDfwIOqgUprmwP2lrBSc1HYWC1MxFiwFpcVad7htQ4Pv8tnV1O_cTJG7KdPr88OelzpbBIxv9fNeDY0YQUzz_cC8qK83sEsCL3Q84ennSlJLE2EHgczMjaMb2v8YGEWgCasOaeOhTNLOR-9JCEn0h6w44DVYcr2R_B3alRnh0HjGpKRtDqW7FT7kUTLWdjDmctEtIejP8eIAbWbw340bJJnmC35sRRSUwj7kkoosHbi5MUB0j8s4-CDjYM74jaJbe4TWtix1T2UQuVZqPp00IQNMTPbQtDIKjFwIjNrQOS8wwOyppOG8biww67iurJahs12jAZZ8JwjO8ZgkhcqOY1hJCMU9I-ODEqCoR8Gfl5wzWkODVtqGEZMZxyH8yAN4KgtCDtCz8u1gkjP85gmv4CLNUSN4qgJAI0i9oI5Dtu_yLAqwAN4ocieypjuQXW6vRtQmKWM9ILQJGEwHRgSWRHB7MNx4rk0SCbyXLwiVmmyTcbf3GuAoQBIeZaR8XgPx9M6rt4gCz3UIy5-EorEg1tN-k_ZG1kEU5_5fjz159Tz4oAuVvNpsIzphAS-P59OLfB4PL5yVuOz6zrQPjp6NPXhIePJDOy6WWJrYD8G0NfYvfPBpv7TN_vBT_Rlt958_x7tvn3ZfbuP_rl72q13X3bPP2vuTa7v3MmYjG27ibKSJZTMrMrBgVIBFijEyZGMMzpk3O6nTYIgc88JrfnstmG041OLtX9Ooy0kDtLv8tmS2naVhz8vgqcGCZjEz6PwDphS8XndHFYpadBkatt8lNUWSWC-rhRRZHYjpNlK2Fh9GaCpTZM4WU7rz7XZLyZgU08xX5i5rFbiK0_1IeoyjJdPgNpaB1kwKx_CtRlNnUDHjbJQUtIh2sh1HS4XyRmUtXyTbaziMT10gKAp61ICIBOpZ3DGgbQL5TrWp0ATWp4ga58Zlti0fwX4XgkjyyewUP5rOAPauIYyrJtrWFc1ZRD9z6Kgxs4wmuB8ftw-QizcwyD194MJpjtyX2CVsnUtg24xFk03ZGoTdD4e2dhGGkuXqciiyN-7lJmhktpeyvQ9XgswgVkgZXjLjZccTcj-dfcUbaK758evu020-_rddudzeOBP0A__7TF6_uvH_d326dTJNPG6mp7idbW4Hq_AE8Pp2o1ec5GI00atzLczhzZIBVQ1x2lzgf1d16U4lZK-f-jdNdinIqGG7YSCG9IdFiIYdz7FQfkbHJQ9HNRueEUHv-fwrkY-6_GOZvpRrmro9_gr_4C_cpA_4l43kk4wsaI62o4TygDOcMn1jkoBgOlbvKQsu21NZ8vtqELonsJFFtB5PPO8qZ-mMZtN_FmYXu-oeggOdFR9kHWshpNJ01HZj4HfhGr3JZVke-jtYaw4ZeBlNyWTrMA7DIxbI9jpquGj112NFTrthKHbElfmJgpQbGZ1u486_AGSYcbCOqK0kNhvo53B3yQDppgsodcAggom900Df4fTvLmFqStwD-GGnAecN3jLzr1DXW7sPUtdycLAdWl35Hc6tf8F213yn2X-1PzhTyYZM7Jgv3He6bWbdVoe3q-z38UVzeDEmXEJbeGhKn5hbaMkZ5ke4-sBc9_OzHtPssMSIXVT4mjhTHGS5BxfgMBcBnMeT8yrU6rJESfv0xwIa0XvyFYwliKOgNMuhkMF5yUHuw1VEce4sR0xx9222iPPWMPhMefCPAvHPfzcQRT-eMAQgUiiqVnHO_icUK3ZsdRIGGY6gt22I695vYxja0aPPAetWsHbd8IgH4SJ5yoTxuPOFYkZh-sXyOxsVD4N39Bz4K0FdrYGDrVhOhE7L58ND82cfj5lD42h5CwRnI0_5lKgkxHM7Wr0NYIISLjCcaW9-oZV09N3VrKc7lVnpe3UL-6oYbN9xRqZBr-D6OYd3zlTyDQCLxN99MymffezjJ4iE23uoksEQb46IKdd1EonUNu7OyfG6lZvK9AtDub9_B0BXfIE8Wv12qENVWuGP669CwLf1rWKW3mNo9dMkmOFscQISmz8oBDF2ImKC3qNSdGR98KYtNofiJPyyV7SxM6k3km088tB4vrymil8N7KnMJ-1Gv9K5QsvbthtMJ9P_dVyufRv0ttJupqs6I3mOme3l_eHp8uXP_9mBnnFL5SI2H6JhCoTmO_MuVNqwwmCBzZuKpnfHrQujV2MnqBvOFSxl4gjlun8pfkzrr-cgtVaqYrhtxdmeMFyc7hNkkW6WITLCZ2vFpTBcuwvp8vFhKZp4i_im5zG0Enfjmbr0Wx7w29DPwz9VTjzV9MgWHkLFiR-4AezNJ2tAn8BJZ4dKc89PNgTcn8jbw0PcQVBNPUxj6rTJrRAfA_5oKFPK30Q8vYIZrgxvN4aRv8LkX9btw">