Skip to content

Commit

Permalink
Fix IMA signature fubar, take III (rpm-software-management#1833, RhBu…
Browse files Browse the repository at this point in the history
…g: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 handled with special zero-block at zero offset whose size
is known beforehand. 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.

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: rpm-software-management#1833
  • Loading branch information
pmatilai committed Feb 7, 2022
1 parent 53b7a0e commit f530280
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 8 deletions.
67 changes: 59 additions & 8 deletions lib/rpmfi.c
Expand Up @@ -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. */
Expand Down Expand Up @@ -578,10 +578,18 @@ 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);
if (len)
*len = fi->signaturelength;
if (fi->signatures != NULL && fi->signatureoffs != NULL) {
uint32_t off = fi->signatureoffs[ix];
size_t slen = 0;

if (off != 0) {
signature = fi->signatures + off;
slen = fi->signatureoffs[ix+1] - off;
}

if (len)
*len = slen;
}
}
return signature;
}
Expand Down Expand Up @@ -1277,6 +1285,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);

Expand Down Expand Up @@ -1506,6 +1515,49 @@ static void rpmfilesBuildNLink(rpmfiles fi, Header h)
return;
}

/*
* Convert a tag of variable len hex strings to binary presentation,
* accessed via offsets to a contiguous binary blob. Empty strings are
* special-cased to a block of zeros at offset 0 which makes them easy
* to distinguish elsewhere. 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;
int nzeros = 16;

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) + nzeros);
offs = xmalloc((num + 1) * sizeof(*offs));

memset(t, 0, nzeros);
t += nzeros;

while ((s = rpmtdNextString(&td))) {
uint32_t len = strlen(s) / 2;
if (len == 0) {
offs[i] = 0;
} else {
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;

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)
{
Expand Down Expand Up @@ -1654,9 +1706,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;
Expand Down
1 change: 1 addition & 0 deletions tests/Makefile.am
Expand Up @@ -102,6 +102,7 @@ EXTRA_DIST += data/RPMS/hello-2.0-1.i686.rpm
EXTRA_DIST += data/RPMS/hello-2.0-1.x86_64.rpm
EXTRA_DIST += data/RPMS/hello-2.0-1.x86_64-signed.rpm
EXTRA_DIST += data/RPMS/hlinktest-1.0-1.noarch.rpm
EXTRA_DIST += data/RPMS/imatest-1.0-1.fc34.noarch.rpm
EXTRA_DIST += data/SRPMS/foo-1.0-1.src.rpm
EXTRA_DIST += data/SRPMS/hello-1.0-1.src.rpm
EXTRA_DIST += data/SOURCES/hello.c
Expand Down
Binary file added tests/data/RPMS/imatest-1.0-1.fc34.noarch.rpm
Binary file not shown.
11 changes: 11 additions & 0 deletions tests/rpmpython.at
Expand Up @@ -624,6 +624,17 @@ for f in fi:
],
[])

RPMPY_TEST([file sets 1],[
ts = rpm.ts()
h = ts.hdrFromFdno('${RPMDATA}/RPMS/imatest-1.0-1.fc34.noarch.rpm')
files = rpm.files(h)
for f in files:
myprint('%s: %s' % (f.name, f.imasig.hex()))
],
[/usr/share/example1: 030204a598255400483046022100e5117bdafa73baaeb1f1dc46ecaa46981a62d417745a33532572b63dc6d95d16022100c789107ac5b91e2d915e1df3c7b78414f6b3f50899d44c1de381d0e938dfc82b
/usr/share/example2: 030204a598255400473045022100c10943795bff5d9c0db53dd4f8e4b845615fd08a2be295c30a80f5bdb4e6a41302203038840cc6abaab92acb56cb3e3ce520b17f22ff7444a8d5d0f703a44d5307a3
],
[])
RPMPY_TEST([string pool 1],[
p = rpm.strpool()
for s in ['foo', 'bar', 'foo', 'zoo']:
Expand Down

0 comments on commit f530280

Please sign in to comment.