Upgrading a kernel can be an intimidating task. I recently wanted to upgrade my Ubuntu 20.04.1 release to have a newer kernel. I don’t know why I wanted to do this, I just thought it would be an interesting learning experience. Below are 3 different ways to work with a kernal. One is a mainline patch, one is upstream and one is an incremental patch. I hope this is helpful to you. Let’s dive into it.


The Easy Way (Ubuntu Mainline)

The first way to attempt upgrading your kernel is through Ubuntu “mainline”. Wait, what is “mainline”? Ubuntu keeps prebuilt, unmodified kernel source from upstream linux that are built with the same configuration files that Ubuntu is built with. This makes it easy to apply a new kernel and test against unmodified upstream code. So let’s upgrade our kernel!

First, navigate to the mainline releases for Ubuntu and see if the kernel you are looking for is built. In this case, I’d like to upgrade to 5.9.12 from 5.4.0 which is located here

In this case, we just want to download the generic files. The lowlatency headers are not needed unless you are building a kernel for something that is a low latency system (recording audio is a great example). Great but what are these files? These have the compiled C code that makes up the kernel. That includes shared libraries that all software uses when running on Linux.

Let’s download these, you can (and should) verify these files using these instructions before installing them.

$ curl -O https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9.12/amd64/linux-headers-5.9.12-050912-generic_5.9.12-050912.202012020835_amd64.deb
$ curl -O https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9.12/amd64/linux-headers-5.9.12-050912_5.9.12-050912.202012020835_all.deb
$ curl -O https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9.12/amd64/linux-image-unsigned-5.9.12-050912-generic_5.9.12-050912.202012020835_amd64.deb
$ curl -O https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9.12/amd64/linux-modules-5.9.12-050912-generic_5.9.12-050912.202012020835_amd64.deb

Then, we install the .deb packages and reboot

$ sudo dpkg -i *.deb
$ sudo reboot

Once the host reboots, you should find that you have a new kernel

$ uname -r
5.9.12-050912-generic

The Hard Way (Linux Upstream)

Say we want an even newer version that Ubuntu mainline doesn’t have or to apply an upstream patch that hasn’t been released yet, these instructions pull the kernel from upstream and do the build steps so you can make customizations to your kernel.

Prereqs

First, we need to install a boatload of tools for working with the codebase.

$ sudo apt-get install git build-essential kernel-package fakeroot libncurses5-dev libssl-dev ccache bison flex

Download The Kernel

Next, I started with a fresh install of Ubuntu Server. I installed OpenSSH and imported my SSH keys from GitHub, but everything else was standard.

Once logged into the server, find what kernel version the server has:

$ uname -r
5.9.12-050912-generic

Navigate to kernel.org and find what the newest version is and download:

$ curl -OL "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.9.13.tar.xz"
$ curl -OL "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.9.13.tar.sign"

Validate The Kernel

When downloading something as important as a kernel, we should always make sure the package is properly signed so we can establish provenance. The kernels are signed and can be validated using PGP with a tool called gpg. First we must download the keys that the kernel is signed with (Linus Torvalds!) and then validate our downloaded version was signed by the proper keys.

$ gpg --locate-keys torvalds@kernel.org gregkh@kernel.org
$ xz -cd "linux-5.9.13.tar.xz" | gpg --verify "linux-5.9.13.sign" -

You should see a response like this:

gpg: Signature made Wed 10 Aug 2016 06:55:15 AM EDT
gpg:                using RSA key 38DBBDC86092693E
gpg: Good signature from "Greg Kroah-Hartman <gregkh@kernel.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 647F 2865 4894 E3BD 4571  99BE 38DB BDC8 6092 693E

This warning message can be fixed by following more of these instructions, but is fine for the sake of this tutorial. Also see here for a scripted version if you need to do this through an automated process.

Build Kernel

Next, we need to build the kernel and package it so it can be installed. Let’s go!

$ tar xvf linux-5.9.13.tar
$ cd linux-5.9.13

Copy the kernel config from your existing system. These are the settings that were used on your current system (Ubuntu 20.04.1 for me) when building the kernel. When you run make oldconfig from the linux source it will prompt you for options in the current kernel that are not found in your current config. I just hit ENTER through all of these to apply defaults.

$ cp /boot/config-$(uname -r) .config
$ make oldconfig

If you want to adjust kernel settings here, you can run make menuconfig. Kernel tuning is a very deep topic that I am not an expert in, but its useful to know how to enable features in the kernel for testing new technologies.

At this point we can build the new kernel. The LOCALVERSION gets appended to the built files and helps ensure we know we are running something custom.

$ make -j4 deb-pkg LOCALVERSION=-custom

Once complete, move up a directory and install the *.deb files and reboot

$ cd ..
$ sudo dpkg -i *.deb
$ sudo reboot

Once rebooted, your kernel should show a custom build

$ uname -r
5.9.13-custom

Patch Kernel

So we recently built and applied this custom 5.9.13 kernel, but in the last 24 hours 5.9.14 was released. So how would we apply this patch?

First, we need to download the patch and decompress it. In this case, because we are going from 5.9.13 to 5.9.14 we just need an incremental patch. If we were going from 5.8 to 5.9.14 then we would want the full patch. Get it?

$ curl -O https://cdn.kernel.org/pub/linux/kernel/v5.x/incr/patch-5.9.13-14.xz
$ unxz patch-5.9.13-14.xz

Then we need to navigate to the linux-5.9.13 directory we created in the above steps, from the top level of that directory we then need to apply the patch.

$ /path/to/linux-5.9.13
$ patch -p1 < ../patch-5.9.13-4
$ cd ../
$ mv linux-5.9.13 linux-5.9.14
$ cd linux-5.9.14
$ make -j4 deb-pkg LOCALVERSION=-custom
$ sudo dpkg -i *.deb
$ sudo reboot

So what does the -p1 do? Linux patches are applied from the top root of the directory using the patch file which is really just a diff. The -p1 remove the first entry in the path for the diffs for applying the patch in the correct way.

Profit

We now have upgraded the kernel in my Ubuntu install with an upstream version. Compilation time takes most of the time up. If I did this again, I think I’d find a way to speed up compilation on a larger machine. Overall, I had fun learning this and will tinker with kernel flags as I learn more.