Industrial-Strength Linux Lockdown, Part 1

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 19

Industrial-strength Linux lockdown, Part 1:

Removing the shell


The first step is disabling command interpretation

Skill Level: Intermediate

Gary V. Vaughan (gary@gnu.org)


Freelance open source developer and technical writer
Azazil

23 May 2007

For technical and non-technical users alike, maintaining a large installed base of
Linux machines can be a harrowing experience for an administrator. Technical users
take advantage of Linux®'s extreme configurability to change everything to their
liking, while non-technical users running amok within their own file systems. This
tutorial is the first in a two-part series that shows you how and why to lock those
machines down to streamline the associated support and administration processes.
In this tutorial, you learn how to remove the interpreters from the installation base
system.

Section 1. Before you start


In addition to the machine on which you're reading this tutorial, you need an old
Linux installation that you don't mind breaking, preferably with a rescue disk in case
something goes wrong. If you have any data you might ever need to get back to on
that machine (even if you follow this tutorial on a different partition or on a separate
drive using a multi-boot setup), you'll need to make and test a full backup of that
data before you try any of the techniques described here.

About this series


From an installation or support manager's point of view, its capacity for modification
is perhaps one of Linux's greatest source of problems; anyone who has been given
responsibility for any kind of medium- to large-scale installation has at least looked

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 1 of 19
developerWorks® ibm.com/developerWorks

over the edge of that precipice. Every machine may have been radically tweaked
with additional applications, configurations, and installations of unknown software.
This series of tutorials is for anyone who has ever wanted to have the ability to
painlessly manage and install such a large-scale Linux installation across an
enterprise.

About this tutorial


This tutorial gives some good reasons for keeping Linux's customizability under
control and takes some first cautious steps toward locking down a standard Linux
distribution to prevent spurious user changes to the baseline installation. It lays the
groundwork for Part 2 in this series, which completes the lockdown process by
building a kernel that enforces the use of only signed binaries that have been
introduced in a controlled way to each machine that must be supported.

Objectives
In this tutorial, you learn about some of the security issues that you must consider
when supporting a large-scale Linux installation and how to head them off before
they cost additional effort to put right. You will see how to set up the hardware and
firmware to prevent basic tampering in the first instance; ultimately, you will take
away the standard Linux interpreters to minimize the risk of users running unaudited
code in your secure environment. By the time you've finished the series, you'll be
able to configure a industrial-grade, locked-down Linux distribution that cannot be
injected with applications that you have not personally audited and signed off.

Prerequisites
This tutorial is written for Linux administrators whose skills and experience are at an
intermediate to advanced level. You should have good familiarity with the Linux boot
process, be comfortable with a command-line shell, and possess a working
knowledge of the C programming language.

System requirements
To follow the steps in this tutorial, you must have root access on a Linux machine
with the ability to reboot the box at will and to destroy all the data stored on it. You
must have an installed compilation environment and a way to get your distribution's
Linux kernel sources and headers as well as special tutorial source code (available
from the Download section) and the freely available dash utility for your flavor of
Linux. (You can use the Debian Linux version of dash , the Ubuntu Linux version of
dash , or whatever version is appropriate to your Linux installation.)

During the development of this tutorial, I used Ubuntu Linux V6.10 installed from a
live installation CD, although except in the finer details, any Linux distribution you're

Removing the shell


Page 2 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

comfortable with should be fine. If you have access to a copy of VMware and don't
need to try the hardware and firmware sections of the tutorial, VMware's snapshot
utility allows you to experiment more freely, because you can go back in time to a
known good state if the Linux installation stops booting at any stage without
resorting to a rescue disk to diagnose and repair the problem.

Section 2. Locking down the machine


Before diving in and removing opportunities for a careless (or determined) user to
radically alter the setup of a Linux machine you administer, it's worth considering
why this is even a useful exercise.

Linux in a secure environment


At the heavyweight end of the scale, many organizations with an interest in
maintaining a secure computing environment (particularly those in the defense
industry) are compelled to use Microsoft® Windows®, in part because of the
perceived difficulty of controlling the software that users can install and run in a
Linux environment. A great deal of useful free software beckons to be downloaded,
compiled, and installed from the Internet or smuggled in on a USB flash drive by
enterprising users. That can consume a substantial chunk of your support time as
well as the time the user loses while he or she isn't performing normal duties. It is, of
course, nonsense to reject Linux out of hand like this when you can simply install
and configure it in such a way as to prevent the introduction of foreign software to
the secure machines in the first place.

Particularly in a tightly controlled environment, it can be a business prerogative to


require that there be an audit trail for every piece of software used on the system
along with every security update and patch installed on every machine in that
environment. To be able to achieve this kind of oversight effectively, you must
monitor the avenue for changing any software introduced into the installed base
closely so that you have a totally accurate picture of every line of code that could be
run on any machine. Achieving this need not be as difficult as it sounds when there
is some means of preventing each machine from executing any code that has not
passed through the proper channels. This tutorial discusses how to close off other
avenues of introducing foreign code to a locked-down machine. In Part 2 of this
series, you will implement cryptographic signing of authorized executables to
prevent execution of anything that has somehow slipped through the net.

Linux in a business environment


Perhaps your organization has to perform lengthy integration testing on in-house
software before the final application can be released for live use. Payroll applications

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 3 of 19
developerWorks® ibm.com/developerWorks

or worse, airline flight-control software, fall into this category. Having Linux reject
execution of unsigned or badly signed binaries is an excellent and effective means
of preventing enterprising testers from implementing a hotfix and recompiling the
offending part of the system, then forgetting to pass that fix back through the correct
channels to be integrated into the next build for thorough testing.

Most likely, even this level of lockdown represents complete overkill for your
organization. Yet, it can still offer surprising advantages even where the security and
audit trail itself is not your primary concern. By implementing this type of low-level
machine lockdown, you can avoid some typical workplace problems simply by
removing the enabling applications. For example, if a user's professional role within
the organization doesn't require access to intranet documents, remove all the Web
browsing software from that person's machine; to prevent users from introducing
illegally downloaded music into the network, remove MP3-playing software from all
machines.

Linux in a server room


Similarly, you can continue to use old, underpowered hardware for specific tasks by
removing all but the task-oriented application binaries. If many users in the
organization need login access to a file server, you can remove all the binaries that
aren't required for serving files. Users will still be able to access the files they need,
but they can't accidentally start up a processor-intensive X application that prevents
other users from getting their files in a timely fashion.

You will even find a use for some of the techniques described in this tutorial series if
you're a busy systems administrator responsible for supporting the Linux
installations of a team of non-technical users. By performing a system lockdown,
your users won't be able to introduce broken or version-mismatched software to their
desktops, nor will their machines be capable of executing unauthorized software.

If you can see yourself in these scenarios, you'll find much of interest here.

Section 3. Securing the hardware


Before even considering using the network to load software onto a Linux machine,
you must identify any other means by which unaudited code can be introduced to
the system. With full access to the hardware, a user can easily sidestep using the
network.

Secure the hardware


The most obvious means of sidestepping the network come from a CD or DVD or

Removing the shell


Page 4 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

even a floppy disk. Usually, after you've used the optical or floppy disk drive to install
the operating system, there's really no business reason for users to use these drives
on a day-to-day basis. When buying new machines for a secure network, it's an
unnecessary security risk to have them built into the machine at all. Not only can
they be used to bring unauthorized files into the network, but they're the easiest
means of transporting data off the network.

One of the many advantages of Linux, however, is getting away from the relentless
operating system-driven hardware-upgrade cycle, so chances are that most of the
machines will have these drives installed nonetheless. Physically removing the
drives isn't necessarily the easiest option; instead, you can remove the appropriate
drivers from the installed kernels to render the drives unusable after booting.

Adjust the kernel parameters


On Ubuntu Linux, ensure that you've installed the linux-kernel-devel and
linux-source packages to install all the packages needed to recompile a custom
kernel for yourself. If you haven't built your own kernel before, don't worry: This is
about the safest possible way to do it, starting with the configuration of your currently
running kernel. But before you can do that, you must install the linux-source and
linux-kernel-devel packages so that you have all the Linux sources and tools. Listing
1 shows how to determine exactly which configuration your running kernel was built
with, then copies that configuration into the kernel source directory ready to be used
for your recompilation.

Listing 1. Copying the configuration of the running kernel

$ cd /usr/src
$ tar jxf linux-source-2.6.17.tar.bz2
$ cd linux-source-2.6.17
$ uname -r
2.6.17-11-generic
$ ls /boot/config*
/boot/config-2.6.17-10-generic
/boot/config-2.6.17-11-generic
$ sudo cp /boot/config-2.6.17-11-generic .config

First, however, remove all the CD and floppy disk drivers, which you can find by
using the kernel menuconfig utility. With the Ubuntu V6.10 kernel, that amounts to
making the changes in Listing 2 to your new .config file.

Listing 2. Linux kernel configuration to remove CD and floppy disk drivers

# Remove Standard Floppy device driver


CONFIG_BLK_DEV_FD=n
# Remove Parallel Port IDE support
CONFIG_PARIDE=n
# Remove CDROM Packet device
CONFIG_CDROM_PKTCDVD=n
# Remove support for IDE/ATAPI CDROM, TAPE and FLOPPY
devices

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 5 of 19
developerWorks® ibm.com/developerWorks

CONFIG_BLK_DEV_IDECD=n
CONFIG_BLK_DEV_IDETAPE=n
CONFIG_BLK_DEV_IDEFLOPPY=n
# Remove support for CD Filesystems
CONFIG_ISO9660_FS=n
CONFIG_UDF_FS=n
# Remove USB and Memory card device drivers
CONFIG_USB=n
CONFIG_MMC=n

Note that this isn't a complete configuration. It merely shows some of the device
drivers you can remove from the kernel you're about to build. You should run make
menuconfig from the source directory and use that interface to turn off these things
so that all the options that depend on the things you've removed are also disabled
correctly. There is much more that you can do at this stage to improve the speed
and security of your kernel, especially if you're familiar enough with the hardware on
which your kernel must run and can remove all the drivers you know you'll never
need.

Remove other, non-essential drivers


In addition to removing support for floppy disk drives and optical drives from this
build, you can remove support for CD file systems and for USB flash drives (in case
some of your machines have memory card readers that people might use to
smuggle files into the network). For my particular network, I know that there's no
legitimate need for USB devices, because my organization uses networked printers
and backup storage. Therefore, I turned all of that off, too. The USB subsystem of
the kernel is highly configurable though, and it's possible to have the drivers load but
only accept certain classes of device, should you need to support USB printers or
fingerprint readers without opening the machine to USB flash drives.

If you're already familiar with kernel configuration and you can confidently remove
the majority of the modular drivers that you know you won't need, you could even try
to build a kernel that has no support for loadable modules. A kernel built in this
fashion won't be able to load kernel modules, preventing the insertion of foreign
code into the running kernel without recompiling again, something I touch upon
again in Part 2 when you configure the kernel to run only correctly signed binaries.

When you're happy with your configuration, follow the instructions in the kernel
README file to build and install it. Having successfully booted the machine with the
new kernel and satisfied yourself that it works well, you'll need to come back later to
remove the old kernel (and modules) as well as their entries in the boot-loader
before making a disk image to install on your other machines. For now, at least while
you're playing with the system configuration, it would be wise to leave the old kernel
around in case you need to use it for temporary access to any of the drivers you've
removed.

Removing the shell


Page 6 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

Section 4. Locking the BIOS


At this stage, another avenue still open to users who are determined to install
additional software on the machine is to boot the machine from a rescue floppy disk,
a live CD, or even a suitably formatted bootable USB flash drive. Although the
booted kernel is now unable to access any such devices, it would be easy to bypass
all your good work by not booting that kernel in the first place.

Allow only hard disk booting


BIOS setup
You typically access the BIOS setup windows on most modern PC
hardware by pressing Delete or F2 as soon as the machine is
powered up. If you have a very old or otherwise unusual machine,
you might need to check your motherboard specification at the
manufacturer's Web site for details.

The only way to forestall this eventuality is to go into the basic input/output system
(BIOS) setup and ensure that the machine is configured to boot only from the correct
hard disk drive and to turn off USB, floppy disk drive, and CD-ROM booting entirely.
You must then set the BIOS password to stop anyone from changing these settings
back temporarily.

At this point, the only way to get this machine to boot from a different kernel or to
access data on an external drive that you haven't configured into your new kernel
build (except for the copy of the old kernel you've temporarily left behind for
emergencies) is with a screwdriver! If you also need to hold burgeoning hardware
engineers from installing an alternative bootable hard disk from which to transfer
data, there are various lockable cases and attachments that you can buy when you
purchase the hardware or as after-market add-ons.

Other means of injecting foreign files


Be aware that there's no way to completely prevent a suitably determined intruder
from gaining access to the machine. So, carefully weigh the cost of purchasing,
installing, and managing each additional security feature against the risk of not doing
so. Some of the many avenues for introducing foreign files into the system that I
haven't covered here but that you might care about include:

• Bluetooth file transfer.


• Ethernet cross-over cable.
• Wi-Fi networking.

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 7 of 19
developerWorks® ibm.com/developerWorks

• Using a router to introduce a foreign machine to the network.


• Manually copying a program's executable code.
I'm sure there are many more, but at some point along the scale, the effort involved
in protecting against these attacks is disproportionate to the risk they present.

Section 5. Removing the shell


An interpreter, or scripting engine (actually, there are several in a typical Linux
installation), provides endless scope for a user to execute unchecked, untested, or
unauthorized code on your critical locked-down machine. From a security point of
view, the problem with scripting engines is that the their executable code is the
actual source code of the program being executed: With a scripting engine installed
on the machine, a malicious user really can manually copy the text of a script from a
printout and be able to run that code! Luckily, because of Linux's extreme
configurability, it's not too hard to remove all the interpreters from the system,
including the various UNIX® shells, and still be left with a useful machine.

A selection of Linux interpreters


The first and easiest stage in the process of preventing user access to the various
interpreters on the system is to remove them. If only it were that easy! It is highly
likely that, for at least some of your users, there will be a strong business need to
have access to applications that are implemented in some part with one of the
troublesome Linux scripting engines. If you don't have a list of those applications yet,
now is the time to collate all the applications to which your users need access.

On the base installation machine you're configuring, remove all the interpreters from
the machine with the distribution package manager, then try to run each application
on the collated list to determine whether any of them are written with a scripting
engine. If you're very lucky, all the applications will run unaided. More likely, you
must apply some of the techniques described in the rest of this section to get the
interpreted programs to run stand-alone. On Ubuntu Linux, I was able to safely
remove the following interpreters and still boot into a GNOME desktop from
power-up:

• Bash
• Gawk
• Guile
• M4

Removing the shell


Page 8 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

• Mono
• Perl
• Python
• Ruby
• Tcl

Instrument the shell


In any case, one of the first things Linux does when the kernel has booted is execute
the so-called init scripts, sometimes stored in /etc/rc.d (or some variant of that path)
or in /etc/init.d (as is the case with Ubuntu Linux). All of these scripts are handed to
/bin/sh for actual execution, so you can't simply remove that shell from the system or
you'll render the machine incapable of booting.

Before removing any of the interpreters above, especially /bin/sh, move the
interpreter to one side and replace it with a simple wrapper program that first logs
each execution to a file before handing control back to the moved interpreter. Listing
3 presents a simple program to do just that.

Listing 3. wrapper.c, a basic logging wrapper program in C

1 #include <pwd.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6 #include <unistd.h>
7
8 #define LOG_FILE "/tmp/wrapper.log"
9 #define LOG_FLAGS O_CREAT|O_APPEND|O_WRONLY
10 #define LOG_MODE
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
11 #define WRAPPED_PROG "/bin/sh-"
12
13 int
14 main (int argc, char **const argv)
15 {
16 FILE * logfile = fopen (LOG_FILE,
"a");
17 const char * program = argv[0];
18
19 argv[0] = WRAPPED_PROG;
20
21 if (logfile)
22 {
23 struct passwd *pw = getpwuid (geteuid
());
24 int i = 0;
25
26 fprintf (logfile, "(%s)", pw->pw_name);
27 for (i = 0; i < argc; ++i)
28 fprintf (logfile, " '%s'", argv[i]);
29 fprintf (logfile, "\n");
30
31 fclose (logfile);
32
33 /* First writer must set global permissions */
34 chmod (LOG_FILE, LOG_MODE);

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 9 of 19
developerWorks® ibm.com/developerWorks

35 }
36
37 execv (argv[0], argv);
38
39 /* Not reached. */
40 exit (EXIT_FAILURE);
41 }

This little program contains some tricky parts:

• Line 8: At line 8, LOG_FILE must be in a directory anyone can write to,


as the wrapped program need not necessarily be executed by the root
user. If your Linux distribution uses tmpfs, you must either disable it or
create another world-writable directory and put LOG_FILE in that
directory.
• Lines 10 and 34: At lines 10 and 34, the program makes sure that the file
it has written to is world writable. Otherwise, other users of /bin/sh might
not have sufficient permissions to append another log line to the file. It
doesn't matter that the chmod call will fail when other users try to write to
the log file.
• Line 37: Although you might like to add better error checking and
reporting while you satisfy yourself that the wrapper program is behaving
properly, when you come to use it at boot time, it's important not to bail
out if any part of the logging process fails; otherwise, execution will never
reach the shell proper at line 37, and the machine won't be able to boot!

Execute a shell wrapper


Listing 4. Installing a shell wrapper

$ gcc -o sh wrapper.c
$ sudo mv /bin/sh /bin/sh-
$ sudo cp sh /bin/sh
$ sudo /bin/sh -c 'echo Hello'
Hello
$ /bin/sh
# exit
$ cat /tmp/wrapper.log
(root) '/bin/sh' '-c' 'echo Hello'
(gary) '/bin/sh'

Here in Listing 4, being careful first to move the original /bin/sh to the exact location
named in line 11 of Listing 3, you can see how to install the wrapper program to the
shell's original location and test that it is working correctly. At this point, you can
reboot the machine to log all the calls the shutdown and reboot processes make to
this shell. After you are successfully logged back in to the rebooted machine, you
should move the original shell back over the wrapper program to prevent any further
spurious log entries.

Removing the shell


Page 10 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

Section 6. Compiling shell scripts


Before you can remove /bin/sh from the system, you must account for each
execution of shell from the log; otherwise, the machine won't be able to boot. In the
first instance, determine whether you can remove that invocation from the machine's
boot process entirely. I had a mail server that the init scripts started at boot time;
rather than figure out how to get it to run without a shell to start it, it's much easier
simply to uninstall it with Ubuntu's package manager and not worry about it any
more.

Remove user login shells


The next-easiest class of shell executions to remove is user login shells. You can
prevent users from logging in with shell access by replacing the relevant entries in
/etc/passwd, as shown in Listing 5.

Listing 5. Deleting user login shells

$ grep gary /etc/passwd


gary:x:1001:1001:Gary V. Vaughan:/home/gary:/bin/false

Notice that although the login shell field has been set to /bin/false, the home
directory must remain for storing the user's application configuration and cache files.
Otherwise, the desktop won't be able to start up after Linux has finished booting.

Rewrite a shell script in C


Some of the scripts executed during boot amount to little more than setting some
variables, looking in a configuration file, and executing a binary. An example of a
simple script of this ilk is Ubuntu's /etc/init.d/keyboard-setup.

In cases like this, rewriting the script in a compiled language is often quite
straightforward. Listing 6 shows the original script, and Listing 7 shows a rewrite in C
that you can compiled as a drop-in replacement that doesn't use /bin/sh.

Listing 6. Ubuntu /etc/init.d/keyboard-setup

1 #!/bin/sh
2 set -e
3 test -f /bin/setupcon || exit 0
4 . /lib/lsb/init-functions
5
6 case "$1" in
7 stop)
8 # keyboard-setup isn't a daemon
9 ;;
10 start|force-reload|restart|reload)

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 11 of 19
developerWorks® ibm.com/developerWorks

11 if [ -z "$DISPLAY" ]; then
12 log_begin_msg "Setting preliminary keymap..."
13 if setupcon -k --force; then
14 log_end_msg 0
15 else
16 log_end_msg $?
17 fi
18 fi
19 ;;
20 *)
21 echo 'Usage: /etc/init.d/keyboard-setup
{start|reload|restart|force-reload|stop}'
22 exit 1
23 ;;
24 esac

Listing 7. keyboard-setup.c rewrite of /etc/init.d/keyboard-setup

1 #include <stdlib.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/wait.h>
5 #include <unistd.h>
6
7 #define streq(a, b) (strcmp ((a), (b)) == 0)
8
9 int
10 main (int argc, char **argv)
11 {
12 pid_t pid;
13 int status;
14
15 if (argc > 1 && streq (argv[1], "stop")) {
16 return EXIT_SUCCESS;
17 } else if (argc > 1 &&
18 (streq (argv[1], "start") || streq
(argv[1], "force-reload") ||
19 streq (argv[1], "restart") || streq
(argv[1], "reload"))) {
20 const char *display = getenv("DISPLAY");
21
22 if (!display || streq (display, "")) {
23 write (STDOUT_FILENO, "Setting preliminary
keymap...", 29);
24
25 pid = fork();
26 if (pid == 0) {
27 execl ("/bin/setupcon", "setupcon",
"-k", "--force", NULL);
28 exit (EXIT_FAILURE);
29 } else {
30 if ((waitpid (pid, &status, 0) != pid)
||
31 (WIFEXITED(status) &&
WEXITSTATUS(status) != 0))
32 write (STDOUT_FILENO, "failed\n",
7);
33 else
34 write (STDOUT_FILENO, "ok\n", 3);
35 }
36 }
37
38 return EXIT_SUCCESS;
39 }
40
41 write (STDOUT_FILENO,
42 "Usage: /etc/init.d/keyboard-setup
{start|reload|restart|force-reload|stop}\n",
43 74);
44 return EXIT_FAILURE;
45 }

Removing the shell


Page 12 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

There is nothing tricky in the Listing 7 code, save that there's no analog to lines 2, 3,
and 4 of the shell script (shown in Listing 6), because those lines aren't necessary in
the C code. For simplicity in this tutorial, I've written the whole program as a single
main() function; in practice, there are dozens of scripts you can rewrite in this
fashion, and it would make sense to put common code (such as the fork/exec
code) into functions you can reuse for each rewritten script.

Embed scripts in a purpose-compiled interpreter


For more complicated shell scripts, rewriting in C can prove a complex project in its
own right, especially where the script is using features that are unnatural to code in
C (for example, using eval or back tick substitution). Ubuntu uses a variant of the
Almqvist shell as patched by the Debian developers, which is extremely small and
fast. This opens the possibility of replacing each complex script with a patched
interpreter that will run only the script embedded within it.

Override argument processing


To see how this replacement works, download the latest sources for dash from the
Debian or Ubuntu Web sites (see Resources) and extract them to their own
directories. Now, you'll need to make a few small changes in the source code to
enable locking a special build of dash to one particular script.

Listing 8 shows a small change to the option processing code so that no matter
which script name you pass to this shell, it will always be overridden by the code you
name in the script_lock parameter. If you don't want to type theses changes
manually, you can download the ready-patched code from the Download section of
this tutorial.

Listing 8. dash-0.5.3/src/options.c changes

119 void
120 procargs(int argc, char **argv, const char
*script_lock)
121 {
...
136 if (*xargv == NULL) {
137 if (xminusc)
138 sh_error("-c requires an
argument");
139 }
...
155 } else {
156 if (script_lock) {
157 setinputfile(script_lock,
0);
158 } else {
159 setinputfile(*xargv, 0);
160 }
...
175 }

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 13 of 19
developerWorks® ibm.com/developerWorks

Listing 9 makes the matching changes in the main loop of dash to make sure that
when you compile it with the SCRIPT_LOCK preprocessor symbol defined,
procargs is called correctly with the additional script_lock argument. Simply
forcing the interpreter to execute a particular script stored somewhere in the file
system wouldn't give you any additional security, as it would be easy for a malicious
user to edit the contents of that script to execute that user's code, which would rather
defeat the purpose of this exercise. At line 72, you include a new file, regurgitate.c,
which in turn will contain the contents of the original script.

Listing 9. dash-0.5.3/src/main.c changes

71 #ifdef SCRIPT_LOCK
72 #include "regurgitate.c"
73 #endif
...
100 int
101 main(int argc, char **argv)
102 {
103 const char *script_lock = 0;
104 char *shinit;
...
113 #if PROFILE
114 monitor(4, etext, profile_buf, sizeof
profile_buf, 50);
115 #endif
116
117 #ifdef SCRIPT_LOCK
118 script_lock = regurgitate ();
119 #endif
120
121 state = 0
...
167 init();
168 setstackmark(&smark);
169 procargs(argc, argv, script_lock);
170 if (argv[0] && argv[0][0] == '-') {
...
213 }

Consume a shell script


Listing 10 shows a script that embeds an external shell script into the interpreter. It
writes the path to that script into the regurgitate.c file that will be included by the
changes you made to main.c above. Lines 19 to 22 convert each line of the
embedded script into a static array of strings along with some glue code to interface
with the regurgitate function.

Listing 10. Eat

1 #!/bin/sh
2
3 scriptfile="$1"
4
5 cat <<EOT
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10

Removing the shell


Page 14 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

11 #define SCRIPT_FILE "`echo $1 | sed 's,^.*/,,'`"


12
13 static char *scriptdir = 0;
14 static char *fullpath = 0;
15
16 static char *script[] = {
17 EOT
18
19 cat $1 | while read line;
20 do
21 echo "\""`echo "$line" | sed 's,",\\\",g'`"\","
22 done
23
24 cat <<'EOT'
25 0 };
26
27 static void
28 fail (const char *msg)
29 {
30 perror (msg);
31 exit (EXIT_FAILURE);
32 }
33
34 static void
35 clean_droppings (void)
36 {
37 unlink (SCRIPT_FILE);
38 rmdir (scriptdir);
39 }
40
41 static char *
42 regurgitate (void)
43 {
44 char template[17];
45 FILE *scriptfile;
46
47 sprintf (template, "%s", "/tmp/barf-XXXXXX");
48 scriptdir = mkdtemp (strdup (template));
49 if (!scriptdir)
50 fail ("mkdtemp");
51
52 asprintf (&fullpath, "%s/%s", scriptdir,
SCRIPT_FILE);
53 if (!fullpath)
54 fail ("asprintf");
55
56 atexit (clean_droppings);
57
58 scriptfile = fopen (fullpath, "w+");
59 if (!scriptfile)
60 fail ("fopen");
61
62 {
63 int i = 0;
64 while (script[i])
65 fprintf (scriptfile, "%s\n", script[i++]);
66 }
67
68 if (fclose (scriptfile) != 0)
69 fail ("fclose");
70
71 return fullpath;
72 }
73 EOT
74
75 exit 0

Regurgitate a shell script


For security purposes, when the embedded script that the eat script consumes is

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 15 of 19
developerWorks® ibm.com/developerWorks

recreated, the mkdtemp call at line 48 creates a randomly generated directory in


/tmp to prevent any user from guessing the location into which the regurgitated script
will be unloaded and intercepting it with his or her own code. The running shell will
own the temporary directory created here, with permissions set to prevent anyone
else looking at the contents of the script inside while it's being executed. An atexit
handler calls lines 34 through 39 to ensure that the temporary directory and its
contents are removed as soon as the shell has finished running them.

Putting this all together in Listing 11, you can now run a shell script needed while
booting or shutting down the machine through the eat script to embed it in the
patched dash sources. Then, you compile those sources to create a one-off
interpreter along with an embedded version of the original script that it will
regurgitate, execute, and clean up.

Listing 11. Testing the embedding process

$ ./configure CFLAGS=-Os CPPFLAGS=-DSCRIPT_LOCK


...
$ cat -n embed-test.sh
1 #! /bin/does-not-exist
2 echo Calling: $0
3 cat $0 | sed 1q
4 exit 0
$ ./eat embed-test.sh > src/regurgitate.c
$ make
...
$ rm embed-test.sh
$ cp src/dash ./sh
$ strip ./sh
$ ls -s ./sh
76 ./sh
$ ./sh
Calling: /tmp/barf-oX547v
#! /bin/does-not-exist

So, you now have a custom 76 KB dash interpreter that will run only its own
embedded shell script. You can use this process to create one-off binaries to replace
the more complicated shell scripts needed to boot and shut down the Linux machine,
at which point you're almost ready to remove /bin/sh itself.

Further study
Some small problems remain with this approach, for which Part 2 of this tutorial
series will offer solutions. With the text of the embedded shell script stored so plainly
inside the compiled interpreter, it would be easy to open the binary in an editor and
change the contents of the script. Obfuscating the code by compressing it, for
instance, would make it a little harder to find the right section to edit, but a
determined user could still copy a binary that embeds a large shell script and replace
the right bits of the copy with a custom compressed shell script, effectively giving the
user access to his or her own shell as if you had never bothered to remove it from
the system in the first place. Another option might be to embed a checksum in the
embedded binary, but again, a determined user could simply replace that checksum
with a new one that matches the new code the user injected.

Removing the shell


Page 16 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

A good way to solve all these problems without resorting to simple obfuscation is to
sign the script and have the patched shell check the signature on the regurgitated
script before executing it. You'll learn how to do this and get more details on having
the kernel execute only signed binaries in the next installment.

Section 7. Summary
With the techniques covered in this tutorial, you see how it's now possible to create a
Linux installation that can boot to the desktop without the need for an interpreter.
Depending on which distribution of Linux you begin with, you'll have more or less
work to remove the shell scripts used to boot the machine. But with a few days to
spare, you'll soon be the proud owner of a industrial-strength, locked-down
installation. The only chink in the armor as it stands is that a user can still download
foreign pre-compiled binaries and use the desktop file manager to run them. In Part
2 of this tutorial series, you'll learn how to close this avenue, too -- first by signing the
legitimate programs that can be used on the machine, and second by changing the
kernel to refuse to execute anything other than correctly signed binaries.

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 17 of 19
developerWorks® ibm.com/developerWorks

Downloads
Description Name Size Download method
Sample files for this tutorial sample_files.zip 236KB HTTP

Information about download methods

Removing the shell


Page 18 of 19 © Copyright IBM Corporation 1994, 2007. All rights reserved.
ibm.com/developerWorks developerWorks®

Resources
Learn
• In the developerWorks Linux zone, find more resources for Linux developers,
including more Linux tutorials, as well as our readers' favorite Linux articles and
tutorials over the last month.
• Stay current with developerWorks technical events and Webcasts.
Get products and technologies
• Download Ubuntu Linux V6.10.
• Download the dash utility for Debian Linux.
• Download the dash utility for Ubuntu Linux.
• Order the SEK for Linux, a two-DVD set containing the latest IBM trial software
for Linux from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
• With IBM trial software, available for download directly from developerWorks,
build your next development project on Linux.
Discuss
• Get involved in the developerWorks community through our developer blogs,
forums, podcasts, and community topics in our new developerWorks spaces.

About the author


Gary V. Vaughan
Gary Vaughan recently left his career as a British government scientist to pursue
freelance technical writing and free software contract work that would allow him to
travel the world, write the follow-up to his first book, GNU Autoconf, Automake and
Libtool, and maintain his blog. He has been involved in the GNU project for nearly 10
years and currently maintains GNU Libtool and GNU M4 as well as contributing
patches to many related free software projects.

Trademarks
DB2, Lotus, Rational, Tivoli, and WebSphere are trademarks of IBM Corporation in
the United States, other countries, or both.
Linux is a trademark of Linus Torvalds in the United States, other countries, or both.
Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft
Corporation in the United States, other countries, or both.
UNIX is a registered trademark of The Open Group in the United States and other
countries.

Removing the shell


© Copyright IBM Corporation 1994, 2007. All rights reserved. Page 19 of 19

You might also like