11

When you compile a kernel source, you can choose to sign kernel modules using the CONFIG_MODULE_SIG* options. The modinfo tool should handle the task of verifying the module signature, but there has been some bug in it for years, and the tool simply can't do the job anymore. All I get is the following:

sig_id:         PKCS#7
signer:
sig_key:
sig_hashalgo:   md4
signature:      30:82:02:F4:06:09:2A:86:48:86:F7:0D:01:07:02:A0:82:02:E5:30:
                ...

So there's no key and the hash algorithm is md4, which isn't even compiled in the kernel.

So how to manually check and verify the module signature? Is that even possible?

13
  • What kernel version is this? It must be quite old to be using such an outdated hash algorithm.
    – Time4Tea
    Jan 18, 2019 at 18:40
  • 1
    According to this page, public keys are stored under /proc/keys. Have you looked there? See the section on 'Public keys in the kernel'.
    – Time4Tea
    Jan 18, 2019 at 18:43
  • The kernel isn't old, it's 4.20. The md4 algorithm is shown because of a kmod bug and it's been there for 2-3 years at least: bugzilla.redhat.com/show_bug.cgi?id=1320921 Jan 18, 2019 at 18:47
  • 1
  • 1
    @MikhailMorfikov for the strange output from modinfo, I guess this answers it: "when PKC#7 signing method is used the old structure doesn't contain any useful data"; if you build a new version of kmod from source, modinfo will print unknown instead of md4 for sig_hashalgo.
    – user313992
    Jan 25, 2019 at 3:50

1 Answer 1

12
+100

Yes, that's possible, but it's quite involved.

First you have to extract the module signature -- you can use the extract-module.sig.pl script from the kernel source for that:

$ scripts/extract-module-sig.pl -s MODULE.ko >/tmp/modsig.
Read 789006 bytes from module file
Found magic number at 789006
Found PKCS#7/CMS encapsulation
Found 670 bytes of signature [3082029a06092a864886f70d010702a0]

Second, you have to extract the certificate and public key from the kernel; you can use the extract-sys-certs.pl script for that:

$ scripts/extract-sys-certs.pl /PATH/TO/vmlinux /tmp/cert.x509
Have 32 sections
Have 28167 symbols
Have 1346 bytes of certs at VMA 0xffffffff81be6db8
Certificate list in section .init.data
Certificate list at file offset 0xde6db8
$ openssl x509 -pubkey -noout -inform der -in /tmp/cert.x509 -out /tmp/pubkey

You can also extract the public key from the certs/signing_key.x509 or certs/signing_key.pem files from the linux kernel's build directory.

Having done that, you have all the data you need in /tmp/modsig and /tmp/cert.x509 and can continue with the dozen or so steps necessary to verify a PKCS#7 signature.

You can look at this blog post for the whole recipe.


I've tried to put the whole process (except for the extract-certs.pl step) in a perl script.

You can use it like this:

perl checkmodsig.pl /path/to/cert.x509 mod1.ko mod2.ko ...

YMMV. I've only tried this with a custom built kernel using sha512 signatures. This should be of course much better done by using the openssl libraries directly, instead of kludging together slow and fragile openssl x509, asn1parse and rsautl invocations.

checkmodsig.pl

use strict;

sub through {
    my ($cmd, $data, $cb) = @_;
    use IPC::Open2;
    my $pid = open2 my $from, my $to, ref $cmd ? @$cmd : $cmd;
    print $to $data; close $to; my $out;
    if($cb){ while(<$from>){ last if $out = $cb->($_) } }
    else { local $/; $out = <$from>; }
    waitpid ($pid, 0);
    die "status $?" if $? != 0;
    $out;
}
sub gethash {
    my ($d) = @_; my ($alg, $hash);
    through [qw(openssl asn1parse -inform der)], $d, sub {
        if(/(\d+):d=\d+ +hl= *(\d+) +l= *(\d+) +prim: +OCTET STRING/){
            $hash = substr $d, $1 + $2, $3
        }elsif(/prim: +OBJECT +:(sha\w+)/){
            $alg = $1;
        }
        undef
    };
    $alg, $hash
}

use File::Temp;
my $tf = new File::Temp;
my $pub_key;
my @type = qw(PGP X509 PKCS7);
my $r = 0;
if((my $cert = shift) =~ /(\.x509)$|\.pem$/i){
    $pub_key = $tf->filename;
    system qw(openssl x509 -pubkey -noout),
        '-inform', $1 ? 'der' : 'pem',
        '-in', $cert, '-out', $pub_key;
    die "status $?" if $? != 0;
}
die "no certificate/key file" unless $pub_key;
for my $kof (@ARGV){
    open my $ko, '<', $kof or die "open $kof: $!\n";
    seek $ko, -4096, 2 or die "seek: $!";
    read $ko, my $d, 4096 or die "read: $!";
    my ($algo, $hash, $type, $signer_len, $key_id_len, $sig_len, $magic) =
        unpack 'C5x3Na*', substr $d, -40;
    die "no signature in $kof"
        unless $magic eq "~Module signature appended~\n";
    die "this script only knows about PKCS#7 signatures"
        unless $type[$type] eq 'PKCS7';

    my $hash = gethash substr $d, - 40 - $sig_len, $sig_len;
    die "hash not found" unless $hash;

    my ($alg, $vhash) = gethash
        through [qw(openssl rsautl -verify -pubin -inkey), $pub_key],
            $hash;

    seek $ko, 0, 0 or die "seek: $!";
    read $ko, my $d, (-s $ko) - $sig_len - 40 or die "read: $!";
    use Digest::SHA;
    my $fhash = new Digest::SHA($alg)->add($d)->digest;

    if($fhash eq $vhash){
        print "OK $kof\n";
    }else{
        print "**FAIL** $kof\n";
        $r = 1;
        warn 'orig=', unpack('H*', $vhash), "\n";
        warn 'file=', unpack('H*', $fhash), "\n";
    }
}
exit $r;
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .