From a04e00c28dfff019ab9a4eae5768af525d72dcd0 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Feb 14 2023 22:52:43 +0000 Subject: [PATCH 1/4] only pad header lengths for signature headers Based on a workaround by puiterwijk https://github.com/fedora-iot/rpm-head-signing/pull/61/files --- diff --git a/koji/__init__.py b/koji/__init__.py index 756b9f6..3b3ca60 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -618,11 +618,12 @@ def find_rpm_sighdr(path): # The lead is a fixed sized section (96 bytes) that is mostly obsolete sig_start = 96 - sigsize = rpm_hdr_size(path, sig_start) + sigsize = rpm_hdr_size(path, sig_start, pad=True) + # "As of RPM 2.1 ... the Signature section is padded to a multiple of 8 bytes" return (sig_start, sigsize) -def rpm_hdr_size(f, ofs=None): +def rpm_hdr_size(f, ofs=None, pad=False): """Returns the length (in bytes) of the rpm header f = filename or file object @@ -652,8 +653,9 @@ def rpm_hdr_size(f, ofs=None): # this is what the section data says the size should be hdrsize = 8 + 16 * il + dl - # hdrsize rounded up to nearest 8 bytes - hdrsize = hdrsize + (8 - (hdrsize % 8)) % 8 + if pad: + # signature headers are padded to a multiple of 8 bytes + hdrsize = hdrsize + (8 - (hdrsize % 8)) % 8 # add eight bytes for section header hdrsize = hdrsize + 8 From f9c1f0c1e0b3fe7fddaf917cc6aa675398b600d8 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Feb 20 2023 20:45:55 +0000 Subject: [PATCH 2/4] unit test --- diff --git a/tests/test_lib/test_header_sizes.py b/tests/test_lib/test_header_sizes.py new file mode 100644 index 0000000..9e0640e --- /dev/null +++ b/tests/test_lib/test_header_sizes.py @@ -0,0 +1,67 @@ +# coding=utf-8 +from __future__ import absolute_import +import os.path +import unittest + +import koji +import rpm + + +SIGTAG_SIZE = 1000 +try: + SIGTAG_LONGSIZE = rpm.RPMTAG_LONGSIGSIZE +except NameError: + SIGTAG_LONGSIZE = None + + +class TestHeaderSizes(unittest.TestCase): + + RPMFILES = [ + "test-deps-1-1.fc24.x86_64.rpm", + "test-files-1-1.fc27.noarch.rpm", + "test-nosrc-1-1.fc24.nosrc.rpm", + "test-deps-1-1.fc24.x86_64.rpm.signed", + "test-nopatch-1-1.fc24.nosrc.rpm", + "test-src-1-1.fc24.src.rpm", + ] + + def test_header_sizes(self): + for basename in self.RPMFILES: + fn = os.path.join(os.path.dirname(__file__), 'data/rpms', basename) + + # the file length we want to match + st = os.stat(fn) + file_length = st.st_size + + # An rpm consists of: lead, signature, header, archive + s_lead, s_sig = koji.find_rpm_sighdr(fn) + ofs = s_lead + s_sig + s_hdr = koji.rpm_hdr_size(fn, ofs) + + # The signature can tell use the size of header+payload + # try LONGSIZE first, fall back to 32bit SIZE + sighdr = koji.rip_rpm_sighdr(fn) + rh = koji.RawHeader(sighdr) + size = None + try: + tag = rpm.RPMTAG_LONGSIGSIZE + size = rh.get(tag) + except NameError: + pass + if size is None: + size = rh.get(SIGTAG_SIZE) + + # Expected file size + calc_size = s_lead + s_sig + size + self.assertEqual(calc_size, file_length) + + # The following bit uses rpmlib to read the header, which advances the file + # pointer past it. This is the same approach rpm2cpio uses. + fd = os.open(fn, os.O_RDONLY) + os.lseek(fd, s_lead + s_sig, 0) # seek to header start + hdr, h_start = rpm.readHeaderFromFD(fd) + p_offset = os.lseek(fd, 0, os.SEEK_CUR) + expect_payload = s_lead + s_sig + s_hdr + if not hdr: + raise Exception("rpm did not return a header") + self.assertEqual(p_offset, expect_payload) From fec447a3871e7807f9f89f4aa885f599a3b20a96 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Feb 20 2023 20:57:50 +0000 Subject: [PATCH 3/4] unit test: close fd --- diff --git a/tests/test_lib/test_header_sizes.py b/tests/test_lib/test_header_sizes.py index 9e0640e..4d41d56 100644 --- a/tests/test_lib/test_header_sizes.py +++ b/tests/test_lib/test_header_sizes.py @@ -58,9 +58,12 @@ class TestHeaderSizes(unittest.TestCase): # The following bit uses rpmlib to read the header, which advances the file # pointer past it. This is the same approach rpm2cpio uses. fd = os.open(fn, os.O_RDONLY) - os.lseek(fd, s_lead + s_sig, 0) # seek to header start - hdr, h_start = rpm.readHeaderFromFD(fd) - p_offset = os.lseek(fd, 0, os.SEEK_CUR) + try: + os.lseek(fd, s_lead + s_sig, 0) # seek to header start + hdr, h_start = rpm.readHeaderFromFD(fd) + p_offset = os.lseek(fd, 0, os.SEEK_CUR) + finally: + os.close(fd) expect_payload = s_lead + s_sig + s_hdr if not hdr: raise Exception("rpm did not return a header") From 41a89ddb47ab1b200199a1f42378fce7e0dff7a9 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Feb 20 2023 21:49:57 +0000 Subject: [PATCH 4/4] unit test: more closely mimic rpm2cpio behavior when reading past header --- diff --git a/tests/test_lib/test_header_sizes.py b/tests/test_lib/test_header_sizes.py index 4d41d56..dc603f8 100644 --- a/tests/test_lib/test_header_sizes.py +++ b/tests/test_lib/test_header_sizes.py @@ -57,10 +57,11 @@ class TestHeaderSizes(unittest.TestCase): # The following bit uses rpmlib to read the header, which advances the file # pointer past it. This is the same approach rpm2cpio uses. + ts = rpm.TransactionSet() + ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES | rpm._RPMVSF_NODIGESTS | rpm.RPMVSF_NOHDRCHK) fd = os.open(fn, os.O_RDONLY) try: - os.lseek(fd, s_lead + s_sig, 0) # seek to header start - hdr, h_start = rpm.readHeaderFromFD(fd) + hdr = ts.hdrFromFdno(fd) p_offset = os.lseek(fd, 0, os.SEEK_CUR) finally: os.close(fd)