Linux Kernel Module CHDIR HOOK - By Michael Rywalt Operating System Concepts [DESCRIPTION] CHDIR HOOK limits or restricts a chosen user from leaving or accessing, a particular directory in the filesystem. It also allows monitoring of all chdir activity on the system with or without limitations or restrictions on a user. The CHDIR HOOK module was designed for kernel versions 2.6.x. Kernels from version 2.6 and above no longer export the sys_call_table[] array making it more difficult, if not impossible, to successfully and/or portably modify the system call table at runtime using modules. The sys_call_table[] array contains links to system calls exported by the kernel. By making the links available, we are able to redirect all processing through our own intermediate functions where we can add our own logic to decide the outcome of the syscall based on criteria such as user ID, group ID, or other parameters supplied to the syscall. Those familiar with MS or PC DOS vector interrupt hooking will appreciate the similarities under Linux. Simply assign a dummy function pointer to the original system call, set the pointer in the sys_call_table to point to our new function, and then call the old function from within our new function to complete the system call, once we filter the data in some way that we would like. Unlike MS Windows API hooking, Linux system call hooking is a relatively easy and intuitive process, even though kernel versions 2.6 and above make finding the system call table more difficult. This particular kernel module uses a non-portable, yet generally accepted solution, to find the sys_call_table[] array by searching a range of possible addresses in memory, and performing comparisons for exported entries in the table. We offset our iterator pointer by a number of known entries into sys_call_table[] and compare this address with the address of a table entry that is known to be exported by the kernel at that offset. If and when we have a match between the two, merely removing the index from our pointer reveals the starting address of the sys_call_table[]. Since this is currently a course project, the effort was limited to making this work at a very basic level. Therefore, it should not be used where security is a real issue. This is merely a demonstration to show that it is possible to lock users out of, or within, directories based on a combination of the directory name and the user ID, and by hooking system calls using a kernel module. There are many very obvious ways to subvert the restrictions that this module imposes on the target user. Therefore, this module should be considered an early step toward building a much more complete security solution. The only platform that this has been tested on has been the Mandriva Linux virtual machine image that opens within the VMWare player, which was provided to the CSE4001 OS Concepts class at Florida Institute of Technology. [BUILDING] To build the CHDIR HOOK module, just issue a 'make' command from the directory containing both the Makefile and the source file, project5.c. If the build is successful, there should be a number of new files in the current directory. One of the files should be called project5.ko. This is the kernel module. After you have successfully built the module, proceed to the usage section below for important usage information, including how to load and unload the module to and from memory. [USAGE] Since CHDIR HOOK is a kernel module, it is not started like an executable. To start CHDIR HOOK, you must be root. You should also have at least one other user account on your system for a successful demonstration to take place. (WARNING: Do not apply restrictions to the root user unless you don't care about your system, as system processes that run as the root user will be unable to navigate a filesystem with a CHDIR HOOKed kernel, regardless of who the current user is). From the same directory as the kernel module, you will use insmod to load the module and optionally provide arguments. [A word about default arguments: If you do not supply any of the arguments that are shown in the following examples, default arguments are supplied to the module. The default values are "mrywalt" for the username, 500 for the userID, and 2 for mode. Always make sure the username corresponds to the correct userID value. This can be obtained from the /etc/passwd file. The mode arguments are as follows: 0 - Restricting mode, 1 - Limiting mode, 2 - Monitor mode (see all below).] Let's assume that there is a user on the system called "mrywalt" who has a user ID of 500. Even though mrywalt and 500 are default arguments, we will supply them as actual arguments for demonstration purposes. Let's start by limiting mrywalt to /home and /home/mrywalt only. =Limiting To...= If we want to start CHDIR HOOK to prevent mrywalt from leaving his home directory (the more practical use of this module), start the module in Limiting mode with the following arguments: [root@host]# insmod project5.ko username="mrywalt" userID=500 mode=1 [root@host]# Now, when user mrywalt tries to navigate outside his home directory, he will receive a permission denied message, like this: [mrywalt@host]$ cd / -bash: cd: /: Permission denied [mrywalt@host]$ In fact, the only locations mrywalt is allowed to visit are /home and /home/mrywalt. Even subdirectories that mrywalt creates will be unavailable to mrywalt. We have locked this user into a very restricted location. Or have we? [Note however, that mrywalt is still able to access files anywhere on the filesystem by supplying the relative path in the filename, or in the case of executables, by just typing the name if it already lives somewhere in the user's PATH. This is precisely where security ends and where more secure extensions and additions to this module would have to begin in order to truly limit the user to a 'jailed' environment.] =Unloading the Module= For the next example to properly work, we will need to unload our module before proceeding. To unload this and other modules, we use the rmmod command. this is done in the following manner: [root@host]# rmmod project5 [root@host]# The module should now be unloaded. =Restricting From...= We can also attempt to keep mrywalt out of his home directory, or even the home directory itself, by setting the mode argument to 0. Observe: [root@host]# insmod project5.ko username="mrywalt" userID=500 mode=0 [root@host]# Now, let's say user mrywalt was currently working from /usr. When he tries to change back to his home directory, either by typing 'cd' or 'cd /home/mrywalt', or one of the derivatives, he will be prompted with a 'permission denied' message, like this: [mrywalt@host]$ cd -bash: cd: /home/mrywalt: Permission denied [mrywalt@host]$ =Monitoring Users= The module can also be used as a tool to monitor where users are changing directories to. Since all output is logged to the system log monitor, actively tailing that file will allow you to monitor what users are doing in realtime, and even watch as restricted users are denied access to your chosen directory. To do this, we are going to assume your system log is located at /var/log/messages. All output from the module is preceeded by the 'Project5' title. First, let's start the module in Monitor mode. This way, we will not place any restrictions on users, but will be able to see who is changing to what directory: [root@host]# insmod project5.ko mode=2 [root@host]# tail -f /var/log/messages Apr 11 01:18:49 localhost kernel: Project5: Module starting... Apr 11 01:18:55 localhost kernel: Project5: Allowing user 500 to chdir to / Apr 11 01:18:59 localhost kernel: Project5: Allowing user 500 to chdir to /usr Apr 11 01:19:05 localhost kernel: Project5: Allowing user 500 to chdir to /etc Apr 11 01:19:10 localhost kernel: Project5: Allowing user 500 to chdir to /usr/local/bin Apr 11 01:19:33 localhost kernel: Project5: Allowing user 500 to chdir to /etc/security Apr 11 01:19:56 localhost kernel: Project5: Allowing user 0 to chdir to / Apr 11 01:20:02 localhost kernel: Project5: Allowing user 0 to chdir to /home/mrywalt/tmp Apr 11 01:20:06 localhost kernel: Project5: Allowing user 0 to chdir to /usr/src/linux Apr 11 01:20:14 localhost kernel: Project5: Allowing user 0 to chdir to /etc/init.d ^C [root@host]# As shown, we can see that the user with ID 500 (which, in our case happens to be mrywalt), changes to various directories on the system. We can see that the last directory he changed to was the /etc/security directory. We then see that user 0, or the root user, migrated around the filesystem to a variety of locations. For the next example to work, please unload the module now before proceeding. Here is a sample output listing, where we now start our module in Limiting mode: [root@host]# insmod project5.ko username="mrywalt" userID=500 mode=1 [root@host]# tail -f /var/log/messages Apr 11 01:20:48 localhost kernel: Project5: Module starting... Apr 11 01:21:24 localhost kernel: Project5: Denying user 500's changedir request to / Apr 11 01:22:25 localhost kernel: Project5: Denying user 500's changedir request to /usr Apr 11 01:22:25 localhost kernel: Project5: Denying user 500's changedir request to /usr/ Apr 11 01:22:39 localhost kernel: Project5: Allowing user 500 to chdir to /home ^C [root@host]# As demonstrated here, we started the kernel module in Limiting mode. This prevents the user from changing out of his or her home directory, or the home directory itself. We see the module starting, and then we see the module intervening and preventing the user from changing to his requested directory, which was /. Mrywalt then tried to switch to /usr, but was not allowed (we see two entries since the caller will process a request with and without a leading /). Finally, we see that mrywalt decided to move back to the home directory, which we do permit. [IMPLEMENTATION] CHDIR HOOK works by hooking the chdir entry in the sys_call_table[] array within the kernel's memory space. Since this has been found to be a dangerous practice, Linus and company decided to remove the export for sys_call_table[] in subsequent versions of the kernel, starting with version 2.6. In order to find the table in memory, a trick is played by making a guess as to where the table is located. By searching between two known exports which are probably located around the target entry, we may land on the table. This is not a guaranteed process by any means. It may very well differ across different machines that run linux, even if they are the same kernel version, as the locations are determined at compile time. There is another way to find the sys_call_table[], which is also not a guaranteed answer. Using the System.map file that is generated when building the kernel, it is possible to find an address for sys_call_table entry by merely inspecting System.map with a standard text editor such as vi. This plaintext file contains a map of where each symbol should be located in memory. [LICENSE, ETC] You are allowed to copy, redistribute, modify, and/or use this software under the conditions that you give full credit to me, Michael Rywalt, and that you let me know that you would like to copy, redistribute, modify, and/or use this software. If you find this software useful, please let me know by emailing me at mrywalt (at} gmail {dot] com.