Memory leak in stream operators for `posix_time::time_duration`

Open
#249 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
cpp
Domain
backend

Research direction

Start in include/boost/date_time/posix_time/posix_time_io.hpp at the posix_time stream insertion and extraction operators, then inspect how their dynamically allocated locale facets are owned. Run the supplied allocation-failure reproducer under ASAN and verify that both operators complete without leaked allocations when locale creation throws.

Written by the indexing model from the issue text.

Description

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

Dominant language
C++
Stars
70
Forks
99
PR merge metrics
No merged PRs in 30d

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from boostorg/date_time

All issues in boostorg/date_time

Similar issues

More C++ issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.