Debugging Kernel Module Signing

31 Jul, 2025    

When I made my first custom disk image to use with Confidential VM on cloud, the kernel and kernel modules I used was one that I grabbed off a Confidential VM on that specific cloud provider. Then, I would create a custom image for that cloud provider and specify my own keys for secure boot, because I wanted to direct boot into a Unified Kernel Image (UKI) and I needed to sign it. For example, on gcp, I could create a custom image on the gcloud command line like so:

gcloud compute images create $IMAGE_NAME \
  --source-uri gs://$BUCKET/$UPLOADED_COMPRESSED_FILE \
  --project="$PROJECT_ID" \
  --guest-os-features "TDX_CAPABLE,SEV_SNP_CAPABLE,GVNIC,UEFI_COMPATIBLE,VIRTIO_SCSI_MULTIQUEUE" \
  --storage-location="$LOCATION" \
  --platform-key-file=secure_boot/PK.crt \
  --key-exchange-key-file=secure_boot/KEK.crt \
  --signature-database-file=secure_boot/db.crt

One of the things I noticed that I could do was that I could sign my out-of-tree kernel (oot) module with the private key of db.crt (which I will call db.key for convenience), and I could still load the oot kernel module when secure boot was enabled.

However, this behavior changed when I swapped to compiling my own kernel, taken off the mainline Linux git - I was suddenly unable to load oot modules signed with db.key. The module signature would be rejected.

My first thought was that I was somehow missing a kernel patch, and after some digging, I realised I was right.

I came across this linux kernel mailing article , and ended up taking a look at 2 corresponding gitlab commits. This is the first commit, which adds the ability to use platform keyring (the signature database file is part of the platform keyring) to verify the signature of the kernel bzImage. I checked my kernel and it appears that this patch already made it into mainline kernel. However, the second commit, which further extends this support to kernel modules, somehow doesn’t seem to be in the mainline kernel still (but interestingly it seems that the cloud providers I use already support this in their kernels by default).

In this case, the solution was rather straightforward - I just needed to add the missing commit into my own kernel and rebuild it.

So, why did I go through the trouble of doing all this? The reason was that I was experimenting with supporting kernel livepatching on this particular minimal disk image. The context is that I would pre-create the disk image for a user to use, and the user might want to create livepatches to fix critical bugs, especially when they may not be able to stop the running machine immediately. In this case, I would not want the user to have to re-create the entire disk by hand just because I signed the kernel with my own key (although it unfortunately cannot be helped that you do need to compile the entire kernel again in order to create a livepatch, but at least you only need to upload the livepatch onto the disk). The simplest way is if they could upload their own db.crt during image creation, and simply use their db.key to sign any livepatches they make.