Software bugs, security bugs in particular are both inevitable and important to fix. Fortunately your distribution has a way of making the fixed version available.

There's 3 steps involved:

Getting updated software onto your machine

This is traditionally accomplished by downloading packages over the internet, since most systems already require an internet connection, so it's usually the most convenient way to get security updates.

To prevent downloaded updates becoming a security risk, packages need to be signed to prove that they have legitimately come from your distribution.

Alternatively, packages can be updated by putting the new versions on some form of removable media. This reduces the need to sign the updates, as you can assert some level of trust about where the updates came from, but not entirely, as USB devices can be compromised.

Updating the versions of software in your filesystem

Packages serve as both a lump of data containing the software to install, and metadata describing how it should be installed.

This is normally accomplished by replacing files on the filesystem in a specified order. The naive approach of opening the file to be replaced and rewriting the contents from the version in the package is problematic, as it results in a file that is in a non-viable state if you can't write the whole file out in one write.

For example, suppose you are replacing the C library. Most programs rely on it, so if the C library is in a partially written state, attempts to start new processes will fail. This is even more of a problem if the update is interrupted and the machine crashes, as after a reboot it crashes immediately, since the init process, which is responsible for starting every other process, cannot be run.

Fortunately there is a better approach, we can atomically update a file by:

  1. Write to a temporary file on the same filesystem as the file you want to replace.
  2. Call fsync(2) to ensure the file is written to disk.
  3. Call rename(2) to replace the old version of the file with the new version.

This means that processes either see the old version of the file or the new version, since the rename atomically replaces the old version with the new version.

Updates seldom update just a single file though, so this operation needs to be generalised to multiple files, so it becomes:

  1. Write out all the new files to temporary file paths.
  2. Rename all temporary files into place
  3. Remove all files that only exist in the old version.

This is atomic at the per-file level, but files are inter-dependent, so there is still a window between the file renames that would, if the machine were restarted at an unfortunate time, could result in a broken system. I recently spoke recently at FOSDEM about the difficulties involved in Live Atomic Updates.

Updating running processes

Making new versions available is not the end though, as all your currently running processes are still using the old code as your processes still have the old versions of the code mapped in, even though the files are no longer accessible by the file system.

It is not generally possible to have a process re-load their code during execution, nor is it generaly necessary, as re-starting the process is usually sufficient.

For the times when it is necessary to keep a process responding to requests, there's techniques to gracefully re-exec without losing state.