Sunday, March 25, 2012

How to Lock Down and Clean up GRUB 2

In Ubuntu 9.10 and later

Introduction
Ubuntu boots using the GRUB boot loader. GRUB has all sorts of fun powers including the ability to pull up a root shell without any authentication. Sometimes, though, you don't want root to be so trivially accessible to your drunk frenemies and script kiddie siblings. This is a perfectly normal sentiment.

In this post, I will explain how to lock down the GRUB 2 boot menu used in Ubuntu. This will not protect your data if your computer is stolen, if the CIA is out to get you, or in the face of another persistent attack. It will, however, make it a lot harder for passersby to ruin your digital life.

GRUB 2 on Ubuntu is a bit of a mess at the moment, and locking down GRUB is a good time to also do some cleaning up. The second part of the post is about just that.

Disclaimer
The procedure I describe worked for me, but it might not work for you. I am not your sysadmin, and I am not responsible for what you do with the computers to which you have access. I cannot be responsible for any data loss resulting from following these instructions. ALWAYS MAKE A BACKUP! I used Ubuntu 11.10 (Oneiric Ocelot); other versions may differ.

Background

This section explains why configuring GRUB 2 on Ubuntu is such a pain. If you're just skimming through, the interesting info starts in the next section.

Since Ubuntu 9.10 (Karmic Koala), Ubuntu has used GRUB 2 as the bootloader rather than classic GRUB. The difference in configuring the two is illustrated in the following diagram:
In words: In classic GRUB, the user wrote a menu.lst file that controlled GRUB. In GRUB 2, the user modifies a series of configuration files and scripts, the update-grub script takes these files and some hard-coded magic, and spits out a grub.cfg configuration file.

Using eight (or more) configuration files has some advantages over using just one. For example, 8 is clearly a bigger and better number than 1. On the downside, the number of files involved makes it difficult to find the setting you want, and sometimes the setting you want is controlled by hard-coded scripting magic instead of a line in a configuration file. Password-protecting GRUB is an example of just such functionality.

The amusingly unhelpful “Security” section of Ubuntu's GRUB 2 documentation says that there's no way to lock down GRUB. This is actually not true; it's possible to have coarse-grained control over what is locked in GRUB through the standard configuration files. Fine-grained control currently requires a slightly more unorthodox approach. I will cover both options.

Files

First, a list of all the useful files:

/etc/default/grub
The main, user-facing configuration file. All the options you would ever want to change as an end user should be in this file. In fact, they are not.

/etc/grub.d/##_<description>
These scripts generate the entries that show up in the GRUB 2 boot menu. You're really only meant to edit the one called 40_custom.

/boot/grub/grub.cfg
This is the autogenerated GRUB 2 configuration. You should never touch this file, but editing it is the only way to get GRUB to really do what you want.

/usr/sbin/update-grub
This is a very thin wrapper around grub-mkconfig. It is called to regenerate grub.cfg.

Locking down GRUB
This section is based on some Googling, the GRUB info pages, and trial and error. Before you change anything, it's a good idea to create copies of the files so that it's easy to revert back. You will need root access to complete these steps.

1. Disable generation of recovery mode menu entries (optional)
In /etc/default/grub, uncomment the GRUB_DISABLE_RECOVERY=”true” line. Recovery mode entries in the GRUB menu allow access to a root shell. Uncommenting this line will make the recovery mode entries go away. Of course, you might want a recovery mode entry in case you need it, so you can skip this step. However, if you do skip this step, you will need to resort to the unorthodox methods in step 4 to lock GRUB.

2. Set superuser name and password
In /etc/grub.d/40_custom, add these two lines at the end:
set superusers="root"
password_pbkdf2 root grub.pbkdf2.sha512.10000.<biglongstring>
As with all shell scripts, make sure the file ends with a blank line.

The first line sets the name of the superuser to “root”. The second line contains the hash of the root user's password. To obtain this, run $> grub-mkpasswd-pbkdf2 on the command line, and type the desired password. Then paste the output of the command into 40_custom.

This change, once passed to grub.cfg, will password-protect command-line access and the ability to edit menu items from within GRUB itself. If you skipped step 1, recovery mode entries will still allow unfettered access to a root shell.

3. Run update-grub 
$> sudo update-grub
This will generate a new grub.cfg file, taking into account the changes in the previous two steps.

If you completed step 1, you now have coarse-grained password-protection: GRUB will allow you to boot all installed Linux kernels as well as any other OS you have on your hard drive without a password. There are no recovery mode menu items, and you need a password to get into a shell from GRUB. You can finish here or go on to the next step.

4. Hack grub.cfg (optional)
If you want fine-grained control over what is password-protected in GRUB or you skipped step 1, you will need to hack grub.cfg. Some system updates will regenerate grub.cfg for you, so you will need to repeat this step after every update.

The first thing to do is find the recovery mode entry and other entires you want to password-protect in grub.cfg, and add the --user "" option:
menuentry 'Ubuntu, with Linux <some version> (recovery mode)' --users "" --class ubuntu ...
This tells GRUB that by default, no users are allowed to access this entry; the root password will be required to run it.

If you have more than one Linux kernels installed and you skipped step 1, update-grub will create recovery mode entries for each kernel. These show up in a GRUB sub-menu called “Previous Linux versions.” You will see
submenu "Previous Linux versions" {...}
in grub.cfg. For some reason, adding the --user "" option does not actually password-protect the recovery modes of previous Linux kernels. The solution is to either uninstall all but one kernel, or just comment out the entries in grub.cfg. When commenting out, make sure to comment out the right number of closing curly braces. You can also comment out the entire “Previous Linux versions” submenu. Unfortunately there is no obvious way to keep this submenu from being created in the first place.

Now just save grub.cfg, and reboot into GRUB to check that it all worked. I have no idea on how GRUB will respond to a malformed grub.cfg file, so be very careful when editing it.

Cleaning up GRUB
Since you're already messing around with all the configuration files, now is a good time to clean up the GRUB menu. Here are three ideas.

Remove old kernel versions
Unless you're a developer, there's really no reason to have the ability to boot previous Linux kernels. In grub.cfg, comment out the bit that starts with
submenu "Previous Linux versions" {
Again, make sure to comment out the right number of closing curly braces.

Remove other operating systems
You might have other operating systems on your computer that you don't plan to boot using GRUB. For example, you might have followed this guide to triple-boot a Mac. You could comment these out in grub.cfg, but the better option is to use /etc/grub.d/30_os-prober. You could rename the file to something like no_touchy_30_os-prober, take away the execute privileges with chmod, or move it somewhere else. All three options will keep other operating systems from showing up in the GRUB menu. Note that this is an all-or-nothing step; there's no way to choose which other OSes to list and which ones to ignore.

Decrease GRUB timeout
When you're booting up, there's no reason to sit at the GRUB menu for half a minute. In /etc/default/grub, change the GRUB_TIMEOUT value to something like 5 or 1 or however many seconds you like. If you need to, you can always stop the timer in the GRUB menu by hitting an arrow key.

Conclusion
Out of the box, GRUB 2 on Ubuntu can be unwieldy and a bit messy. There are many configuration files in various places that control GRUB, and it can be difficult to keep track of them. It's possible to lock down GRUB and clean up the GRUB menu just by using the configuration files, but to really control GRUB, you need to break the configuration interface.

I'm not sure how much of the configuration process is designed by the Ubuntu developers, and how much is designed by the GRUB developers. I can see that the developers are trying to organize the configuration process to make it more user-friendly, but I don't think it's working as well as it could. Admittedly, GRUB 2 is still in active development, but I'm not convinced that it's even possible to extend the current configuration infrastructure to provide both simplicity for normal users and flexibility for power users.

Sunday, January 29, 2012

Bash One-Liner and Function to grep PDFs


Sometimes you need to find some text in PDF files. The problem is that PDFs are often compressed, making standard grep useless. Spotlight in OS X keeps PDFs indexed, and there might be something similar in Windows. There is no obvious way of doing a simple search of PDFs in Linux, however.

My solution is the following bash one-liner that lists all PDFs in a directory and subdirectories that contain a search term. It uses the pdftotext utility from Xpdf (here, no compilation needed). Both search terms and PDF file names can include spaces.

$> find . -iname "*.pdf" -print0 | xargs -n1 -i -0 bash -ci 'if [ `pdftotext "{}" - | grep -ci "<search term>"` != 0 ] ; then echo "\"{}\"" ; fi ; '

Just replace <search term> with what you're looking for (and make sure you don't miss any of the spaces—they're important).

An even better option is to add this function to your .bashrc file:

function fpdf ()
{
acroread &
find . -iname "*.pdf" -print0 | xargs -n1 -i -0 bash -ci 'if [ `pdftotext "{}" - | grep -ci "$0"` != 0 ] ; then echo "\"{}\"" ; fi ; ' "$1" | xargs -n1 acroread
}

Now running $> fpdf "search term" in a directory will use acroread to open all PDFs in the directory and subdirectories that contain the search term. You can, of course, replace acroread with any PDF reader of your choice.

How does it work?

First, acroread is started as a background process. This keeps every subsequent invocation of acroread from blocking the search.

Next, bash replaces $1 with the search term.

Then, find . -iname "*.pdf" -print0 does a case-insensitive search for all files in the current directory that end with .pdf and prints them out as a NUL-separated list. This list is piped to xargs. The NUL-separated list is better at keeping file names from interfering with xargs than a new-line separated list.

Next, xargs runs bash once for each of the PDF files:
bash -ci 'if [ `pdftotext "{}" - | grep -ci "$0"` != 0 ] ; then echo "\"{}\"" ; fi ; ' "$1"
xargs replaces both instances of {} with the name of the PDF file; bash replaces $0 with the contents of $1. The command executed by the inner bash instance, then, is:
if [ `pdftotext "<a doc.pdf>" - | grep -ci "<search term>"` != 0 ] ; then echo "a doc.pdf" ;fi ;

The section in backticks (``) is executed first. pdftotext dumps the contents of the PDF in plain text. The text is piped into grep, which counts how many times the search term shows up. If the result is not zero, the name of the PDF, surrounded by quotation marks, is echoed to standard out. The result is a new-line separated list of double-quoted PDF file names.

Finally, this list is piped to the second xargs, which calls acroread for each file. The result is one instance of acroread that has open all files that match the search term.