Written September 26, 2014 — Updated Sept. 27.

What do you do when there is a critical security bug, but you're running such an old operating system version that it is no longer supported and you can't just do a normal system update to get the fix?

Here's how I manually patched an old Fedora Linux system. This may not be the correct or best way to do this, but hopefully it may prove useful to others who at looking for solutions.

This is an ongoing event! As of the time when I wrote this there has been one patch released (4.3.25, 4.2.48, ...), and which repairs the worst problem. Unfortunately additional related bugs are being found as more attention is being focused on the code. It is highly likely that more patches will be coming over the next few days.
Update Sept. 27: A second official patch has been published, i.e. Bash 4.3.26. However the 4.2 equivalent patch (4.2.49) currently appears to be broken. So if patching 4.2, only patch up through patchlevel 48 for now until that issue is addressed.

The Shellshock bug

On September 24, 2014 a public announcement was made about a serious security flaw in the Bash command line shell. The bug, present in nearly all versions of Bash, has become known a the Shellshock Bug.

This bug allows a user to cause Bash to execute any arbitrary command simply by manipulating the value of environment variables. It does not escalate priviliges (i.e., doesn't give you root access), so by itself it's not terribly interesting. However many network services invoke bash shell processes, and so may facilitate making this a remotely exploitabe bug. The most obvious attack vector is via Web servers using the CGI scripting protocol; because CGI gives a remote user direct contol over environment variable values. Other less-obvious services such as CUPS (print server), DHCP, and even SSH under certain configurations could also allow for remote exploitation.

For more information on the bug, read:

Official Bash resources:

The rest of this article is not so much about the bug or the internal design of Bash, but rather how to manually apply patches (in Fedora Linux) when they are made available.

Manually patching as a last resort

First, if your system is currently supported then you should probably follow your standard system update procedure. For Fedora this would be to run the command:

$ yum update bash    # Only works on currently-supported OS versions

Unfortunately, I have a system which is running Fedora 17, a version old enough to be unsupported. This means that I can't simply get the patch by running a system update. I need to manually patch it myself, from sources. I have already manually patched or replaced many older software components, but updating bash requires a little more thought as it is so central to the whole system.

A first thought is to just grab the official Bash source code and compile it. But it should be noted that Fedora has made many Fedora-specific changes to Bash, both as source code patches and as installation changes. To insure a working system I want the new Bash I install to be as similar to the Fedora-version as possible.

It seems that the safest route would be to build Bash exactly as the Fedora distribution had done, but with the newer additional patches applied. The basic outline of what I want to do is:

  1. Get the most recent Fedora source packages (SRPMs) for bash.
  2. Get the newest official (GNU) sources and patches for Bash.
  3. Merge the new patches into the Fedora code.
  4. Recompile Bash using Fedora's build system and additional patches.
  5. Install the newly compiled bash into the system.

Get the old Fedora sources

Download the Source RPM for your OS version. For Fedora 17 these are archived at: https://archive.fedoraproject.org/pub/archive/fedora/linux/updates/17/SRPMS/. Download file "bash-4.2.39-3.fc17.src.rpm".

Install (extract) the SRPM file:

$ rpm -ivv bash-4.2.39-3.fc17.src.rpm

This extracts the source-package's code to /root/rpmbuild/SOURCES/ and the "spec" file to /root/rpmbuild/SPECS/bash.spec. The spec file contains all the Fedora-specific instructions on how to build and install the program. Listing the sources directory shows a lot of files, some of which are:

$ ls /root/rpmbuild/SOURCES

Like all RPM packages, the original and unmodified upstream source code is in the *.tar.gz file, with all the Fedora modifications appearing as separate *.patch files. In this case, due to the atypical way in which Bash is officially distributed, there are also 39 separate patch files named "bash42-001" through "bash42-039". Those are the first 39 official Bash patches to version 4.2; e.g versions 4.2.1 to 4.2.39.

Of particular note is that last official bash patch included with Fedora was 39, the file "bash039".

Get the new bash patches

Grab the new official bash patches from the GNU FTP site. For Bash 4.2,

$ mkdir bash42
$ cd bash42
$ wget -nd -nH -r ftp://ftp.gnu.org/pub/gnu/bash/bash-4.2-patches/

The latest official patch is 48, as evidence by the file "bash42-048" just downloaded. Patch 48 (for version 4.2) is the patch which includes the fix for the "Shellshock" bug. This also means all patches 40 through 48 need to be applied to the Fedora-supplied code.

Important: patch 48 was the most recent patch for the 4.2 version at the time this article was written. If there are additional patches when you perform this you may wish to install those as well. Generally it is always a good idea to review the upstream (Bash) patches and compare them with the Fedora-specific patches to insure there are no conflicts or duplicates (for example Fedora could have written a patch which at a later date was subsequently accepted as an official Bash patch). I have already reviewed the patches up to patch 48.

Apply the newer patches to the old sources

Copy the patch files for 40 through 48 into the SOURCES directory.

$ cp bash42-04? /root/rpmbuild/SOURCES/

Now you have to edit the bash.spec file, which is just a text file, to tell the build system to apply those patches. There are three places in the file that need to change: (1) the package version number; (2) the file manifest section which lists input files or where to obtain them; and (3) the list of instructions for applying the patches. The lines to edit look like:

$ vi /root/rpmbuild/SPECS/bash.spec
%define patchleveltag .48      --- was .39
%define baseversion 4.2
Patch039: ftp://ftp.gnu.org/pub/gnu/bash/bash-4.2-patches/bash42-039
Patch041: ftp://ftp.gnu.org/pub/gnu/bash/bash-4.2-patches/bash42-040
... and so on adding 039 through 048
Patch048: ftp://ftp.gnu.org/pub/gnu/bash/bash-4.2-patches/bash42-048
%patch039 -p0 -b .039
%patch040 -p0 -b .040
... and so on adding 039 through 048
%patch048 -p0 -b .048

Rebuild the bash executable

Use the rpmbuild command to build the source.

$ rpmbuild -bp   # prep stage: applies patches, etc.
$ rpmbuild -bc   # compile stage: runs configure and make

Note that rpmbuild will do all its work in the directory /root/rpmbuild/BUILD/bash-4.2/.

Install the new bash executable

You could use the rpmbuild -bi command to do a full (re-)install. But in this specific case there is really only a need to install the lone "bash" executable file. All the other ancillary files, such as documentation, will have had no significant changes. So the installation step can be done manually, which gives you more control and recoverability in case something goes wrong. First, make sure to save a copy of of the old shell in case you need to get it back:

$ cp -p /usr/bin/bash /usr/bin/bash-4.2-39

Then copy the newly compiled bash into a temporary path:

$ cp /root/rpmbuild/BUILD/bash-4.2/bash /usr/bin/bash-4.2.48

Set the permissions:

$ chown root:root /bin/bash-4.2.48
$ chmod 755 /bin/bash-4.2.48
$ chcon --reference=/bin/bash /bin/bash-4.2.*   # If SELinux is enabled

Examine the new executable to make sure it has the same run-time dynamic library dependencies. This insures it is still usable as the 'root' login shell in the minimal system environment during boots. Your details may differ, but on my system:

$ ldd /usr/bin/bash-4.2.39
	linux-vdso.so.1 =>  (0x00007ffff18ed000)
	libtinfo.so.5 => /lib64/libtinfo.so.5 (0x000000303ea00000)
	libdl.so.2 => /lib64/libdl.so.2 (0x000000302ae00000)
	libc.so.6 => /lib64/libc.so.6 (0x000000302a600000)
	/lib64/ld-linux-x86-64.so.2 (0x000000302a200000)

$ ldd /usr/bin/bash-4.2.48
	linux-vdso.so.1 =>  (0x00007ffff18ed000)
	libtinfo.so.5 => /lib64/libtinfo.so.5 (0x000000303ea00000)
	libdl.so.2 => /lib64/libdl.so.2 (0x000000302ae00000)
	libc.so.6 => /lib64/libc.so.6 (0x000000302a600000)
	/lib64/ld-linux-x86-64.so.2 (0x000000302a200000)

Now try using the new shell before installing it permanently,

$ PS1='NEW$ ' bash-4.2.48
NEW$   # Now in new bash


NEW$  env x='() { :;}; echo vulnerable' bash-4.2.48 -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test
     -- the above errors means the Shellshock bug was fixed!

NEW$ exit

It looks good, so lets move the new bash into place. You might want to do this in a different shell,

$ dash
$ mv /usr/bin/bash /usr/bin/bash.old && cp -p /usr/bin/bash-4.2.48 /usr/bin/bash
$ chcon --reference=/usr/bin/bash-4.2.39 /usr/bin/bash
$ exit   # Leave the temporary 'dash' shell

Lets take a final look at the executable files and permissions:

$ ls -l /usr/bin/bash*
-rwxr-xr-x. 1 root root 3581578 Sep 26 02:01 /usr/bin/bash
-rwxr-xr-x. 1 root root  967008 Jan 31  2013 /usr/bin/bash-4.2.39
-rwxr-xr-x. 1 root root 3581578 Sep 26 02:01 /usr/bin/bash-4.2.48

$ ls -lZ /usr/bin/bash*
-rwxr-xr-x. root root system_u:object_r:shell_exec_t:s0 /usr/bin/bash
-rwxr-xr-x. root root system_u:object_r:shell_exec_t:s0 /usr/bin/bash-4.2.39
-rwxr-xr-x. root root system_u:object_r:shell_exec_t:s0 /usr/bin/bash-4.2.48

Ignore the file size differences, that's just because debugging symbols and such were not stripped out. But the file mode, ownership, and SELinux contexts should be the same.

Eventually you'll probably want to reboot your system, as any previously-running shells are still using the old version. But any new shells that are started from this point will use the new version, which is sufficient to close the security hole.

The end.