Skip to content

Memory leak in stream operators for posix_time::time_duration #249

@belyaev-ms

Description

@belyaev-ms

Memory leak in stream operators for posix_time::time_duration

The << and >> operators for boost::posix_time::time_duration may cause memory leaks when an exception is thrown during memory allocation within these operators.

Steps to reproduce:

The following example simulates a memory allocation failure during the stream output operation for a time_duration variable. Custom new/delete operators are overloaded to force an exception on the 8th allocation call. The example should be built with AddressSanitizer (ASAN) to confirm the leak.

#include <vector>
#include <string>
#include <iostream>
#include <stdint.h>
#include <atomic>
#include <boost/date_time/posix_time/posix_time.hpp>

static std::atomic<int> fault_counter(-1);

void* operator new(std::size_t size)
{
    if (0 == size)
    {
        ++size;
    }

    if (fault_counter == -1 || fault_counter++ != 8)
    {
        void* ptr = malloc(size);
        if (ptr)
        {
            printf("[+] %p size=%lu\n", ptr, size);
            return ptr;
        }
    }
    printf("[!] FAULT size=%lu\n", size);
    throw std::bad_alloc{};
}

void operator delete(void* ptr) noexcept
{
    printf("[-] %p\n", ptr);
    std::free(ptr);
}

void operator delete(void* ptr, std::size_t ) noexcept
{
    printf("[-] %p\n", ptr);
    std::free(ptr);
}

int main(int , char** )
{
    try
    {
        puts("---------------------------------------");
        fault_counter = 0;
        boost::posix_time::time_duration td = boost::posix_time::seconds(60) + boost::posix_time::microsec(1000);
        std::ostringstream ss;
        ss << td;
        puts("---------------------------------------");
    }
    catch (...)
    {

    }
    return 0;
}

Live demonstration:
https://godbolt.org/z/zWo4Mdeac

Result (ASAN report):

=================================================================
==1==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 424 byte(s) in 1 object(s) allocated from:
    #0 0x7db40edd5c2b in malloc (/opt/compiler-explorer/gcc-15.2.0/lib64/libasan.so.8+0x121c2b)
    #1 0x0000004056ed in operator new(unsigned long) /app/example.cpp:19
    #2 0x00000040c062 in std::basic_ostream<char, std::char_traits<char> >& boost::posix_time::operator<< <char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, boost::posix_time::time_duration const&) /app/boost/include/boost/date_time/posix_time/posix_time_io.hpp:190
    #3 0x000000405ab9 in main /app/example.cpp:50
    #4 0x7db40e029d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)

... (additional indirect leaks reported) ...

SUMMARY: AddressSanitizer: 831 byte(s) leaked in 4 allocation(s).

Proposed fix:

The issue is in include/boost/date_time/posix_time/posix_time_io.hpp. The dynamically allocated facet objects are not deleted if an exception occurs during locale creation. The fix is to wrap the allocations in try-catch blocks and ensure proper cleanup.

diff --git a/include/boost/date_time/posix_time/posix_time_io.hpp b/include/boost/date_time/posix_time/posix_time_io.hpp
index 6a72e32..7da07c3 100644
--- a/include/boost/date_time/posix_time/posix_time_io.hpp
+++ b/include/boost/date_time/posix_time/posix_time_io.hpp
@@ -187,10 +187,20 @@ namespace posix_time {
       //since we would always need to reconstruct for every time period
       //if the locale did not already exist.  Of course this will be overridden
       //if the user imbues as some later point.
-      custom_ptime_facet* f = new custom_ptime_facet();
-      std::locale l = std::locale(os.getloc(), f);
-      os.imbue(l);
-      f->put(oitr, os, os.fill(), td);
+      custom_ptime_facet* f = nullptr;
+      try {
+        custom_ptime_facet* f = new custom_ptime_facet();
+        f_ = f;
+        std::locale l = std::locale(os.getloc(), f);
+        f_ = nullptr;
+        os.imbue(l);
+        f->put(oitr, os, os.fill(), td);
+      } catch(...) {
+        if(f_) {
+          delete f_;
+        }
+        throw;
+      }
     }
     return os;
   }
@@ -211,10 +221,20 @@ namespace posix_time {
           std::use_facet<time_input_facet_local>(is.getloc()).get(sit, str_end, is, td);
         }
         else {
-          time_input_facet_local* f = new time_input_facet_local();
-          std::locale l = std::locale(is.getloc(), f);
-          is.imbue(l);
-          f->get(sit, str_end, is, td);
+          time_input_facet_local* f_ = nullptr;
+          try {
+            time_input_facet_local* f = new time_input_facet_local();
+            f_ = f;
+            std::locale l = std::locale(is.getloc(), f);
+            f_ = nullptr;
+            is.imbue(l);
+            f->get(sit, str_end, is, td);
+          } catch(...) {
+            if(f_) {
+              delete f_;
+            }
+            throw;
+          }
         }
       }
       catch(...) {

This change ensures that if an exception is thrown during the creation of the locale, the dynamically allocated facet is properly deleted before rethrowing the exception.

posix_time_io.patch

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions