Info: Version 1.8.x is available.

Japanese Page

Last modified: $Date: 2024-03-30 11:25:00 +0000 (Sat, 30 Mar 2024) $

The world of TOMOYO Linux
The fifth installment: "Let's add conditions to policy."

Contents of this installment.

In this installment, I explain "conditional ACL" which utilizes TOMOYO Linux's powerful parameter checking functionality.

Adding conditions to ACL entries

About conditional ACL

TOMOYO Linux does not support RBAC ( Role Based Access Control ). But you can specify conditions based on user ID in the policy configuration. Therefore, you can use access control based on system account's user ID. Conditions are appended at the tail of individual permissions in the form of "if condition". Available conditions are listed in Fig. 1 and usage examples are listed in Fig. 2.

♦ Fig. 1  Parameters which can be used as conditions
VariableMeaning
task.uidUID of current process
task.euidEffective UID of current process
task.suidSaved UID of current process
task.fsuidFile System UID of current process
task.gidGID of current process
task.egidEffective GID of current process
task.sgidSaved GID of current process
task.fsgidFile System GID of current process
task.pidPID of current process
task.ppidPID of parent process
task.typeType of current process (execute_handler or not)
task.state[0]Current process's state variable 0
task.state[1]Current process's state variable 1
task.state[2]Current process's state variable 2
path1.uidUID of first object
path1.gidGID of first object
path1.inoi-node number of first object
path1.typeType of first object (e.g. file, directory, socket)
path1.permDAC's permission of first object
path1.majorDevice major number of a device file which first object resides
path1.minorDevice minor number of a device file which first object resides
path1.dev_majorDevice major number of first object (assuming that first object is a device file)
path1.dev_minorDevice minor number of first object (assuming that first object is a device file)
path1.parent.uidUID of first object's parent directory
path1.parent.gidGID of first object's parent directory
path1.parent.inoi-node number of first object's parent directory
path1.parent.permDAC's permission of first object's parent directory
path2.uidUID of second object
path2.gidGID of second object
path2.inoi-node number of second object
path2.typeType of second object (e.g. file, directory, socket)
path2.permDAC's permission of second object
path2.majorDevice major number of a device file which second object resides
path2.minorDevice minor number of a device file which second object resides
path2.dev_majorDevice major number of second object (assuming that second object is a device file)
path2.dev_minorDevice minor number of second object (assuming that second object is a device file)
path2.parent.uidUID of second object's parent directory
path2.parent.gidGID of second object's parent directory
path2.parent.inoi-node number of second object's parent directory
path2.parent.permDAC's permission of second object's parent directory
exec.argcNumber of argv[] passed for execute request
exec.envcNumber of envp[] passed for execute request
exec.argv[index]index-th argument for execute request
exec.envp["name"]value of environment variable "name" for execute request
exec.realpathDereferenced pathname of the requested program
symlink.targetThe content of a symlink to be created
♦ Fig. 2  Example of conditions
ConditionMeaning
if task.uid=0if process's user ID is 0 (i.e. "root" user)
if task.uid!=0if process's user ID is not 0 (i.e. not "root" user)
if task.uid=100-500if process's user ID is between 100 and 500
if task.uid=0 task.gid=0if both process's user ID is 0 and group ID is 0 (i.e. "root")
if task.uid!=0 task.gid!=0if both process's user ID is not 0 and group ID is not 0 (i.e. not "root")
if task.uid=100-500 task.gid!=0if process's user ID is between 100 and 500 and process's group ID is not 0
if exec.argv[index]="value"if index-th ( 0<=index<exec.argc ) argument's value matches "value"
if exec.argv[index]!="value"if index-th ( 0<=index<exec.argc ) argument's value does not match "value"
if exec.envp["name"]="value"if environment variable "name" is defined and its value matches "value"
if exec.envp["name"]!="value"if environment variable "name" is not defined or its value does not match "value"
if exec.envp["name"]!=NULLif environment variable "name" is defined
if exec.envp["name"]=NULLif environment variable "name" is not defined

Let's add conditions

I explain steps for allowing browsing user's own files only for users logged in from console using /bin/cat command as an example. I use "user1" user and "user2" user for this purpose. Login from console and do below operations. Firstly, create 2 accounts. (Fig. 3)

♦ Fig. 3  Create 2 accounts
# useradd -s /bin/bash user1
# useradd -s /bin/bash user2

Set some passwords for these accounts. (Fig. 4)

♦ Fig. 4  Set passwords for created accounts
# passwd user1
# passwd user2

Run /bin/cat command in order to create domain for cat command. In addition, let's print the domain of cat command. (Fig. 5)

♦ Fig. 5  Print domain which cat command belongs to
# cat /proc/ccs/self_domain
<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat

Assign a profile for learning mode to /bin/cat command's domain. In this series, the profile for learning mode is profile 1, thus do like Fig. 6.

♦ Fig. 6  Assign a profile for learning mode to the domain which cat command belongs to
# /usr/sbin/ccs-setprofile 1 '<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat'

Logout and re-login as "user1" user. Then, do operations listed in Fig. 7.

♦ Fig. 7  Login as "user1" user and create /tmp/testfile1
$ echo "This file was created by user1" > /tmp/testfile1
$ cat /tmp/testfile1
This file was created by user1

Logout and re-login as "user2" user. Then, do operations listed in Fig. 8.

♦ Fig. 8  Login as "user2" and create /tmp/testfile2
$ echo "This file was created by user2" > /tmp/testfile2
$ cat /tmp/testfile2
This file was created by user2

Logout and re-login as "root" user. Run ccs-editpolicy command and check "<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat" domain's permissions. There should be entries listed in Fig. 9.

♦ Fig. 9  Permissions given to "<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat" domain
allow_read /tmp/testfile1
allow_read /tmp/testfile2

As of now, everyone can read these files if granted by DAC (Discretionary Access Control) permission. Press "A" key on the keyboard and enter lines listed in Fig. 10.

♦ Fig. 10  Adding permissions with conditions
(♦ You may specify "allow_read /tmp/testfile\+ if task.uid=path1.uid" instead for these lines.)
allow_read /tmp/testfile1 if task.uid=path1.uid
allow_read /tmp/testfile2 if task.uid=path1.uid

Then, delete entries listed in Fig. 11 using "D" key on the keyboard.

♦ Fig. 11  Deleting permissions without conditions
allow_read /tmp/testfile1
allow_read /tmp/testfile2

Press "Q" key on the keyboard to quit ccs-editpolicy command. Then, assign a profile for enforcing mode to /bin/cat command's domain. In this series, the profile for enforcing mode is profile 3, thus do like Fig. 12.

♦ Fig. 12  Assign a profile for enforcing mode to the domain which cat command belongs to
# /usr/sbin/ccs-setprofile 3 '<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat'

Logout and re-login as "user1" user. Then, try to print /tmp/testfile1 and /tmp/testfile2 using /bin/cat command. You can see that access to /tmp/testfile2 is forbidden. (Fig. 13)

♦ Fig. 13  Access to /tmp/testfile2 is forbidden
$ cat /tmp/testfile1
This file was created by user1
$ cat /tmp/testfile2
cat: /tmp/testfile2: Operation not permitted

Logout and re-login as "user2" user. Do the same operations like "user1" user. You can see that access to /tmp/testfile1 is forbidden. You may specify constant user ID instead of variables. For example, you can forbid "root" user's login if you change like Fig. 14 in the "<kernel> /sbin/mingetty /bin/login" domain (which authenticates user and executes login shell).

♦ Fig. 14  Deny login as "root" user
[Before modification]
allow_execute /bin/bash
[After modification]
allow_execute /bin/bash if task.uid!=0

You can allow login of users with user ID between 500 and 1000 if you change like Fig. 15. You can use it for restricting logins via SSH.

♦ Fig. 15  Allow login of users with user ID between 500 and 1000
[Before modification]
allow_execute /bin/bash
[After modification]
allow_execute /bin/bash if task.uid=500-1000

Now, I've finished explanation of conditional permissions. Delete accounts created for this explanation. (Fig. 16)

♦ Fig. 16  Delete accounts created at Fig. 3
# userdel user1
# userdel user2

Change of behavior by how the program is executed

Getting sidetracked a bit. I'd like to show you change of behavior by how the program is executed which can become a pitfall when doing access control. Login to a system (needn't to use TOMOYO Linux kernel) and save the program listed in Fig. 17 as /tmp/argv0.c .

♦ Fig. 17  A C program which prints the invocation name
#include <stdio.h>
int main(int argc, char *argv[])
{
        printf("I am running as %s\n", argv[0]);
        return 0;
}

Then, compile and create symbolic links (or hard links) like Fig. 18.

♦ Fig. 18  Compile and create symbolic links
# gcc -o /tmp/argv0 /tmp/argv0.c
# ln -s /tmp/argv0 /tmp/cat
# ln -s /tmp/argv0 /tmp/passwd

Then, execute these symbolic links (or hard links), and you will see the name of symbolic links (or hard links) are printed. (Fig. 19).

♦ Fig. 19  Execute via symbolic links
# /tmp/cat
I am running as /tmp/cat
# /tmp/passwd
I am running as /tmp/passwd

Some programs are designed to behave differently depending on the invocation name (called argv[0]). For example, in CentOS 5, /sbin/pidof is a symbolic link to /sbin/killall5 . If /sbin/killall5 is executed as "pidof", the program prints process ID of specified program. If /sbin/killall5 is executed as "killall5", the program sends signals to all processes except processes running in the same session.

In TOMOYO Linux, the process runs in "/tmp/cat" domain if the program is executed as "/tmp/cat" and the process runs in "/tmp/passwd" domain if the program is executed as "/tmp/passwd". Then, what will happen if /tmp/passwd is executed as /tmp/cat like Fig. 20? (Guess the result.)

♦ Fig. 20  Executing "passwd" command as "cat"
# sh -c 'exec -a /tmp/cat /tmp/passwd'

The result of Fig. 20 is Fig. 21. This means that allowing Fig. 20 is equal to allowing Fig. 22. However, note that Fig. 22 allows running as /tmp/cat in the /tmp/cat domain whereas Fig. 20 allows running as /tmp/cat in the /tmp/passwd domain.

♦ Fig. 21  Behave as "cat" command
I am running as /tmp/cat
♦ Fig. 22  Consequence of Fig. 20
# sh -c 'exec /tmp/cat'

Suppose /usr/bin/passwd command and /bin/cat command are provided using symbolic links (or hard links) to /sbin/busybox command. In this case, TOMOYO Linux transits to /bin/cat domain if /bin/cat is executed and transits to /usr/bin/passwd domain if /usr/bin/passwd is executed. To allow changing password, you need to allow access to /etc/shadow to the /usr/bin/passwd domain. But you don't want to allow access to /etc/shadow to the /bin/cat domain to print the password file. Then, what will happen if /usr/bin/passwd is executed as "/bin/cat" like Fig. 23?

♦ Fig. 23  Executing "passwd" command as "cat"
# sh -c 'exec -a /bin/cat /usr/bin/passwd /etc/shadow'

By doing Fig. 23, /usr/bin/passwd will run in the /usr/bin/passwd domain. But since the invocation name was /bin/cat , /usr/bin/passwd behaves like /bin/cat . As a result, you allowed behavior like Fig. 24.

♦ Fig. 24  Allowing "cat" to read password file
# sh -c 'exec /bin/cat /etc/shadow'

To avoid such threat, TOMOYO Linux provides ability to check combination of program's pathname and argv[0] , and the dereferenced pathname ( exec.realpath ) and invocation name ( exec.argv[0] ) are automatically checked against the execute permission. Thus, you can avoid threat like Fig. 23 by forbidding execution of /usr/bin/passwd command as /bin/cat .

Generating policy from access logs (for advanced users)

Permissions generated using learning mode contains only exec.realpath and exec.argv[0] and symlink.target as conditions. But access logs contain all variables listed in Fig. 1. Thus, by generating policy from access logs rather than using learning mode, you can define permissions with strictest conditions.

Configure not to automatically append permissions.

In learning mode, once a policy violation occurs, permissions needed for avoiding that violation are automatically appended to the policy configuration in order to avoid future policy violation. But permissions automatically appended do not contain all information listed in Fig. 1. Thus, configure profile not to automatically append permissions. (Fig. 25)

♦ Fig. 25  Changes in /etc/ccs/profile.conf
[Before modification]
PREFERENCE::learning={ verbose=no }
[After modification]
PREFERENCE::learning={ verbose=no max_entry=0 }

Save the profile, and then run the command in Fig. 26 in order to reflect the changes.

♦ Fig. 26  Reflect the changes
# /usr/sbin/ccs-loadpolicy p

Run the programs you want to generate policy

Run the programs. All policy violations are saved as "access rejected logs".

A lot of messages like Fig. 27 will be printed since you specified max_entry=0 in Fig. 25, but you can ignore these messages.

♦ Fig. 27  Message when reached the upper limit for learning mode
WARNING: Domain 'domainname' has so many ACLs to hold. Stopped learning mode.

Picking up necessary part from access rejected logs

Pick up lines containing " mode=learning " using grep command. (Fig. 28) Note the leading and trailing spaces. If you forget to specify these spaces, it might match the filename (e.g. "allow_read /path/to/file/mode=learning/and/so/on ).

♦ Fig. 28  Pick up only learning mode's access logs
# grep -F -A 2 " mode=learning " /var/log/tomoyo/reject_log.conf > /var/log/tomoyo/learning_log.txt

Then, convert to conditional permissions. (Fig. 29)

♦ Fig. 29  Convert to permissions with conditions
# /usr/lib/ccs/convert-audit-log < /var/log/tomoyo/learning_log.txt > /var/log/tomoyo/policy.tmp

Then, compress by sorting by domainnames. (Fig. 30)

♦ Fig. 30  Sort by domainnames
# /usr/sbin/ccs-sortpolicy < /var/log/tomoyo/policy.tmp > /var/log/tomoyo/policy.txt

Edit and append to policy configuration

You may append the contents of /var/log/tomoyo/policy.txt to /etc/ccs/domain_policy.conf as domain policy. But the contents of /var/log/tomoyo/policy.txt will be too strict to use as policy configuration (like Fig. 31). For example, you should not specify process ID and i-node number because they will change every time. Thus, prune unnecessary conditions using text editor before appending to /etc/ccs/domain_policy.conf .

♦ Fig. 31  Permission which is too strict to apply
allow_execute /usr/bin/id if task.pid=4641 task.ppid=4637 task.uid=48 task.gid=48 task.euid=48 task.egid=48 task.suid=48 task.sgid=48 task.fsuid=48 task.fsgid=48 task.state[0]=0 task.state[1]=0 task.state[2]=0 task.type!=execute_handler path1.uid=0 path1.gid=0 path1.ino=603159 path1.major=8 path1.minor=1 path1.perm=0755 path1.type=file path1.parent.uid=0 path1.parent.gid=0 path1.parent.ino=589834 path1.parent.perm=0755 exec.realpath="/usr/bin/id" exec.argc=1 exec.envc=7 exec.argv[0]="id" exec.envp["TERM"]="linux" exec.envp["PATH"]="/sbin:/usr/sbin:/bin:/usr/bin" exec.envp["PWD"]="/usr/share/horde/admin" exec.envp["LANG"]="en_US.UTF-8" exec.envp["SHLVL"]="3" exec.envp["LANGUAGE"]="en_US.UTF-8" exec.envp["_"]="/usr/bin/id"

Trailer

In this installment, I explained conditional permissions. In the next installment, I explain countermeasure for SSH bruteforce attacks which is recently burgeoning and steps to splitting administrative operations, using TOMOYO Linux's characteristic hierarchized domain transitions. Don't miss it!

Go back to the fourth installment.  Proceed to the sixth installment.


Return to index page.

sflogo.php