Copyright © 2001 Data Deliverance Pty Ltd
This README, patch and associated utility programs (if any) are released under the GNU Public License Version 2.
Dynamic symbolic links are symlinks that do not point to a fixed location. A normal symbolic link refers to the particular location you point it at. If you do:
# ln -s tmp /mytmp
Dynamic symlinks, on the other hand, take as the "file" to point to, a more complex specification, and may actually point to several different files, depending on the environment of who is accessing them.
So, for example, you may have a symlink which points to /tmp if root is following it, or /nonroottmp if another user is following it, like this:
# ln -s ///root/=tmp=nonroottmp /mytmp
Some dynamic symlink types will also create the target if it is not already there. This can be useful in types such as the uid type where the name of the destination may be different for each user accessing it.
For instructions on how to use this to solve /tmp race condition problems, click here. It is recommended that you also have a glance at the security discussion.
Note: for the Linux kernel 2.2 version, these symlinks work only for ext2 filesystems. This is the type of filesystem that is normally installed on your machine for local files. They will not work across NFS or other filesystem types. The upcoming kernel 2.4 version provides dynamic symlinks more generically.
Dynamic symlinks comes as a kernel patch. You can also order a CD containing a fully compiled kernel and modules, with automatic instaler. Click here for more information.
To install the kernel patch you will need:
This should be in /usr/src/linux. If it is not there, it should be available as a package in your linux distribution. Under RedHat this is an rpm called something like kernel-source-version-number.
A C compilerTry typing "gcc". If you get something like "gcc: No input files" you have a C compiler. Again, this should be available as part of a package in your Linux distribution. Under RedHat, for example there is an rpm called "egcs-version-number".
The "patch" utility.Try "patch --help" and see if you get a verbose list of options. If not, try looking for a "patch" package. RedHat has "patch-version-num".
A recent 2.2 kernel.Use the command "uname -r" to find out what kernel you are running. If you have "2.2.16" (comes with RedHat 7.0) this should work fine. 2.2.14 (RedHat 6.2) and above should be ok too. Your mileage may vary. If you get messages about rejected chunks when you patch the kernel below, you have an incompatible kernel. If you are familiar with the insides of a Linux kernel, you should be able to resolve the difficulties fairly easily. If not, consider upgrading to 2.2.16, or just ordering a pre-compiled kernel.
To install, do the following:
# patch -p1 < path-to-patch-file
This may generate quite a few messages. If you get any errors indicating that patches have been rejected, your kernel is not fully compatible with the patch. If you do not have good knowledge of the inside workings of the kernel, you may be advised to upgrade to a kernel which is supported. See also our consulting services.
Configure the kernelIf you have not customised or compiled your kernel before, you will need to do this step. Type:
# make xconfig
Type these commands to set up and compile the kernel and modules:
# make dep # make bzImage modules
NOTE: These instructions relate to systems running lilo (if you get the "lilo:" prompt when your system boots). For other systems you will need to find out how to safely replace the kernel.
First, rename the old kernel. It should live in /boot, and should be called something like "vmlinuz-some-version" - e.g. "vmlinuz-2.2.16". Rename it to have the extension ".old". It is safe to do this while the system is running. For example:
# cd /boot # mv vmlinuz-2.2.16 vmlinuz-2.2.16.old
Now copy the new kernel into the /boot directory. It should live in your "/usr/src/linux" directory under the "arch/i386/boot" subdirectory (or "arch/something-else/boot" if you are not using a PC), and should be called "bzImage". Copy it over and call it the name of the old kernel before you renamed it. Make sure that you have renamed the old kernel first!! Also make the "vmlinuz" kernel link point to the new kernel. For example:
# cd /usr/src/linux/arch/i386/boot # cp -i bzImage /boot/vmlinuz-2.2.16 # mv /boot/vmlinuz /boot/vmlinuz.old # ln -s vmlinuz-2.2.16 /boot/vmlinuz
Now time to install and set up the kernel modules. Check to see if you have a directory called "2.2.16" in /lib/modules. If so, rename it to "2.2.16.old". Similarly, if you have a file called "/boot/System.map-2.2.16", rename it as well. Copy the file called "System.map" in your kernel build directory to "/boot/System.map-2.2.16". For example:
# cd /usr/src/linux/ # cp -i System.map /boot/System.map-2.2.16
# make modules_install
# cd /boot # mv System.map System.map.old # ln -s System.map-2.2.16 System.map # depmod -a -i -m /boot/System.map-2.2.16 2.2.16
Now back up the file "/etc/lilo.conf" - call it "/etc/lilo.conf.orig" or something like that. This is a critical system file and you are about to make changes in it. If is safe to do this, but make sure you have a backup first. The file should look something like this, plus or minus a few lines:
boot=/dev/hda map=/boot/map install=/boot/boot.b prompt timeout=50 linear default=linux image=/boot/vmlinuz-2.2.16 label=linux initrd=/boot/initrd-2.2.16.img read-only root=/dev/hda5
There may be more than one section starting "image=". If so, find the one with a "label=" parameter matching the "default=" near the top. So for example if your file has "default=linux", you should search for an image section with "label=linux" in it. Normally you will have only one image section anyway. Duplicate the section starting "image=" to the end of the file, or until the next "image=" line.
Edit the old image section, adding ".old" onto the end of the label, and onto the end of the image line. In the example, the file would now look like this:
boot=/dev/hda map=/boot/map install=/boot/boot.b prompt timeout=50 linear default=linux image=/boot/vmlinuz-2.2.16.old label=linux.old initrd=/boot/initrd-2.2.16.img read-only root=/dev/hda5 image=/boot/vmlinuz-2.2.16 label=linux initrd=/boot/initrd-2.2.16.img read-only root=/dev/hda5
Finally, run the command "lilo". If you get any errors, make sure you have copied the file with the correct name, and that there are no mistakes in the /etc/lilo.conf file.
If you cannot get it to work, do not reboot the computer without doing the following:
# rm /etclilo.conf # mv /etc/lilo.conf.old /etc/lilo.conf # cd /boot # rm vmlinuz System.map # mv vmlinuz.old vmlinuz # mv System.map System.map.old # lilo
Assuming you have it working, now reboot your system. If it fails to boot, you can back out of the patch by doing the following:
Creating dynamic symlinks is no different from creating normal symlinks - they just point to strange "locations". A dynamic symlink always points to a "file" starting with "///". This is to distinguish it from a normal path.
The general format of the target for a dynamic symlink is this:
///type/type-specific-data
What type is determines how the data after the following slash is interpreted. There are currently two types available:
A root dynamic symlink can point to one of two different locations. One if the effective uid of the process is root, and the other if it is not. The syntax for this symlink type is as follows:
///root/=root-path=nonroot-path
The working of the symlink is illustrated in this example:
# mkdir /nonroottmp # chmod 1777 /nonroottmp # touch /nonroottmp/nonroot # mkdir /roottmp # chmod 700 /roottmp # touch /roottmp/root # ln -s ///root/=roottmp=nonroottmp /mytmp # cd /mytmp # ls root # /bin/pwd /roottmp # su fred $ cd /mytmp $ ls nonroot $ /bin/pwd $ /nonroottmp
These are much more complicated symlink types, which allow you to point to a different location for each user accessing the link. The only difference between the two types is that with the uid type, the location to use is determined by the real UID of the process accessing it, whereas with the euid type, the effective UID is used. The euid type is probably the one to use in all but a few specialised circumstances.
In discussing these types, we will simply refer to the euid type, but remember that you can use the uid type in exactly the same way.
The format of the euid symlink looks like this:
///euid/[mode]/[uid]/[gid]/path-name
The only required part of this symlink is the last component: the path. When you access the link, this will have the effective UID of the process tacked onto the end. So let's say we make a symlink like this:
# ln -s ///euid////mytmp /mytmp
If you want the UID to be placed in the middle of the path name rather than at the end, you can put an exclamation mark (!) character into the path, and that will be replaced with the UID. So doing this:
# ln -s ///euid////tmp.!.dir/ /mytmp
One other final point to beware of with the path name: the slash before it is not part of the path - it just serves to separate the path from the gid before it. The paths we have been giving are relative: "///euid////mytmp" foo refers to the path of "mytmp". If you want an absolute path, put in an additional slash before the path. So to use "/var/mytmp", do this:
# ln -s ///euid/////var/mytmp/ /mytmp
These symlinks will automatically create a file or directory that they refer to, if it does not already exist. This is done because it's a bit onerous to expect someone to explicitly create a target for each possible UID. The other parameters which we saw, control how this is done:
Note that when the file or directory is created, the create operation is performed with the UID and GID of the owner of the link, not the current process' UID and GID [though with the capabilities of the current process]. This is done to allow the automatic, controlled creation of files or directories in places which a normal user would not normally be able to make them.
For example, you might want each user to have a separate /tmp directory, and set things up, as root, like this:
# rm -rf /tmp # or mv /tmp /tmp/old # mkdir /tmps # chmod 755 /tmps # ln -s ///euid/700////tmps/!/ /tmp
This will give each user a separate directory under /tmps, with the name of the user's effective UID. So root gets "/tmps/0", and fred gets "/tmps/100". Now, you don't want people creating arbitrary directories under /tmps, so you make the mode 755, allowing only root to create directories there. But because the symlink is owned by root, normal users can cause their specific directory to appear under /tmps, but cannot create other directories there.
The only case where this does not take place like this is if the filesystem is mounted nosuid, in which case the file creation is performed as the current user.
File OwnershipNormally, the uid and gid fields of the symlink will be left blank, and the files or directories created will have the effective (or real for "uid" link) UID and GID of the process accessing the link. Even if the symlink is owned by root, and the actual creation happens as root, the owner and group of the files will be set appropriately.
However, you may force the files to be owned by a particular UID and GID, if you have the privileges. See the in-depth security discussion below for more details.
File TypeWhen you are accessing a link, it may not be possible for the kernel to tell if you are expecting it to point to a file or a directory. To make it clear which you want, you must follow the following rule:
If the link should reference a directory, always end the path with a slash.
If it should reference a file, never end with a slash.
# ln -s ///euid/////var/log/mylog. /var/log/mylog # ln -s ///euid/////var/mytmp/!/ /var/tmpdir
Automatic kernel-level creation of files raises many design and security issues. The choices, which are discussed here, have been made with the aim of allowing maximum flexibility, without compromising security.
Creation PrivilegesFirstly, let's look at the issue of what privileges are accorded to the file creation process. A minimalist approach would have been to try to perform the creation simply as the current user. However, this means that it is not possible to have separate directories created in a subdirectory that cannot be tampered with with except by root.
To allow this scenario, it was necessary to cause files to be created by a user other than the current user. To use root always, would have caused these symlinks to be "privileged", and some restriction would have had to be put on their creation, which could have become messy very quickly.
The decision was made to use the owner of the link, so that
This is mitigated to a considerable degree however, by the fact that the files created by the other user will not be owned by the victim, as discussed later. Note also that the capabilities used to create the file are still those of the current user - only the UID and GID are temporarily changed, and are changed back before the end of the system call.
On filesystems (if any) that support normal users giving away files using chown, this "set-uid" symlink behaviour could be used to create files and directories in arbitrary locations, when a user makes a symlink then chowns it to root, for example. However, all the names would have to contain the UID of the user, and not already exist, and would be owned by the user. It seems unlikely that this could be used as the basis of a security attack; perhaps the worst that could be done is to fill a partition by creating a large file. However, since the file could not be deleted (no write permission on the directory above), and would be owned by the user concerned, tracking down the perpetrator (and taking appropraite action) should not be very difficult.
No filesystems examined (including ext2) appear to support users chowning files. Note however that the same issues apply to a normal user granted the CAP_CHOWN capability.
Perhaps the chown code could somehow disable the setuid properties of a symlink that is chown'ed. This is probably not a large issue currently, as all capabilities are normally handed out all at once or not al all; nevertheless it is an area which could probably use improvement.
File OwnershipWhen the file/dir has been created, it is normally given the [e]uid and [e]gid of the current process. To do this, the process is given, for the only the duration of the internal kernel chown call, the CAP_CHOWN capability. However, a link can be created which requests that a fixed UID and GID be given to a file. In this case, to prevent security problems, the CAP_CHOWN capability may not be granted to the process. CAP_CHOWN is not given if:
This means that whilst a normal user can create a symlink which requests a file to be created as UID/GID 0/0, it will not in fact be created like that unless either the user is in fact root, or aready has CAP_CHOWN capability.
The plan below uses dynamic symlinks to eliminate the existence of a globally writable /tmp directory, and replace it with automatically created individual /tmp directories for each user, writable only by that user. Since these directories are writable only by one user, hijackers cannot create smybolic links to cause poorly written programs to overwrite system files.
Here is one way of setting this up. It involves each user having a /tmp directory local to each system. It is highly recommended that you do this with the system in single-user mode, or with the normally running daemons shut down. This is because you will be replacing one /tmp with several (empty) ones. If a daemon is referring to a file in /tmp, and it suddenly disappears, things might go awry.
NOTE: Programs such as tmpwatch that scan /tmp for old files, or any other program that assumes that there is just one /tmp directory, will not work correctly with this setup.
The steps to set this up are outlined below:
In the next step you will be creating a symlink called "/tmp". The original /tmp cannot co-exist with this. If you have no files in it that you want, you may decide to simply remove it. Alternatively, you might want to give it to one user - root, for example. If you want to do this, rename it for the moment to /tmp.orig:
# mv /tmp /tmp.orig
Call it what you want - here we call it /tmps:
# mkdir /tmps
In the example, the new tmp directories will be created with mode 755 (readable by everyone, writable by the owner only. For extra security, change the "755" to "700" below.
# ln -s ///euid/755///tmps/!/ /tmp
To do this, rename /tmp.orig to /tmpsuid-of-user. If, for example, you want root to have the old /tmp, do this:
# mv /tmp.old /tmps/0
# cd /tmp # /bin/pwd /tmps/0 # su fred $ cd /tmp $ /bin/pwd /tmps/100