Files Directory security setuid sticky bit permissions

| 0 Comments | 0 TrackBacks
Understanding Files and Directory permissions, their creation and deletion rules on Unix is one of the most obvious and important topics that many even veteran unix administrators sometime ignore to master. I don't mean the extended stuff. FreeBSD, Linux, Solaris and AIX all have their own extended access control list on top of the standard security they all have in common. 

In this article I'll talk about what the read, the write and the execute bits mean on a file and also on a directory. I will also cover the setuid and the sticky bits. I will then explain what rules does the kernel follow when deleting a file and why is it most often called "unlinking a file" rather than "deleting." In another article I'll cover what hard links and soft links fundamentally are. Some of this stuff such as the read, write and execute bits are easily known by everyone on a file, but not entirely. And I promise that things will get more interesting further down.

I won't get into explaining what user ID's, group ID's and "other" mean for each file because you already know it. When I talk about the read permission bit on a file, it should be known that if the read bit is on the group then any user in that group will have read access and if the read bit is on the "other" (sometimes called world) then everyone with a user account on the system will have read access.

Before we start let's explain what a directory is. A directory in Unix is simply a file and we occasionally call it a "directory file." Except that the directory is a file with a special flag telling the kernel that its content consists nothing but names (a character array in C) and for each name there's a number. This name is the name of a file, a directory, or any other kind of file. The number is a positive integer and it is called the inode. It is the address of the i-node in the file system's i-list. The i-node inside the i-list contains information about the file such as the file's type, the file's access permission bits, the size of the file, pointers to the data blocks for the file and so on. 

unix_file_system.gif

That's how the location of each other file is found. The inode number for a file is looked up inside the directory file. Then the inode structure is fetched from the file system's i-list. In it the start of the data block for this file is read. Notice that the one information that the inode does not contain about its file is the name of the file. That's in the directory file.

The read bit on a file: 
-r - - r - - r - -
  Allows the user to read those blocks of data assigned to this file.

The read bit on a directory: 
dr - - r - - r - -
  Allows the user to execute the opendir() system call to read the directory file. Notice that we are not referring to the inodes yet, as the command ls does without any options. However, if we issue the "ls -l" command then for each file in the directory, its inode is seeked and read so that the file size, permission bits, number of links to the file and so on are displayed. Important to know that anything other than LISTING does NOT require the read bit.

The write bit on a file: 
- - w - - w - - w -
Allows the user to modify the content of the hard drive address blocks assigned to this file.

The write bit on a directory: 
d- w - - w - - w -
It allows the user to write a name (a string) and a number in the directory's address space. This name and number will define another file or a directory (a directory is also a file) that will be the child of this directory. If the user cannot write the name of a new file inside its directory file then the entire new file creation fails.

armadillo.jpg

The execute bit on a file: 
- - - x - - x - - x
It allows the user to open the file and try to execute it by asking the Kernel to give birth to a new process. If the shell detects that the file is executable, or if it is not it will try to execute an interpreter (perl, python, bash, php) which will translate the content into machine executable instructions.

The execute bit on a directory: 
d- - x - - x - - x
Every process in unix has a state called the Current Working Directory (CWD).  If the execution bit is set for a user then this user's shell which is the current process will be permitted to change its current working directory to this one. In the case of a process, the executable bit for a directory must be set in order to allow a process to successfully make the system call chdir to this directory. This is the case for the web server Apache. If the execute bit on a docroot is not set for apache's process ID then apache's chdir call into its docroot will fail and apache will exit out because of it. We will see below that this failure could be circumvented. In short, the execute bit allows the user to change directories into this directory.

With all the above being said, let's put ourselves in scenarios to test our knowledge. We have two users, farhad and usr1. Consider the permission bits on this file and directory:


farhad@farhad-desktop:/tmp/test$ ls -l
drwx------ 2 farhad farhad 4096 2010-12-23 15:51 dir1

farhad@farhad-desktop:/tmp/test$ ls -l dir1/
---------x 1 farhad farhad 29 2010-12-23 15:51 hello.sh

farhad@farhad-desktop:/tmp/test$ chmod 001 dir1/ ; ls -l
d--------x 2 farhad farhad 4096 2010-12-23 15:51 dir1

The question is whether usr1 is allowed to execute hello.sh. It is under dir1 who's only permission is the execute bit on other. To demonstrate this with the user or group bits would have been the same. I could add usr1 to the group farhad and set the execute bit on the group instead of other.

This output shows whether usr1 can execute hello.sh:

usr1@farhad-desktop:/tmp/test$ ls -l
d--------x 2 farhad farhad 4096 2010-12-23 15:51 dir1
usr1@farhad-desktop:/tmp/test$ ls -l dir1/
ls: cannot open directory dir1/: Permission denied
usr1@farhad-desktop:/tmp/test$ ./dir1/hello.sh
hello there
usr1@farhad-desktop:/tmp/test$ cd dir1
usr1@farhad-desktop:/tmp/test/dir1$ ls
ls: cannot open directory .: Permission denied

Most people will be surprised here. Yes it was able to execute it!! Consider how important the above is. usr1 was unable to list the directory dir1 because there's no read access anywhere on it. But it *was* able to *execute* the file hello.sh which is under it. Why? because executing the file did not require to *list* the directory (no need to call opendir() ?). Only that usr1 has to be able to chdir to dir1 and if it already knows the name of the file, it can execute it by looking up its inode inside the directory. This is different than *listing* the directory. With this knowledge we can drastically improve the security of our web servers!

Here's a side note on linux 2.6.35-22 on which i'm testing. Instead of the above if I were to do this with user farhad the whole thing would fail because Linux does not consider the owner to also be part of other. (Very weird!) Keeping the same permissions as before, let's run the same commands with user farhad this time:

farhad@farhad-desktop:/tmp/test$ ls -l
d--------x 2 farhad farhad 4096 2010-12-23 15:51 dir1
farhad@farhad-desktop:/tmp/test$ ls -l ./dir1/
ls: cannot open directory ./dir1/: Permission denied
farhad@farhad-desktop:/tmp/test$ ./dir1/hello.sh
bash: ./dir1/hello.sh: Permission denied

See how Linux is preventing hello.sh from executing? The same thing can be executed by any other user (such as usr1 shown above). Linux is not considering the owner of the file to be part of the world (others). That's a peculiar behavior.

Now notice something else. I'll be switching between users to show this so pay attention :-)

farhad@farhad-desktop:/tmp/test$ ls -l dir1/
ls: cannot open directory dir1/: Permission denied
farhad@farhad-desktop:/tmp/test$ ls -ld dir1
d--------x 2 farhad farhad 4096 2010-12-23 15:51 dir1
farhad@farhad-desktop:/tmp/test$ chmod o+w dir1 ; ls -ld dir1
d-------wx 2 farhad farhad 4096 2010-12-23 15:51 dir1

Now on to the usr1 console, see how usr1 has no read access to the directory. And the permissions on hello.sh remain "---------x" as before.

usr1@farhad-desktop:/tmp/test$ ls -l
d-------wx 2 farhad farhad 4096 2010-12-23 15:51 dir1
usr1@farhad-desktop:/tmp/test$ ls -l dir1
ls: cannot open directory dir1: Permission denied
usr1@farhad-desktop:/tmp/test$ rm dir1/hello.sh
rm: remove write-protected regular file `dir1/hello.sh'? yes
usr1@farhad-desktop:/tmp/test$ ls -l dir1
ls: cannot open directory dir1: Permission denied

Wow! We think that usr1 just deleted hello.sh but we can't even confirm it because we cannot list the directory dir1. Let's check with user farhad whether hello.sh was really deleted.

farhad@farhad-desktop:/tmp/test$ ls -ld dir1
d-------wx 2 farhad farhad 4096 2010-12-23 15:51 dir1
farhad@farhad-desktop:/tmp/test$ ls -l dir1
ls: cannot open directory dir1: Permission denied
farhad@farhad-desktop:/tmp/test$ chmod 505 dir1/
farhad@farhad-desktop:/tmp/test$ ls -l dir1
total 0  (It is really deleted)


Not even farhad was able to list dir1 even though he's the owner. I gave dir1 read permission to its owner to see and indeed usr1 previously was able to delete hello.sh. But wait a minute! hello.sh did not have the w bit set anywhere. No one could update the script hello.sh. How was usr1 able to delete it? That's because the file hello.sh exists as long as there is an entry for it in a directory somewhere. It doesn't even have to be it's current directory (more on that later. Hint: hard links). So you see that a file's existence is only related to a mention of its inode in a directory. Not the actual blocks of the file's content on disk or the permission of the file itself. We don't need write access to a file in order to delete it! We only need write access to all of the directories that mention this file's i-node number. You only get this info in a Unix System's programmer book.

Sticky bit:

Let's first get the case of a sticky bit on a regular file out of the way. It has NO effect at all.

The sticky bit is always set on the /tmp directory or on Solaris /var/tmp as well. If the bit is set then a file in that directory can be removed or renamed only if the user has write permission for the directory, and either:
  • Owns the file
  • Owns the directory, or
  • is the superuser

Let's demonstrate on our dir1 by first setting a full 777 permissions:

farhad@farhad-desktop:/tmp/test$ ls -l
drwxrwxrwx 2 farhad farhad 4096 2010-12-23 17:59 dir1
farhad@farhad-desktop:/tmp/test$ chmod o+t dir1 ; ls -l
drwxrwxrwt 2 farhad farhad 4096 2010-12-23 17:59 dir1

We now have two other users, usr1 and usr2. Let's create a file with usr1:

usr1@farhad-desktop:/tmp/test/dir1$ touch file1; ls -l
-rw-r--r-- 1 usr1 usr1 0 2010-12-23 23:05 file1

Because dir1 has the sticky bit set, only usr1 can delete file1 or the owner of the directory which is farhad. Let's see if usr2 can delete it:

usr2@farhad-desktop:/tmp/test/dir1$ ls -l
-rw-r--r-- 1 usr1 usr1 0 2010-12-23 23:05 file1
usr2@farhad-desktop:/tmp/test/dir1$ rm file1
rm: remove write-protected regular empty file `file1'? yes
rm: cannot remove `file1': Operation not permitted

But farhad can delete it because it owns the directory:

farhad@farhad-desktop:/tmp/test$ rm dir1/file1
rm: remove write-protected regular empty file `dir1/file1'? yes
farhad@farhad-desktop:/tmp/test$ ls -l dir1/
total 0
farhad@farhad-desktop:/tmp/test$

That's basically the gist of it. You set the sticky bit on a directory that has its write bit open to everybody in order to protect its contents from being deleted by anybody else.

If you ever see the sticky bit with a capital T instead of a small t then it means that the directory has its execute bit removed on other.

farhad@farhad-desktop:/tmp/test$ chmod 776 dir1/ ; ls -ld dir1
drwxrwxrw- 2 farhad farhad 4096 2010-12-23 18:09 dir1/
farhad@farhad-desktop:/tmp/test$ chmod o+t dir1/ ; ls -ld dir1
drwxrwxrwT 2 farhad farhad 4096 2010-12-23 18:09 dir1/

See the T in capital? it is the same sticky bit but since at that other position the execute bit is missing, the sticky bit shows in capital to notify us of this fact. In this case the directory is useful to only the user ID belonging to the group ID of the directory cause the rest of the ID's won't be able to chdir into it (see above).

setuid:

The setuid bit on a directory is only effective when it is on the group bit.

farhad@farhad-desktop:/tmp/test$ chmod g+s dir1/ ; ls -l
drwxrwsrwx 2 farhad farhad 4096 2010-12-24 00:40 dir1

Since the group id of dir1 is farhad, with the setgid bit set any file created under dir1 will inherit the group id of dir1 as well.

usr1@farhad-desktop:/tmp/test/dir1$ touch file1 ; ls -l
-rw-r--r-- 1 usr1 farhad 0 2010-12-24 00:50 file1

You can see that file1 was created by usr1 but it inherited dir1's group. When the setuid is on the user or the other bit then it has no effect. I'll update this if i find otherwise.

The main use of the setuid is when it is on an executable file. Every process has a "real UID" and an "effective UID." (there's also a "saved set-UID" which we don't need to worry about now. That's for when we talk about the exec() function). The real UID is the actual ID fetched from the /etc/passwd file. It is the ID you get when you login to your shell. It is the "effective user ID" and the "effective group ID" that determine the process's file access permissions (not the real user id nor the real group ID).

All processes have their user and group ID's equal to their effective user and effective group ID's. The setuid bit instructs the kernel to set the effective user ID of the process to be that of the owner of the file. And similarly the set group ID tells the kernel to set the effective group ID of the process to that of the group ID of the executable file.

Remember that file access permission are checked against the effective user ID and effective group ID of a process. Let's explain with an example:

farhad@farhad-desktop:/tmp/test$ ls -l /usr/bin/passwd ; ls -l /etc/shadow
-rwsr-xr-x 1 root root 37100 2010-09-03 03:28 /usr/bin/passwd
-rw-r----- 1 root shadow 1159 2010-12-23 22:24 /etc/shadow

You can see that the passwd executable file has the setuid bit set. This means that any process executing passwd will end up with its effective user ID as being that of the executable file. In this case root. Everyone can execute passwd because it's executable bit is set on the other field. This is how usr1 can set its effective user ID to that of the root and hence obtain file access permissions to change the shadow file which contains the encrypted password. The same rule applies to the group setuid.

I hope that this article cleared some things up for some folks.

No TrackBacks

TrackBack URL: http://www.farhadsaberi.com/cgi-bin/mt/mt-tb.cgi/8

Leave a comment

About this Entry

This page contains a single entry by Farhad Saberi published on December 19, 2010 12:58 AM.

Dual Monitor ATI Radeon RV100 QY Radeon 7000/VE X.org was the previous entry in this blog.

Hard Link Soft Symbolic Links is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.