将shared_ptr用于private_key时出现分段错误
更新
[X] 我发现
TLS::credentials creds在全局范围内声明时会发生这种情况,但是如果我在外部声明它不会发生段错误。我需要它是全局的,因为它有助于缓存证书,并且多个线程可以使用其他线程创建的证书,而无需花时间创建新证书。
[X] 我进一步减少了大约 200 行的代码。到 100 行
我正在使用 Botan 创建一个 TLS 应用程序,我的应用程序在应用程序结束时因段错误而崩溃。
我试图用 Valgrind 调试它,但它无济于事。
这是来自 Valgrind 的堆栈跟踪,
==3841967== Invalid write of size 8
==3841967== at 0x4842964: memset (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3841967== by 0x566A82F: Botan::deallocate_memory(void*, unsigned long, unsigned long) (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x55E1A4D: ??? (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== by 0x40EC29: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr_base.h:730)
==3841967== by 0x41112D: std::__shared_ptr<Botan::RSA_Public_Data const, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr_base.h:1169)
==3841967== by 0x411107: std::shared_ptr<Botan::RSA_Public_Data const>::~shared_ptr() (shared_ptr.h:103)
==3841967== by 0x41109D: Botan::RSA_PublicKey::~RSA_PublicKey() (rsa.h:25)
==3841967== by 0x410FC1: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410DC5: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410E8A: std::_Sp_counted_ptr<Botan::RSA_PrivateKey*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:377)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== Address 0x9419080 is not stack'd, malloc'd or (recently) free'd
==3841967==
==3841967==
==3841967== Process terminating with default action of signal 11 (SIGSEGV)
==3841967== Access not within mapped region at address 0x9419080
==3841967== at 0x4842964: memset (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3841967== by 0x566A82F: Botan::deallocate_memory(void*, unsigned long, unsigned long) (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x55E1A4D: ??? (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== by 0x40EC29: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr_base.h:730)
==3841967== by 0x41112D: std::__shared_ptr<Botan::RSA_Public_Data const, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr_base.h:1169)
==3841967== by 0x411107: std::shared_ptr<Botan::RSA_Public_Data const>::~shared_ptr() (shared_ptr.h:103)
==3841967== by 0x41109D: Botan::RSA_PublicKey::~RSA_PublicKey() (rsa.h:25)
==3841967== by 0x410FC1: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410DC5: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410E8A: std::_Sp_counted_ptr<Botan::RSA_PrivateKey*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:377)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== If you believe this happened as a result of a stack
==3841967== overflow in your program's main thread (unlikely but
==3841967== possible), you can try to increase the size of the
==3841967== main thread stack using the --main-stacksize= flag.
==3841967== The main thread stack size used in this run was 8388608.
==3841967==
==3841967== HEAP SUMMARY:
==3841967== in use at exit: 149,626 bytes in 1,143 blocks
==3841967== total heap usage: 211,782 allocs, 210,639 frees, 90,582,963 bytes allocated
==3841967==
==3841967== LEAK SUMMARY:
==3841967== definitely lost: 0 bytes in 0 blocks
==3841967== indirectly lost: 0 bytes in 0 blocks
==3841967== possibly lost: 1,352 bytes in 18 blocks
==3841967== still reachable: 148,274 bytes in 1,125 blocks
==3841967== of which reachable via heuristic:
==3841967== newarray : 1,536 bytes in 16 blocks
==3841967== suppressed: 0 bytes in 0 blocks
==3841967== Rerun with --leak-check=full to see details of leaked memory
==3841967==
==3841967== For lists of detected and suppressed errors, rerun with: -s
==3841967== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)
==3841967== Invalid write of size 8
==3841967== at 0x4842964: memset (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3841967== by 0x566A82F: Botan::deallocate_memory(void*, unsigned long, unsigned long) (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x55E1A4D: ??? (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== by 0x40EC29: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr_base.h:730)
==3841967== by 0x41112D: std::__shared_ptr<Botan::RSA_Public_Data const, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr_base.h:1169)
==3841967== by 0x411107: std::shared_ptr<Botan::RSA_Public_Data const>::~shared_ptr() (shared_ptr.h:103)
==3841967== by 0x41109D: Botan::RSA_PublicKey::~RSA_PublicKey() (rsa.h:25)
==3841967== by 0x410FC1: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410DC5: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410E8A: std::_Sp_counted_ptr<Botan::RSA_PrivateKey*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:377)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== Address 0x9419080 is not stack'd, malloc'd or (recently) free'd
==3841967==
==3841967==
==3841967== Process terminating with default action of signal 11 (SIGSEGV)
==3841967== Access not within mapped region at address 0x9419080
==3841967== at 0x4842964: memset (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3841967== by 0x566A82F: Botan::deallocate_memory(void*, unsigned long, unsigned long) (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x55E1A4D: ??? (in /usr/lib/x86_64-linux-gnu/libbotan-2.so.12.12.1)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== by 0x40EC29: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr_base.h:730)
==3841967== by 0x41112D: std::__shared_ptr<Botan::RSA_Public_Data const, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr_base.h:1169)
==3841967== by 0x411107: std::shared_ptr<Botan::RSA_Public_Data const>::~shared_ptr() (shared_ptr.h:103)
==3841967== by 0x41109D: Botan::RSA_PublicKey::~RSA_PublicKey() (rsa.h:25)
==3841967== by 0x410FC1: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410DC5: Botan::RSA_PrivateKey::~RSA_PrivateKey() (rsa.h:92)
==3841967== by 0x410E8A: std::_Sp_counted_ptr<Botan::RSA_PrivateKey*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:377)
==3841967== by 0x40EC7B: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==3841967== If you believe this happened as a result of a stack
==3841967== overflow in your program's main thread (unlikely but
==3841967== possible), you can try to increase the size of the
==3841967== main thread stack using the --main-stacksize= flag.
==3841967== The main thread stack size used in this run was 8388608.
==3841967==
==3841967== HEAP SUMMARY:
==3841967== in use at exit: 149,626 bytes in 1,143 blocks
==3841967== total heap usage: 211,782 allocs, 210,639 frees, 90,582,963 bytes allocated
==3841967==
==3841967== LEAK SUMMARY:
==3841967== definitely lost: 0 bytes in 0 blocks
==3841967== indirectly lost: 0 bytes in 0 blocks
==3841967== possibly lost: 1,352 bytes in 18 blocks
==3841967== still reachable: 148,274 bytes in 1,125 blocks
==3841967== of which reachable via heuristic:
==3841967== newarray : 1,536 bytes in 16 blocks
==3841967== suppressed: 0 bytes in 0 blocks
==3841967== Rerun with --leak-check=full to see details of leaked memory
==3841967==
==3841967== For lists of detected and suppressed errors, rerun with: -s
==3841967== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)
您可以通过发出以下命令将 Botan 克隆到您的机器中,
git clone https://github.com/randombit/botan.git
然后按照他们官方网站的说明来构建和安装它。
您需要创建一个根证书颁发机构来与应用程序一起使用,为此您必须在您的机器上安装 OpenSSL。
创建一个名为testApplication并cd进入的文件夹。
然后使用 Bash,发出以下一系列命令来创建一个根 CA,
请thisispassword用作密码。
在你的机器上安装 clang 编译器,然后你可以编译源文件如下,
clang++ example.cpp -o example -Wthread-safety -Wall -Wextra -g -std=c++17 -pthread -lssl -lcrypto -lbotan-2 --I/usr/include/botan-2
例子.cpp
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <botan/tls_server.h>
#include <botan/tls_callbacks.h>
#include <botan/tls_session_manager.h>
#include <botan/tls_policy.h>
#include <botan/auto_rng.h>
#include <botan/certstor.h>
#include <botan/pk_keys.h>
#include <botan/pkcs10.h>
#include <botan/pkcs8.h>
#include <botan/x509self.h>
#include <botan/x509path.h>
#include <botan/x509_ca.h>
#include <botan/x509_ext.h>
#include <botan/pk_algs.h>
#include <botan/ber_dec.h>
#include <botan/der_enc.h>
#include <botan/oids.h>
#include <botan/rsa.h>
namespace TLS
{
typedef std::chrono::duration<int, std::ratio<31556926>> years;
class credentials : public Botan::Credentials_Manager
{
private:
struct certificate
{
std::vector<Botan::X509_Certificate> certs;
std::shared_ptr<Botan::Private_Key> key;
};
std::vector<certificate> creds;
std::vector<std::shared_ptr<Botan::Certificate_Store>> store;
public:
void createCert(std::string hostname)
{
/**
* Initialize Root CA
**/
Botan::AutoSeeded_RNG rng;
const Botan::X509_Certificate rootCert("myCA.pem");
std::ifstream rootCertPrivateKeyFile("myCAKey.pkcs8.pem");
Botan::DataSource_Stream rootCertPrivateKeyStream(rootCertPrivateKeyFile);
std::unique_ptr<Botan::Private_Key> rootCertPrivateKey = Botan::PKCS8::load_key(rootCertPrivateKeyStream, "thisispassword");
Botan::X509_CA rootCA(rootCert, *rootCertPrivateKey, "SHA-256", rng);
/**
* Generate a Cert & Sign with Root CA
**/
Botan::X509_Cert_Options opts;
std::shared_ptr<Botan::Private_Key> serverPrivateKeyShared(new Botan::RSA_PrivateKey(rng, 4096));
Botan::RSA_PrivateKey* serverPrivateKey = (Botan::RSA_PrivateKey*)serverPrivateKeyShared.get();
opts.common_name = hostname;
opts.country = "US";
auto now = std::chrono::system_clock::now();
Botan::X509_Time todayDate(now);
Botan::X509_Time expireDate(now + years(1));
Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *serverPrivateKey, "SHA-256", rng);
auto serverCert = rootCA.sign_request(req, rng, todayDate, expireDate);
/**
* Load Cert to In-Memory Database
**/
certificate cert;
cert.certs.push_back(serverCert);
cert.key = serverPrivateKeyShared;
creds.push_back(cert);
}
};
}; // namespace TLS
TLS::credentials globalCreds;
int main() {
globalCreds.createCert("www.google.com");
std::cout << "End" << "n";
return 0;
}
这是 Valgrind 所指的 Botan Lib 中的函数,
void deallocate_memory(void* p, size_t elems, size_t elem_size)
{
if(p == nullptr)
return;
secure_scrub_memory(p, elems * elem_size);
#if defined(BOTAN_HAS_LOCKING_ALLOCATOR)
if(mlock_allocator::instance().deallocate(p, elems, elem_size))
return;
#endif
std::free(p);
}
回答
Botan的作者回复我说
问题是全局定义的对象。
问题是 mlock 池是第一次使用时创建的单例,然后在 main 返回后的某个时间销毁。首先创建您的对象。它分配内存。这导致池被创建。破坏发生在 LIFO。所以首先池被破坏。然后您的对象被销毁,并尝试触摸已经取消映射的内存(将其归零)。
解决方法,
- 创建一个 Botan::Allocator_Initializer 对象以在创建对象之前强制初始化(因此池一直存在到您的对象被破坏之后)
- 禁用locking_allocator模块
- 将环境变量 BOTAN_MLOCK_POOL_SIZE 设置为 0
- 没有全局变量
原则上,锁定分配器不是对内存进行映射,而是将其归零,并在进程退出时由操作系统取消映射。这可能仍然会破坏不变量,但不会那么严重。它还会导致 valgrind 报告令人讨厌的泄漏。
我认为因为它是直接映射而不是通过 malloc,所以 valgrind 不会跟踪它。
- I think the issue is that you have a global vector holding shared pointers to objects which are therefore destroyed only when the vector is destroyed; but the vector's life time is not linked to the pool. You could clear the vector explicitly before the program exits in order to destroy the objects pointed to.
回答
全局变量,尤其是单例,是多线程、复杂应用程序的祸害。对于这种设计,您将始终遇到此类问题。
这是我通常做的:所有全局变量都被定义为局部变量main或一些子功能,以适当的顺序,以便它以适当的相反顺序被销毁。在“几乎所有东西”都依赖于这些对象的情况下,可以使用类似依赖注入的技术来传递这些对象。我花了一些痛苦才意识到这基本上是在大型复杂应用程序中可调试的唯一方法(想想应用程序本身和它在 C++ 库之外使用的数十个库之间的 2M loc)。在全局变量从定制代码中取出,然后从一些有问题的库中取出后,“关闭时死亡”的幽灵几乎消失了。我不保证它会解决每个人的问题——因为人们在提出新问题时可能很有创意——但恕我直言,这是朝着正确方向迈出的一步。