Issue
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
.
Solution
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)
Answered By - Shane Bishop Answer Checked By - Mary Flores (WPSolving Volunteer)