Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valgrind memcheck: still reachable bytes in Singleton #1145

Open
chazzmcdaniels opened this issue Aug 19, 2022 · 2 comments
Open

Valgrind memcheck: still reachable bytes in Singleton #1145

chazzmcdaniels opened this issue Aug 19, 2022 · 2 comments

Comments

@chazzmcdaniels
Copy link

The new in a Singleton has no complementary delete which causes still reachable bytes in memcheck. (misc.h line 258 and 346)

Code snippet to reproduce (FixedMaxPlaintextLength is the trigger here):

CryptoPP::RSA::PublicKey publicKey{};
....
CryptoPP::RSAES<CryptoPP::OAEP<CryptoPP::SHA256>>::Encryptor encryptor(publicKey);
if (mSessionData.size() > encryptor.FixedMaxPlaintextLength())
...

Environment: Ubuntu 20.04, Crypto++ 8.7.0, gcc 11.1.0, Valgrind 3.15.0
Command line: valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all ..........
Expected output: No leaks at all
Current output:

==2901338== Memcheck, a memory error detector
==2901338== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2901338== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==2901338== Command: ..........
==2901338== 
....

OK (4)
==2901338== 
==2901338== HEAP SUMMARY:
==2901338==     in use at exit: 8 bytes in 1 blocks
==2901338==   total heap usage: 2,491,956 allocs, 2,491,955 frees, 96,789,772 bytes allocated
==2901338== 
==2901338== 8 bytes in 1 blocks are still reachable in loss record 1 of 1
==2901338==    at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2901338==    by 0x1E27CC: CryptoPP::NewObject<CryptoPP::OAEP<CryptoPP::SHA256, CryptoPP::P1363_MGF1> >::operator()() const (misc.h:258)
==2901338==    by 0x1E241D: CryptoPP::Singleton<CryptoPP::OAEP<CryptoPP::SHA256, CryptoPP::P1363_MGF1>, CryptoPP::NewObject<CryptoPP::OAEP<CryptoPP::SHA256, CryptoPP::P1363_MGF1> >, 0>::Ref() const (misc.h:346)
==2901338==    by 0x1E209C: CryptoPP::TF_ObjectImplBase<CryptoPP::TF_EncryptorBase, CryptoPP::TF_CryptoSchemeOptions<CryptoPP::TF_ES<CryptoPP::RSA, CryptoPP::OAEP<CryptoPP::SHA256, CryptoPP::P1363_MGF1>, int>, CryptoPP::RSA, CryptoPP::OAEP<CryptoPP::SHA256, CryptoPP::P1363_MGF1> >, CryptoPP::RSAFunction>::GetMessageEncodingInterface() const (pubkey.h:594)
==2901338==    by 0x1E05D8: CryptoPP::TF_CryptoSystemBase<CryptoPP::PK_Encryptor, CryptoPP::TF_Base<CryptoPP::RandomizedTrapdoorFunction, CryptoPP::PK_EncryptionMessageEncodingMethod> >::FixedMaxPlaintextLength() const (pubkey.h:273)
==2901338==    ..........
==2901338== 
==2901338== LEAK SUMMARY:
==2901338==    definitely lost: 0 bytes in 0 blocks
==2901338==    indirectly lost: 0 bytes in 0 blocks
==2901338==      possibly lost: 0 bytes in 0 blocks
==2901338==    still reachable: 8 bytes in 1 blocks
==2901338==         suppressed: 0 bytes in 0 blocks
==2901338== 
==2901338== For lists of detected and suppressed errors, rerun with: -s
==2901338== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
@graphitemaster
Copy link

graphitemaster commented Oct 15, 2023

Yep, the problem is the Singleton<T> implementation in
https://github.com/weidai11/cryptopp/blob/master/misc.h#L332-L352

Does something like:

static std::atomic<T*> s_pObject;
T *newObject = m_objectFactory(); // where m_objectFactory is `new T`
s_pObject.store(newObject);

But the heap allocated object stored in s_pObject is never deleted on exit. This can be fixed by implementing an atomic_unique_ptr like the following

template<typename T>
struct atomic_unique_ptr {
  using pointer = T *;
  std::atomic<pointer> ptr;
  constexpr atomic_unique_ptr() noexcept : ptr() {}
  explicit atomic_unique_ptr(pointer p) noexcept : ptr(p) {}
  atomic_unique_ptr(atomic_unique_ptr&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(atomic_unique_ptr&& p) noexcept { reset(p.release()); return *this; }
  void reset(pointer p = pointer()) { auto old = ptr.exchange(p); if (old) delete old; }
  operator pointer() const { return ptr; }
  pointer operator->() const { return ptr; }
  pointer get() const { return ptr; }
  explicit operator bool() const { return ptr != pointer(); }
  pointer release() { return ptr.exchange(pointer()); }
  ~atomic_unique_ptr() { reset(); }
};

Then replacing the static std::atomic<T*> s_pObject with an atomic_unique_ptr<T> as a drop-in replacement, fixing the memory leaks.

@noloader
Copy link
Collaborator

noloader commented Oct 15, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants