dcavalca / rpms / rpm

Forked from rpms/rpm a year ago
Clone
Blob Blame History Raw
From 07f1d3132f0c7b7ecb69a47a9930edb534a9250e Mon Sep 17 00:00:00 2001
From: Panu Matilainen <pmatilai@redhat.com>
Date: Mon, 7 Feb 2022 13:38:48 +0200
Subject: [PATCH] Fix IMA signature fubar, take III (#1833, RhBug:2018937)

At least ECDSA and RSA signatures can vary in length, but the IMA code
assumes constant lengths and thus may either place invalid signatures on
disk from either truncating or overshooting, and segfault if the stars are
just so.

As we can't assume static lengths and attempts to use maximum length
have proven problematic for other reasons, use a data structure that
can actually handle variable length data properly: store offsets into
the decoded binary blob and use them to calculate lengths when needed,
empty data is simply consequtive identical offsets. This avoids a whole
class of silly overflow issues with multiplying, makes zero-length data
actually presentable in the data structure and saves memory too.

Add tests to show behavior with variable length signatures and missing
signatures.

Additionally update the signing code to store the largest IMA signature
length rather than what happened to be last to be on the safe side.
We can't rely on this value due to invalid packages being out there,
but then we need to calculate the lengths on rpmfiles populate so there's
not a lot to gain anyhow.

Fixes: #1833
---
 lib/rpmfi.c         | 61 +++++++++++++++++++++++++++++++++++++++------
 sign/rpmsignfiles.c |  5 +++-
 2 files changed, 58 insertions(+), 8 deletions(-)

diff --git a/lib/rpmfi.c b/lib/rpmfi.c
index 439179689..4673fbb85 100644
--- a/lib/rpmfi.c
+++ b/lib/rpmfi.c
@@ -116,7 +116,7 @@ struct rpmfiles_s {
     struct fingerPrint_s * fps;	/*!< File fingerprint(s). */
 
     int digestalgo;		/*!< File digest algorithm */
-    int signaturelength;	/*!< File signature length */
+    uint32_t *signatureoffs;	/*!< File signature offsets */
     int veritysiglength;	/*!< Verity signature length */
     uint16_t verityalgo;	/*!< Verity algorithm */
     unsigned char * digests;	/*!< File digests in binary. */
@@ -566,10 +566,15 @@ const unsigned char * rpmfilesFSignature(rpmfiles fi, int ix, size_t *len)
     const unsigned char *signature = NULL;
 
     if (fi != NULL && ix >= 0 && ix < rpmfilesFC(fi)) {
-	if (fi->signatures != NULL)
-	    signature = fi->signatures + (fi->signaturelength * ix);
+	size_t slen = 0;
+	if (fi->signatures != NULL && fi->signatureoffs != NULL) {
+	    uint32_t off = fi->signatureoffs[ix];
+	    slen = fi->signatureoffs[ix+1] - off;
+	    if (slen > 0)
+		signature = fi->signatures + off;
+	}
 	if (len)
-	    *len = fi->signaturelength;
+	    *len = slen;
     }
     return signature;
 }
@@ -1242,6 +1247,7 @@ rpmfiles rpmfilesFree(rpmfiles fi)
 	fi->flangs = _free(fi->flangs);
 	fi->digests = _free(fi->digests);
 	fi->signatures = _free(fi->signatures);
+	fi->signatureoffs = _free(fi->signatureoffs);
 	fi->veritysigs = _free(fi->veritysigs);
 	fi->fcaps = _free(fi->fcaps);
 
@@ -1471,6 +1477,48 @@ err:
     return;
 }
 
+/*
+ * Convert a tag of variable len hex strings to binary presentation,
+ * accessed via offsets to a contiguous binary blob. Empty values
+ * are represented by identical consequtive offsets. The offsets array
+ * always has one extra element to allow calculating the size of the
+ * last element.
+ */
+static uint8_t *hex2binv(Header h, rpmTagVal tag, rpm_count_t num,
+			uint32_t **offsetp)
+{
+    struct rpmtd_s td;
+    uint8_t *bin = NULL;
+    uint32_t *offs = NULL;
+
+    if (headerGet(h, tag, &td, HEADERGET_MINMEM) && rpmtdCount(&td) == num) {
+	const char *s;
+	int i = 0;
+	uint8_t *t = bin = xmalloc(((rpmtdSize(&td) / 2) + 1));
+	offs = xmalloc((num + 1) * sizeof(*offs));
+
+	while ((s = rpmtdNextString(&td))) {
+	    uint32_t slen = strlen(s);
+	    uint32_t len = slen / 2;
+	    if (slen % 2) {
+		bin = rfree(bin);
+		offs = rfree(offs);
+		goto exit;
+	    }
+	    offs[i] = t - bin;
+	    for (int j = 0; j < len; j++, t++, s += 2)
+		*t = (rnibble(s[0]) << 4) | rnibble(s[1]);
+	    i++;
+	}
+	offs[i] = t - bin;
+	*offsetp = offs;
+    }
+
+exit:
+    rpmtdFreeData(&td);
+    return bin;
+}
+
 /* Convert a tag of hex strings to binary presentation */
 static uint8_t *hex2bin(Header h, rpmTagVal tag, rpm_count_t num, size_t len)
 {
@@ -1623,9 +1671,8 @@ static int rpmfilesPopulate(rpmfiles fi, Header h, rpmfiFlags flags)
     fi->signatures = NULL;
     /* grab hex signatures from header and store in binary format */
     if (!(flags & RPMFI_NOFILESIGNATURES)) {
-	fi->signaturelength = headerGetNumber(h, RPMTAG_FILESIGNATURELENGTH);
-	fi->signatures = hex2bin(h, RPMTAG_FILESIGNATURES,
-				 totalfc, fi->signaturelength);
+	fi->signatures = hex2binv(h, RPMTAG_FILESIGNATURES,
+				 totalfc, &fi->signatureoffs);
     }
 
     fi->veritysigs = NULL;
diff --git a/sign/rpmsignfiles.c b/sign/rpmsignfiles.c
index b143c5b9b..372ba634c 100644
--- a/sign/rpmsignfiles.c
+++ b/sign/rpmsignfiles.c
@@ -98,8 +98,9 @@ rpmRC rpmSignFiles(Header sigh, Header h, const char *key, char *keypass)
     td.count = 1;
 
     while (rpmfiNext(fi) >= 0) {
+	uint32_t slen = 0;
 	digest = rpmfiFDigest(fi, NULL, NULL);
-	signature = signFile(algoname, digest, diglen, key, keypass, &siglen);
+	signature = signFile(algoname, digest, diglen, key, keypass, &slen);
 	if (!signature) {
 	    rpmlog(RPMLOG_ERR, _("signFile failed\n"));
 	    goto exit;
@@ -110,6 +111,8 @@ rpmRC rpmSignFiles(Header sigh, Header h, const char *key, char *keypass)
 	    goto exit;
 	}
 	signature = _free(signature);
+	if (slen > siglen)
+	    siglen = slen;
     }
 
     if (siglen > 0) {
-- 
2.40.1