Shane Bishop
Shane Bishop

Reputation: 4812

How to open and close RPM DB without leaking memory?

I am working on emulating rpm -qa using my own code that uses the librpm library. I am doing this as initial experimentation for a larger program that will analyze installed software for security purposes.

For now, I only open the RPM DB and close it without reading anything.

When I compare the output of valgrind for my code and against the valgrind output for rpm -qa, here are the results:

$ valgrind ./leaky 
==8201== Memcheck, a memory error detector
==8201== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8201== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8201== Command: ./leaky
==8201== 
==8201== 
==8201== HEAP SUMMARY:
==8201==     in use at exit: 104,700 bytes in 2,352 blocks
==8201==   total heap usage: 10,430 allocs, 8,078 frees, 2,292,650 bytes allocated
==8201== 
==8201== LEAK SUMMARY:
==8201==    definitely lost: 0 bytes in 0 blocks
==8201==    indirectly lost: 0 bytes in 0 blocks
==8201==      possibly lost: 25,740 bytes in 325 blocks
==8201==    still reachable: 78,960 bytes in 2,027 blocks
==8201==         suppressed: 0 bytes in 0 blocks
==8201== Rerun with --leak-check=full to see details of leaked memory
==8201== 
==8201== For lists of detected and suppressed errors, rerun with: -s
==8201== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$ valgrind rpm -qa > /dev/null
==8101== Memcheck, a memory error detector
==8101== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8101== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8101== Command: rpm -qa
==8101== 
==8101== 
==8101== HEAP SUMMARY:
==8101==     in use at exit: 287 bytes in 2 blocks
==8101==   total heap usage: 170,103 allocs, 170,101 frees, 120,309,981 bytes allocated
==8101== 
==8101== LEAK SUMMARY:
==8101==    definitely lost: 0 bytes in 0 blocks
==8101==    indirectly lost: 0 bytes in 0 blocks
==8101==      possibly lost: 0 bytes in 0 blocks
==8101==    still reachable: 287 bytes in 2 blocks
==8101==         suppressed: 0 bytes in 0 blocks
==8101== Rerun with --leak-check=full to see details of leaked memory
==8101== 
==8101== For lists of detected and suppressed errors, rerun with: -s
==8101== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

As you can see, my program possibly lost 25,740 bytes, whereas rpm -qa lost 0 bytes.

Here is my code:

#include <rpm/rpmdb.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>

bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
void closeDb(rpmts & ts, rpmdbMatchIterator & mi);

int main()
{
    rpmts ts;
    rpmdbMatchIterator mi;

    if (!openDb(ts, mi)) {
        return 1;
    }

    closeDb(ts, mi);
    return 0;
}

bool openDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    {
        static volatile bool s_bHereBefore = false;
        if (!s_bHereBefore) {
            s_bHereBefore = true;
            rpmReadConfigFiles(NULL, NULL);
        }
    }

    mi = NULL;
    ts = rpmtsCreate();

    if (!ts) {
        printf("RPM open failed\n");
    } else {
        mi = rpmtsInitIterator(ts, (rpmTag)RPMDBI_PACKAGES, NULL, 0);
        if (!mi) {
            printf("RPM iterator failed\n");
            rpmtsFree(ts);
        }
    }

    return mi != NULL;
}

void closeDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    mi = rpmdbFreeIterator(mi);
    if (ts) {
        rpmtsFree(ts);
    }
}

I compile with g++ -Wall -Wextra -Wunused -Og -g try_to_fix_mem_leak.cpp -lrpm -o leaky.

I closely inspected my program, but I was unable to spot any memory leaks from manual inspection.

When I run valgrind --leak-check=full ./leaky and search the output for try_to_fix_mem_leak.cpp, all of the hits are for line 27, i.e., the rpmReadConfigFiles(NULL, NULL); line (technically there are also hits for line 13, but that is just because that is where the openDb call is made in main). (See pastebin link below.) But I don't know how this line could cause any memory leaks. The function's documentation for my version of librpm (4.16.1) doesn't mention anything about needing to free any memory.

How can I correctly open and close the RPM DB without leaking memory? Or, to put my question another way, how can I open and close the RPM DB while leaking at worst only as many bytes as rpm -qa does?


Edit

pastebin link with full output of valgrind --leak-check=full ./leaky.

Upvotes: 0

Views: 211

Answers (1)

Shane Bishop
Shane Bishop

Reputation: 4812

A colleague of mine found that, in the code for the rpm binary itself, the RPM devs call the following two functions to free their memory:

rpmFreeMacros(NULL);
rpmFreeRpmrc();

To use rpmFreeMacros(), include the rpm/rpmmacro.h system header in your source code, and link against librpmio.so.

Here is my updated code:

#include <rpm/rpmdb.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
#include <rpm/rpmmacro.h>

bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
void closeDb(rpmts & ts, rpmdbMatchIterator & mi);

int main()
{
    rpmts ts;
    rpmdbMatchIterator mi;

    if (!openDb(ts, mi)) {
        return 1;
    }

    closeDb(ts, mi);
    return 0;
}

bool openDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    {
        static volatile bool s_bHereBefore = false;
        if (!s_bHereBefore) {
            s_bHereBefore = true;
            rpmReadConfigFiles(NULL, NULL);
        }
    }

    mi = NULL;
    ts = rpmtsCreate();

    if (!ts) {
        printf("RPM open failed\n");
    } else {
        mi = rpmtsInitIterator(ts, (rpmTag)RPMDBI_PACKAGES, NULL, 0);
        if (!mi) {
            printf("RPM iterator failed\n");
            rpmtsFree(ts);
        }
    }

    return mi != NULL;
}

void closeDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    mi = rpmdbFreeIterator(mi);
    if (ts) {
        rpmtsFree(ts);
    }
    rpmFreeMacros(NULL);
    rpmFreeRpmrc();
}

Here is a diff between my new code (leak_fixed.cpp) and my old leaky code (try_to_fix_mem_leak.cpp):

$ diff -u try_to_fix_mem_leak.cpp leak_fixed.cpp 
--- try_to_fix_mem_leak.cpp 2023-01-04 10:19:28.084587731 -0500
+++ leak_fixed.cpp  2023-01-04 10:19:57.484483431 -0500
@@ -1,6 +1,7 @@
 #include <rpm/rpmdb.h>
 #include <rpm/rpmlib.h>
 #include <rpm/rpmts.h>
+#include <rpm/rpmmacro.h>
 
 bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
 void closeDb(rpmts & ts, rpmdbMatchIterator & mi);
@@ -50,4 +51,6 @@
     if (ts) {
         rpmtsFree(ts);
     }
+    rpmFreeMacros(NULL);
+    rpmFreeRpmrc();
 }

I compile with g++ -Wall -Wextra -Wunused -Og -g leak_fixed.cpp -lrpm -lrpmio -o rpm_open_close. Note that I needed to add -lrpmio in order to be able to use the rpmFreeMacros() function.

I get the same leak summary output from valgrind for both my binary and for the system rpm binary:

$ valgrind ./rpm_open_close 
==3470== Memcheck, a memory error detector
==3470== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3470== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==3470== Command: ./rpm_open_close
==3470== 
==3470== 
==3470== HEAP SUMMARY:
==3470==     in use at exit: 287 bytes in 2 blocks
==3470==   total heap usage: 10,495 allocs, 10,493 frees, 2,317,132 bytes allocated
==3470== 
==3470== LEAK SUMMARY:
==3470==    definitely lost: 0 bytes in 0 blocks
==3470==    indirectly lost: 0 bytes in 0 blocks
==3470==      possibly lost: 0 bytes in 0 blocks
==3470==    still reachable: 287 bytes in 2 blocks
==3470==         suppressed: 0 bytes in 0 blocks
==3470== Rerun with --leak-check=full to see details of leaked memory
==3470== 
==3470== For lists of detected and suppressed errors, rerun with: -s
==3470== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$ valgrind rpm -qa > /dev/null
==3483== Memcheck, a memory error detector
==3483== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3483== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==3483== Command: rpm -qa
==3483== 
==3483== 
==3483== HEAP SUMMARY:
==3483==     in use at exit: 287 bytes in 2 blocks
==3483==   total heap usage: 172,604 allocs, 172,602 frees, 121,827,169 bytes allocated
==3483== 
==3483== LEAK SUMMARY:
==3483==    definitely lost: 0 bytes in 0 blocks
==3483==    indirectly lost: 0 bytes in 0 blocks
==3483==      possibly lost: 0 bytes in 0 blocks
==3483==    still reachable: 287 bytes in 2 blocks
==3483==         suppressed: 0 bytes in 0 blocks
==3483== Rerun with --leak-check=full to see details of leaked memory
==3483== 
==3483== For lists of detected and suppressed errors, rerun with: -s
==3483== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Upvotes: 0

Related Questions