0b2921
From 820dcc1db9f2130a21fdaf721217034376eb8e38 Mon Sep 17 00:00:00 2001
0b2921
Message-Id: <820dcc1db9f2130a21fdaf721217034376eb8e38.1544785848.git.pmatilai@redhat.com>
0b2921
From: Panu Matilainen <pmatilai@redhat.com>
0b2921
Date: Fri, 30 Nov 2018 13:10:44 +0200
0b2921
Subject: [PATCH] Add support for logging audit events for package installs as
0b2921
 per OSPP v4.2
0b2921
0b2921
If enabled at build-time, log audit events for package install, update
0b2921
and remove. The log includes the operation, package nevra, signature
0b2921
check result, whether signatures are being enforced enforced and overall
0b2921
success result. Package install/update/remove are logged as such,
0b2921
obsoletion is logged as install + remove (whereas the erasure element
0b2921
on updates is silent)
0b2921
0b2921
Loosely based on initial RHEL 7-8 implementations by Pavlina Moravcova
0b2921
Varekova and Florian Festi (RhBug:1555326, RhBug:1607612)
0b2921
0b2921
(cherry picked from commit cfc9dde70fe65e91c83e03e9a9441e627b741489)
0b2921
---
0b2921
 configure.ac         |  21 +++++++++
0b2921
 lib/Makefile.am      |   1 +
0b2921
 lib/rpmte.c          |  11 +++++
0b2921
 lib/rpmte_internal.h |   6 +++
0b2921
 lib/transaction.c    | 104 +++++++++++++++++++++++++++++++++++++++++++
0b2921
 5 files changed, 143 insertions(+)
0b2921
0b2921
diff --git a/configure.ac b/configure.ac
0b2921
index 34ea85f9f..ab8a368d3 100644
0b2921
--- a/configure.ac
0b2921
+++ b/configure.ac
0b2921
@@ -312,6 +312,27 @@ fi
0b2921
 AC_SUBST(WITH_BEECRYPT_LIB)
0b2921
 AC_SUBST(WITH_BEECRYPT_INCLUDE)
0b2921
 
0b2921
+
0b2921
+#=================
0b2921
+# Check for audit library.
0b2921
+AC_ARG_WITH(audit,
0b2921
+AS_HELP_STRING([--with-audit],[log results using Linux Audit]),
0b2921
+with_audit=$withval,
0b2921
+with_audit=auto)
0b2921
+
0b2921
+WITH_AUDIT_LIB=
0b2921
+AS_IF([test "x$with_audit" != xno],[
0b2921
+    AC_SEARCH_LIBS([audit_open],[audit],[
0b2921
+    WITH_AUDIT_LIB="$ac_res"
0b2921
+    AC_DEFINE(WITH_AUDIT, 1, [libaudit support])
0b2921
+    ],
0b2921
+       [if test "x$with_audit" != xauto; then
0b2921
+		AC_MSG_ERROR([missing audit library])
0b2921
+	fi
0b2921
+    ])
0b2921
+])
0b2921
+AC_SUBST(WITH_AUDIT_LIB)
0b2921
+
0b2921
 #=================
0b2921
 # Check for OpenSSL library.
0b2921
 # We need evp.h from OpenSSL.
0b2921
diff --git a/lib/Makefile.am b/lib/Makefile.am
0b2921
index baf3238ee..c055962a3 100644
0b2921
--- a/lib/Makefile.am
0b2921
+++ b/lib/Makefile.am
0b2921
@@ -51,6 +51,7 @@ librpm_la_LIBADD = \
0b2921
 	@WITH_POPT_LIB@ \
0b2921
 	@WITH_CAP_LIB@ \
0b2921
 	@WITH_ACL_LIB@ \
0b2921
+	@WITH_AUDIT_LIB@ \
0b2921
 	@LIBINTL@
0b2921
 
0b2921
 if WITH_LUA
0b2921
diff --git a/lib/rpmte.c b/lib/rpmte.c
0b2921
index d980a37a4..bd5d53edc 100644
0b2921
--- a/lib/rpmte.c
0b2921
+++ b/lib/rpmte.c
0b2921
@@ -69,6 +69,7 @@ struct rpmte_s {
0b2921
     int nrelocs;		/*!< (TR_ADDED) No. of relocations. */
0b2921
     uint8_t *badrelocs;		/*!< (TR_ADDED) Bad relocations (or NULL) */
0b2921
     FD_t fd;			/*!< (TR_ADDED) Payload file descriptor. */
0b2921
+    int verified;		/*!< (TR_ADDED) Verification status */
0b2921
 
0b2921
 #define RPMTE_HAVE_PRETRANS	(1 << 0)
0b2921
 #define RPMTE_HAVE_POSTTRANS	(1 << 1)
0b2921
@@ -753,6 +754,16 @@ rpmfs rpmteGetFileStates(rpmte te)
0b2921
     return te->fs;
0b2921
 }
0b2921
 
0b2921
+void rpmteSetVerified(rpmte te, int verified)
0b2921
+{
0b2921
+    te->verified = verified;
0b2921
+}
0b2921
+
0b2921
+int rpmteGetVerified(rpmte te)
0b2921
+{
0b2921
+    return te->verified;
0b2921
+}
0b2921
+
0b2921
 int rpmteProcess(rpmte te, pkgGoal goal, int num)
0b2921
 {
0b2921
     /* Only install/erase resets pkg file info */
0b2921
diff --git a/lib/rpmte_internal.h b/lib/rpmte_internal.h
0b2921
index a5a991ec5..2895925ce 100644
0b2921
--- a/lib/rpmte_internal.h
0b2921
+++ b/lib/rpmte_internal.h
0b2921
@@ -86,6 +86,12 @@ int rpmteHaveTransScript(rpmte te, rpmTagVal tag);
0b2921
 /* XXX should be internal too but build code needs for now... */
0b2921
 rpmfs rpmteGetFileStates(rpmte te);
0b2921
 
0b2921
+RPM_GNUC_INTERNAL
0b2921
+void rpmteSetVerified(rpmte te, int verified);
0b2921
+
0b2921
+RPM_GNUC_INTERNAL
0b2921
+int rpmteGetVerified(rpmte te);
0b2921
+
0b2921
 /** \ingroup rpmte
0b2921
  * Retrieve size in bytes of package header.
0b2921
  * @param te		transaction element
0b2921
diff --git a/lib/transaction.c b/lib/transaction.c
0b2921
index 67b9db579..866e87fc2 100644
0b2921
--- a/lib/transaction.c
0b2921
+++ b/lib/transaction.c
0b2921
@@ -7,6 +7,10 @@
0b2921
 #include <inttypes.h>
0b2921
 #include <libgen.h>
0b2921
 
0b2921
+#if WITH_AUDIT
0b2921
+#include <libaudit.h>
0b2921
+#endif
0b2921
+
0b2921
 #include <rpm/rpmlib.h>		/* rpmMachineScore, rpmReadPackageFile */
0b2921
 #include <rpm/rpmmacro.h>	/* XXX for rpmExpand */
0b2921
 #include <rpm/rpmlog.h>
0b2921
@@ -1195,12 +1199,17 @@ static rpm_loff_t countPkgs(rpmts ts, rpmElementTypes types)
0b2921
 
0b2921
 struct vfydata_s {
0b2921
     char *msg;
0b2921
+    int signature;
0b2921
     int vfylevel;
0b2921
 };
0b2921
 
0b2921
 static int vfyCb(struct rpmsinfo_s *sinfo, void *cbdata)
0b2921
 {
0b2921
     struct vfydata_s *vd = cbdata;
0b2921
+
0b2921
+    if (sinfo->type == RPMSIG_SIGNATURE_TYPE && sinfo->rc == RPMRC_OK)
0b2921
+	vd->signature = RPMRC_OK;
0b2921
+
0b2921
     switch (sinfo->rc) {
0b2921
     case RPMRC_OK:
0b2921
 	break;
0b2921
@@ -1241,6 +1250,7 @@ static int verifyPackageFiles(rpmts ts, rpm_loff_t total)
0b2921
 	struct rpmvs_s *vs = rpmvsCreate(vfylevel, vsflags, keyring);
0b2921
 	struct vfydata_s vd = {
0b2921
 	    .msg = NULL,
0b2921
+	    .signature = RPMRC_NOTFOUND,
0b2921
 	    .vfylevel = vfylevel,
0b2921
 	};
0b2921
 	rpmRC prc = RPMRC_FAIL;
0b2921
@@ -1255,6 +1265,9 @@ static int verifyPackageFiles(rpmts ts, rpm_loff_t total)
0b2921
 	if (prc == RPMRC_OK)
0b2921
 	    prc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
0b2921
 
0b2921
+	/* Record verify result, signatures only for now */
0b2921
+	rpmteSetVerified(p, vd.signature == RPMRC_OK);
0b2921
+
0b2921
 	if (prc)
0b2921
 	    rpmteAddProblem(p, RPMPROB_VERIFY, NULL, vd.msg, 0);
0b2921
 
0b2921
@@ -1619,6 +1632,95 @@ rpmRC runScript(rpmts ts, rpmte te, Header h, ARGV_const_t prefixes,
0b2921
     return rc;
0b2921
 }
0b2921
 
0b2921
+#if WITH_AUDIT
0b2921
+struct teop {
0b2921
+    rpmte te;
0b2921
+    const char *op;
0b2921
+};
0b2921
+
0b2921
+/*
0b2921
+ * Figure out the actual operations:
0b2921
+ * Install and remove are straightforward. Updates need to discovered 
0b2921
+ * via their erasure element: locate the updating element, adjust it's
0b2921
+ * op to update and silence the erasure part. Obsoletion is handled as
0b2921
+ * as install + remove, which it technically is.
0b2921
+ */
0b2921
+static void getAuditOps(rpmts ts, struct teop *ops, int nelem)
0b2921
+{
0b2921
+    rpmtsi pi = rpmtsiInit(ts);
0b2921
+    rpmte p;
0b2921
+    int i = 0;
0b2921
+    while ((p = rpmtsiNext(pi, 0)) != NULL) {
0b2921
+	const char *op = NULL;
0b2921
+	if (rpmteType(p) == TR_ADDED) {
0b2921
+	    op = "install";
0b2921
+	} else {
0b2921
+	    op = "remove";
0b2921
+	    rpmte d = rpmteDependsOn(p);
0b2921
+	    /* Fixup op on updating elements, silence the cleanup stage */
0b2921
+	    if (d != NULL && rstreq(rpmteN(d), rpmteN(p))) {
0b2921
+		/* Linear lookup, but we're only dealing with a few thousand */
0b2921
+		for (int x = 0; x < i; x++) {
0b2921
+		    if (ops[x].te == d) {
0b2921
+			ops[x].op = "update";
0b2921
+			op = NULL;
0b2921
+			break;
0b2921
+		    }
0b2921
+		}
0b2921
+	    }
0b2921
+	}
0b2921
+	ops[i].te = p;
0b2921
+	ops[i].op = op;
0b2921
+	i++;
0b2921
+    }
0b2921
+    rpmtsiFree(pi);
0b2921
+}
0b2921
+
0b2921
+/*
0b2921
+ * If enabled, log audit events for the operations in this transaction.
0b2921
+ * In the event values, 1 means true/success and 0 false/failure. Shockingly.
0b2921
+ */
0b2921
+static void rpmtsAudit(rpmts ts)
0b2921
+{
0b2921
+    int auditFd = audit_open();
0b2921
+    if (auditFd < 0)
0b2921
+	return;
0b2921
+
0b2921
+    int nelem = rpmtsNElements(ts);
0b2921
+    struct teop *ops = xcalloc(nelem, sizeof(*ops));
0b2921
+    char *dir = audit_encode_nv_string("root_dir", rpmtsRootDir(ts), 0);
0b2921
+    int enforce = (rpmtsVfyLevel(ts) & RPMSIG_SIGNATURE_TYPE) != 0;
0b2921
+
0b2921
+    getAuditOps(ts, ops, nelem);
0b2921
+
0b2921
+    for (int i = 0; i < nelem; i++) {
0b2921
+	const char *op = ops[i].op;
0b2921
+	if (op) {
0b2921
+	    rpmte p = ops[i].te;
0b2921
+	    char *nevra = audit_encode_nv_string("sw", rpmteNEVRA(p), 0);
0b2921
+	    char eventTxt[256];
0b2921
+	    int verified = rpmteGetVerified(p);
0b2921
+	    int result = (rpmteFailed(p) == 0);
0b2921
+
0b2921
+	    snprintf(eventTxt, sizeof(eventTxt),
0b2921
+		    "op=%s %s sw_type=rpm key_enforce=%u gpg_res=%u %s",
0b2921
+		    op, nevra, enforce, verified, dir);
0b2921
+	    audit_log_user_comm_message(auditFd, AUDIT_SOFTWARE_UPDATE,
0b2921
+				    eventTxt, NULL, NULL, NULL, NULL, result);
0b2921
+	    free(nevra);
0b2921
+	}
0b2921
+    }
0b2921
+
0b2921
+    free(dir);
0b2921
+    free(ops);
0b2921
+    audit_close(auditFd);
0b2921
+}
0b2921
+#else
0b2921
+static void rpmtsAudit(rpmts ts)
0b2921
+{
0b2921
+}
0b2921
+#endif
0b2921
+
0b2921
 int rpmtsRun(rpmts ts, rpmps okProbs, rpmprobFilterFlags ignoreSet)
0b2921
 {
0b2921
     int rc = -1; /* assume failure */
0b2921
@@ -1732,6 +1834,8 @@ exit:
0b2921
 	rpmpluginsCallTsmPost(rpmtsPlugins(ts), ts, rc);
0b2921
 
0b2921
     /* Finish up... */
0b2921
+    if (!(rpmtsFlags(ts) & (RPMTRANS_FLAG_TEST|RPMTRANS_FLAG_BUILD_PROBS)))
0b2921
+	rpmtsAudit(ts);
0b2921
     (void) umask(oldmask);
0b2921
     (void) rpmtsFinish(ts);
0b2921
     rpmpsFree(tsprobs);
0b2921
-- 
0b2921
2.19.2
0b2921