Files
libguestfs/daemon/rpm-c.c
Richard W.M. Jones 488245ed6c daemon: rpm: Check return values from librpm calls
We previously didn't bother to check the return values from any librpm
calls.  In some cases where possibly the RPM database is faulty, this
caused us to return a zero-length list of installed applications (but
no error indication).

One way to reproduce this is given below.  Note this reproducer will
only work when run on a RHEL 8 host (or more specifically, with
rpm <= 4.16):

$ virt-builder fedora-28
$ guestfish -a fedora-28.img -i rm /var/lib/rpm/Packages
$ guestfish --ro -a fedora-28.img -i inspect-list-applications /dev/sda4 -vx
...
chroot: /sysroot: running 'librpm'
error: cannot open Packages index using db5 - Read-only file system (30)
error: cannot open Packages database in
error: cannot open Packages index using db5 - Read-only file system (30)
error: cannot open Packages database in
librpm returned 0 installed packages
...

With this commit we get an error instead:

...
chroot: /sysroot: running 'librpm'
error: cannot open Packages index using db5 - Read-only file system (30)
error: cannot open Packages database in
ocaml_exn: 'internal_list_rpm_applications' raised 'Failure' exception
guestfsd: error: rpmtsInitIterator
guestfsd: => internal_list_rpm_applications (0x1fe) took 0.01 secs
libguestfs: trace: internal_list_rpm_applications = NULL (error)
libguestfs: error: internal_list_rpm_applications: rpmtsInitIterator
libguestfs: trace: inspect_list_applications2 = NULL (error)
libguestfs: trace: inspect_list_applications = NULL (error)
...

Not in this case, but in some cases of corrupt RPM databases it is
possible to recover them by running "rpmdb --rebuilddb" as a guest
command (ie. with guestfs_sh).

See-also: https://bugzilla.redhat.com/show_bug.cgi?id=2089623#c12
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2089623
Fixes: commit c9ee831aff
Reported-by: Xiaodai Wang
Reported-by: Ming Xie
Acked-by: Laszlo Ersek <lersek@redhat.com>
2022-05-26 10:16:21 +01:00

190 lines
5.1 KiB
C

/* libguestfs - the guestfsd daemon
* Copyright (C) 2021 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <caml/alloc.h>
#include <caml/fail.h>
#include <caml/memory.h>
#include <caml/mlvalues.h>
#ifdef HAVE_LIBRPM
#include <rpm/rpmlib.h>
#include <rpm/header.h>
#include <rpm/rpmts.h>
#include <rpm/rpmdb.h>
#endif
#include "daemon.h"
#include "actions.h"
/* Very lightweight OCaml bindings for librpm. */
#pragma GCC diagnostic ignored "-Wimplicit-function-declaration"
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#ifndef HAVE_LIBRPM
value __attribute__((noreturn))
guestfs_int_daemon_rpm_init (value unitv)
{
CAMLparam1 (unitv);
caml_failwith ("no support for RPM guests because "
"librpm was missing at compile time");
}
value __attribute__((noreturn))
guestfs_int_daemon_rpm_start_iterator (value unitv)
{
guestfs_int_daemon_rpm_init (unitv);
}
value __attribute__((noreturn))
guestfs_int_daemon_rpm_next_application (value unitv)
{
guestfs_int_daemon_rpm_init (unitv);
}
value __attribute__((noreturn))
guestfs_int_daemon_rpm_end_iterator (value unitv)
{
guestfs_int_daemon_rpm_init (unitv);
}
#else /* HAVE_LIBRPM */
value
guestfs_int_daemon_rpm_init (value unitv)
{
CAMLparam1 (unitv);
/* Nothing in actual RPM C code bothers to check if this call
* succeeds, so using that as an example, just print a debug message
* if it failed, but continue. (The librpm Python bindings do check)
*/
if (rpmReadConfigFiles (NULL, NULL) == -1)
fprintf (stderr, "rpmReadConfigFiles: failed, errno=%d\n", errno);
CAMLreturn (Val_unit);
}
static rpmts ts;
static rpmdbMatchIterator iter;
value
guestfs_int_daemon_rpm_start_iterator (value unitv)
{
CAMLparam1 (unitv);
ts = rpmtsCreate ();
if (ts == NULL)
caml_failwith ("rpmtsCreate");
#ifdef RPMVSF_MASK_NOSIGNATURES
/* Disable signature checking (RHBZ#2064182). */
rpmtsSetVSFlags (ts, rpmtsVSFlags (ts) | RPMVSF_MASK_NOSIGNATURES);
#endif
iter = rpmtsInitIterator (ts, RPMDBI_PACKAGES, NULL, 0);
/* This could return NULL in theory if there are no packages, but
* that could not happen in a real guest. However it also returns
* NULL when unable to open the database (RHBZ#2089623) which is
* something we do need to detect.
*/
if (iter == NULL)
caml_failwith ("rpmtsInitIterator");
CAMLreturn (Val_unit);
}
value
guestfs_int_daemon_rpm_next_application (value unitv)
{
CAMLparam1 (unitv);
CAMLlocal2 (rv, sv);
Header h;
guestfs_int_application2 app = { 0 };
h = rpmdbNextIterator (iter);
if (h == NULL) caml_raise_not_found ();
h = headerLink (h);
app.app2_name = headerFormat (h, "%{NAME}", NULL);
app.app2_version = headerFormat (h, "%{VERSION}", NULL);
app.app2_release = headerFormat (h, "%{RELEASE}", NULL);
app.app2_arch = headerFormat (h, "%{ARCH}", NULL);
app.app2_url = headerFormat (h, "%{URL}", NULL);
app.app2_summary = headerFormat (h, "%{SUMMARY}", NULL);
app.app2_description = headerFormat (h, "%{DESCRIPTION}", NULL);
/* epoch is special as the only int field. */
app.app2_epoch = headerGetNumber (h, RPMTAG_EPOCH);
headerFree (h);
/* Convert this to an OCaml struct. Any NULL fields must be turned
* into empty string.
*/
rv = caml_alloc (17, 0);
#define TO_CAML_STRING(i, name) \
sv = caml_copy_string (app.name ? app.name : ""); \
Store_field (rv, i, sv); \
free (app.name)
TO_CAML_STRING (0, app2_name);
TO_CAML_STRING (1, app2_display_name);
sv = caml_copy_int32 (app.app2_epoch);
Store_field (rv, 2, sv);
TO_CAML_STRING (3, app2_version);
TO_CAML_STRING (4, app2_release);
TO_CAML_STRING (5, app2_arch);
TO_CAML_STRING (6, app2_install_path);
TO_CAML_STRING (7, app2_trans_path);
TO_CAML_STRING (8, app2_publisher);
TO_CAML_STRING (9, app2_url);
TO_CAML_STRING (10, app2_source_package);
TO_CAML_STRING (11, app2_summary);
TO_CAML_STRING (12, app2_description);
TO_CAML_STRING (13, app2_spare1);
TO_CAML_STRING (14, app2_spare2);
TO_CAML_STRING (15, app2_spare3);
TO_CAML_STRING (16, app2_spare4);
#undef TO_CAML_STRING
CAMLreturn (rv);
}
value
guestfs_int_daemon_rpm_end_iterator (value unitv)
{
CAMLparam1 (unitv);
rpmdbFreeIterator (iter);
rpmtsFree (ts);
CAMLreturn (Val_unit);
}
#endif /* HAVE_LIBRPM */