简体   繁体   English

将 shared_ptr 用于 private_key 时出现分段错误

[英]Segmentation fault when using a shared_ptr for private_key

Updates更新

[X] I discovered this happen when TLS::credentials creds is declared on global scope but if I declare it outside seg fault won't happen. [X] 我发现在全局 scope 上声明TLS::credentials creds时会发生这种情况,但如果我在外部声明它,则不会发生 seg 错误。

I need it to be global because it helps with caching certificates and that multiple threads can use certificates created by other threads without spending time on creating new certificates.我需要它是全局的,因为它有助于缓存证书,并且多个线程可以使用其他线程创建的证书,而无需花费时间创建新证书。

[X] I further reduced code from 200 lines approx. [X] 我进一步减少了大约 200 行的代码。 to 100 lines到 100 行

I'm using Botan to create a TLS application and my application crash with a seg fault at end of the application.我正在使用 Botan 创建一个 TLS 应用程序,但我的应用程序在应用程序结束时因 seg 错误而崩溃。

I made an attempt to debug this with Valgrind but it leading nowhere.我试图用 Valgrind 调试它,但它无处可去。

Here is the stack trace from 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)

You can clone the Botan into your machine by issuing,您可以通过发出将 Botan 克隆到您的机器中,

git clone https://github.com/randombit/botan.git

Then follow instructions from their official website to build & install it.然后按照他们官方网站上的说明进行构建和安装。

You will need to create a Root Certificate Authority to use with the application and for that you must install OpenSSL on your machine.您需要创建一个根证书颁发机构以与应用程序一起使用,为此您必须在您的机器上安装 OpenSSL。

Create a folder called testApplication and cd into it.创建一个名为testApplication的文件夹并将cd放入其中。

Then using Bash, issue the following series of commands to create a Root CA,然后使用 Bash,发出以下一系列命令来创建根 CA,

# Generate private key
openssl genrsa -des3 -out myCA.key 2048
# Generate root certificate
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 825 -out myCA.pem
# Convert to Botan Format
openssl pkcs8 -topk8 -in myCA.key > myCAKey.pkcs8.pem

Please use thisispassword as password.请使用thisispassword作为密码。

Install clang compiler on your machine and then you can compile the source file as follows,在你的机器上安装 clang 编译器,然后你可以编译源文件如下,

clang++ example.cpp -o example  -Wthread-safety -Wall -Wextra -g -std=c++17 -pthread -lssl -lcrypto -lbotan-2 --I/usr/include/botan-2

example.cpp例子.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;
}

Here is the function from the Botan Lib that Valgrind refers to,这是 Valgrind 所指的 Botan Lib 中的 function,

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);
   }

Author of Botan replied to me that Botan的作者回复我说

The problem is the globally defined object.问题是全局定义的 object。

The problem is that the mlock pool is a singleton created on first use then destroyed sometime after main returns.问题是 mlock 池是一个 singleton 在第一次使用时创建,然后在主返回后的某个时间销毁。 First your object is created.首先创建您的 object。 It allocates memory.它分配 memory。 This results in the pool being created.这将导致创建池。 Destruction happens LIFO.破坏发生后进先出。 So first the pool is destroyed.因此,首先销毁池。 Then your object is destroyed, and attempts to touch memory (to zero it) which has already been unmapped.然后您的 object 被破坏,并尝试触摸已经未映射的 memory(将其归零)。

Workarounds,解决方法,

  • Create a Botan::Allocator_Initializer object to force initialization before your object is created (thus the pool lives until after your object has been destructed)创建 Botan::Allocator_Initializer object 以在创建 object 之前强制初始化(因此池一直存在到 object 被破坏之后)
  • Disable locking_allocator module禁用locking_allocator模块
  • Set env var BOTAN_MLOCK_POOL_SIZE to 0将环境变量 BOTAN_MLOCK_POOL_SIZE 设置为 0
  • No global vars没有全局变量

In principle the locking allocator instead of munmaping the memory, just zeros it, and leave it to be unmapped by the OS on process exit.原则上,锁定分配器而不是 munmaping memory,只是将它归零,并让它在进程退出时由操作系统取消映射。 This might still break invariants, but not as badly.这可能仍然会破坏不变量,但不会那么严重。 It also causes valgrind to reports leaks which is obnoxious.它还导致 valgrind 报告令人讨厌的泄漏。

I think because it was mmap'ed directly and not through malloc, valgrind doesn't track it.我认为因为它是直接映射而不是通过 malloc,所以 valgrind 不会跟踪它。

Global variables, and especially singletons, are the scourge of multithreaded, complex applications.全局变量,尤其是单例,是多线程、复杂应用程序的祸害。 You'll be always running into such problems with this sort of design.这种设计总是会遇到这样的问题。

Here's what I usually do: everything global gets defined as a local variable in main or some sub-function, in proper order, so that it gets destroyed in an appropriate reverse order.这就是我通常做的事情:所有全局都被定义main函数或某些子函数中的局部变量,以适当的顺序,以便它以适当的相反顺序被销毁。 Dependency-injection-like techniques can be used to pass those objects around in cases where "almost everything" depends on them.在“几乎所有东西”都依赖于它们的情况下,可以使用类似依赖注入的技术来传递这些对象。 It took me some pain to realize that this was essentially the only way that was debuggable in large, complex applications (think 2M loc between the app itself and the dozens of libraries it uses outside of the C++ library).我有些痛苦地意识到这实际上是在大型复杂应用程序中可调试的唯一方法(想想应用程序本身和它在 C++ 库之外使用的几十个库之间的 2M loc)。 After the globals got eviscerated from the bespoke code, and then from a few problematic libraries, the specter of "death on closure" pretty much vanished.在全局变量从定制代码中剔除后,然后从一些有问题的库中剔除后,“关闭时死亡”的幽灵几乎消失了。 I don't guarantee that it'll fix everyone's problems - since people can be quite creative at coming up with new ones - but it's IMHO a step in the right direction.我不保证它会解决每个人的问题——因为人们可以很有创意地提出新问题——但恕我直言,这是朝着正确方向迈出的一步。

This is an example of static de-initialization order 'fiasco'.这是 static 去初始化命令“惨败”的示例。

There are technics to prevent this, but as you link libraries they might not work as you can't control their life time.有一些技术可以防止这种情况发生,但是当您链接库时,它们可能无法工作,因为您无法控制它们的生命周期。

So the best solution might be to explicit clear the content of globalCreds before the program exits, at end of main or in an atexit function.因此,最好的解决方案可能是在程序退出之前、main 结束时或 atexit function 中显式清除globalCreds的内容。 Next best is to leak the structure if there is no need to clean up.如果不需要清理,下一个最好的方法是泄漏结构。

How to leak example taken from isocpp如何泄漏取自isocpp的示例

TLS::credentials& x() {
  static TLS::credentials* creds = new TLS::credentials();
  return *creds;
}
TLS::credentials &globalCreds = x();

Yes that offends my feeling of neatness too.是的,这也冒犯了我的整洁感。

But this is just work arounds, globalCreds should be created in main , passed to the classes (that are also created in main, after Creds) that needs it as a reference.但这只是变通方法,应该在main中创建globalCreds ,并将其传递给需要它作为参考的类(也在 main 中创建,在 Creds 之后)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM