PROBLEM SOLVER
Permissiveness
by Bob Toxen
File permissions, file ownership, and process ownership are overlooked all too
often -- curious in light of the benefits and dangers they represent. Each
file has a user and a group associated with it, along with a list of
permissions defining who can do what with the file.
Who can be limited either to processes with the same effective
user identification (UID), processes with the same effective group
identification (GID), or all other processes.
Processes that have permission can be restricted either to reading the file's
data, writing data to the file (either by writing over existing data or by
appending data to the end of the file), executing the file (invoking it as a
binary program or process), or some combination of these.
When someone first logs in, a login shell is created with the user
id and group id specified for that person's account in the
/etc/passwd file. These designate both the real and
effective user and group ids. Under normal use, the real and
effective ids stay the same. Any child process created with the
fork() system call will inherit the user and group ids of its
parent.
When the exec() system call is used to start a program, the
real user and group ids stay the same but the effective ids may be changed to
the user and group of the file executed (instead of that of the process
performing the exec()). This can be accomplished by
establishing the set-UID or set-GID permissions (bits) on the
file prior to execution.
Programs with the set-UID or set-GID bits set in their file entries are called
set-UID programs or set-GID programs and are crucial to the
day-to-day operations of UNIX. For example, the passwd
program is set-UID to root but is executable by everyone. By contrast, the
/etc/passwd file, which contains information on people's accounts, is
owned by root and its permissions allow only root to write data to it. Thus,
anyone can use the passwd program to change their own
password, but the program is written in such a way as to prevent people from
changing other users' passwords or any other data in the file. It does this
by using the getuid() system call to determine a user's real
UID.
The su program (which runs set-UID to root) uses a somewhat
different technique. It allows one to start a subshell running under
someone else's id without first logging out of one's own account, assuming
that the password listed for the new account in the /etc/passwd file
is known.
After verifying the password, su uses the
setgid() and setuid() system calls (in that
order) to change both the real and effective group and user ids for the
running su process. It then uses the exec()
call to start a shell running with the new permissions.
A program running with an effective user id of zero (the id assigned to root)
may use the setuid() and setgid() system calls
to change its real and effective user and group ids to any value. Of course,
after using setuid() to change its UID, the program no longer
will have root's unlimited power. This is why su and almost
all similar programs invoke setgid before
setuid.
A program with an effective UID other than root may use
setuid() and setgid() (in any order) to change
its effective ids to match its real ids. This capability is quite powerful
and will be discussed when rogue is investigated later in
this column.
These programs illustrate how an ordinary user can be granted extraordinary
permissions (powers) in a controlled manner. The technique is basic to UNIX
programming and is used by ps, df,
uucp, some printer spoolers, and many databases, among
others.
Also central to the UNIX system is the fact that whatever is true about
permissions for files is true about directories as well -- but in spades. A
directory, such as /usr/people/bob or /bin, may be opened
and read like an ordinary file. The ls utility does this
when a directory is listed. Directories consist of 16-byte records (4.2BSD
has a similar but different format), of which the last 14 bytes specify a
null-padded file name of some file "in that directory" and the first two bytes
contain a 16-bit integer specifying the file's inode number. An inode number
of zero indicates that a record or "slot" is not currently in use.
Not even root can write directly to a directory. This prevents file system
corruption and disables an operation that is useless. The
create(), link(), unlink(),
and mknod() system calls are the mechanisms one uses to write
to directories as well as to create or destroy inodes. Thus, these calls
require write access to the directory that contains or soon will contain the
affected file. Write access to the file itself is not required.
Since it makes no sense to execute a directory as a program, the execute
permission bit is interpreted as permission to access a particular file in the
directory. Examples of operations that require access are the execution of a
program in the directory; the opening of a file within the directory for
reading or writing (which will require read or write access to the file); the
use of stat() on a file (which requires no access permission
for the file itself); the issue of a creat() or
unlink() call (which will require write access to the
directory); a change into the directory (by the chdir() call)
or any references to a subdirectory ... and so forth and so on.
USAGE
It is clear that inattention to permissions could potentially invite others to
execute programs, read documents, and write over or remove files. What can be
done to defend against this? First, you must decide who should have access to
your files, and what level of access they should have. This can often be a
complex question. Typically, most files are not confidential and so may be
left readable by anyone. Others, such as company planning documents, customer
lists, or personnel reports, should be held in strict confidence.
The chmod program can be used to change the permissions of
any file you own to the modes you desire. The first argument (parameter) to
chmod specifies the modes to which files identified in
subsequent arguments should be set. This first argument consists of an octal
number, in which the rightmost digit specifies the permissions for others, the
next digit to the left specifies permissions for users in the same group, and
the following digit to the left specifies permissions for the file's owner.
Each digit is either zero or the sum of some combination of four, two, and
one. The digit 4 signifies read permission,
2 stands for write permission, 1 indicates
execute permission, and 0 means that no permissions
whatsoever have been granted.
An optional fourth digit, located on the
extreme left, may be used to have a program be set-UID or set-GID. If this
digit is a 4, then the program will be set-UID. If the digit
is a 2, the program will be set-GID, and if the digit is a
6, the program will be set-UID and set-GID. [Letters can
also be used to designate permissions under System V and 4.2BSD
implementations of chmod. The argument g+w, for
instance, indicates that group members should enjoy write permission for the
specified file. Likewise, the argument o-w indicates that write
permission should be denied to others.]
Thus, if you have a document called group_plan that you personally
want to be able to read and write, that members of your group should be able
to read, and that other users at large should not be able to access at all,
you would want to issue the command:
% chmod 640 group_plan
If you create lots of files, this process can become tiresome and it likely
will be overlooked from time to time. Fortunately, a command available in
both the regular Bourne shell and the C shell can do this automatically.
Called umask, it takes a single argument in the same format
as chmod but the digits represent permissions that should be
turned off, rather than on (as with chmod). Thus,
if the command:
% umask 037
is issued before the group_plan file is created, the file
automatically will be generated with full permissions for you, read
permissions for your group, and no permissions whatsoever for other users. It
is common practice to include an invocation of the umask
command in one's .profile or .login file so that the command
will be executed automatically each time you log in (see the July, 1984
Problem Solver for more details).
Another way to make things easier is to group similar files in subdirectories
and structure the directory permissions so as to limit access to the files
they contain. For example, one can create a directory called
group_notes and set its permissions to, say, 770. This would allow
anyone in the group to search the directory, create or delete files, and read
and write those files that offer permission to do so, while keeping others out
of the directory altogether.
Execute permission for a directory is required in order to access the files it
contains. Note that in order to access a file, one must also have permissions
for the file itself (except when creating or removing it or using the
stat() system call).
GROUP PERMISSIONS
A user's account should be assigned to a group containing other users with
whom data will likely be shared. Access to that user's account then will be
denied to those outside of the group.
The group that a person's account is in (GID) is specified in the
/etc/passwd file in the colon-separated field following the UID
field. Once that field is modified, the new GID (and UID) automatically will
be used by all programs that the person invokes, starting with the next time
the person logs in. (The set-UID and set-GID programs, of course, are
exceptions to this rule.)
In order to associate a numerical GID with a named group (in much the same way
that the /etc/passwd file associates UIDs with named accounts), an
entry must be made in the /etc/group file. This is a text file with
one entry per line. Each entry consists of several comma-separated fields.
The first field contains the group's name. The second contains a group
password that can be used in conjunction with newgrp. If you
don't know what this password is, I recommend putting an "x" or
"*" in the field for security. The third field, which should be
terminated by a colon, contains the GID.
The optional fourth field of /etc/group usually contains a
comma-separated list of accounts associated with the group (to help
administrators track these things). If someone from a different group
(according to the /etc/passwd file) is listed here, she can use the
newgrp command (in some implementations) to switch her GID to
the new group for all subsequent processes in the login session.
Under 4.2BSD, different rules apply. When a file is created, its group will
be that of the directory in which it's created. Additionally, an account
automatically belongs to all of the groups in which it is listed in
/etc/group.
MOVING UPWARD
The use of permissions can be truly innovative. For example, suppose that a
system contains a printer or modem that belongs to a particular department.
To prevent it from being used by others, the device's file in the
/dev directory can be set up such that its group contains the
accounts of the people in the department, with its mode set to 660. This will
prevent others from using it. The same technique can be used for modems, tape
drives, file systems, or any other device. In one installation, I applied
this technique to the device file for a serial tty line that was
connected to a remote computer containing sensitive company source code. This
prevented unauthorized access to the remote computer -- an important
consideration since the local computer was part of the more-or-less public
UUCP network. (UUCP has its own set of security rules, but that is a column
unto itself.)
The game rogue offers an interesting study in permissions
problems. As many people know, rogue is a fantasy game
program similar in concept to adventure. Permissions become
an issue because the game maintains a scoreboard that ranks scorers (according
to who finds the most gold). To prevent cheating, ordinary users should not
have unlimited access to this file. The obvious approach is to emulate the
relationship between the passwd program and the
/etc/passwd file. Thus, the score file would be owned by the
games account and set to mode 600 while rogue would
run set-UID to games
This presents a problem, however, because rogue has another
feature that allows one to suspend a game by saving its state in the file of
the user's choice, making it possible to continue a game at a later time, even
if that time comes after the system has been rebooted. See if you can figure
out the problem.
It should be rogue rather than the user that worries about
how to keep the score file away from the clutches of dastardly data diddlers.
Users, though, may be interested in preserving security in their own
directories.
Consider the situation where the user's current working directory is her login
directory (the usual situation), where the permissions have been set to 700 to
prevent anyone else from accessing files or creating new ones. What will
happen if the user starts playing rogue and then decides to
save the game in a file called hotstuff? Rogue will
not be able to create this file in her current directory because it will be
running set-UID to games, which is not her account. One solution
would be to have rogue use the setuid()
system call to change its effective user id back to its real user id. It can
then create the hotstuff file and exit.
Alas! Life is not so simple. Rogue was created as a game of
risk. If one could save the game whenever danger arose, it would be tempting
to start the game repeatedly from that point, trying different strategies
until one succeeded -- and where would the risk be in that? Happily,
rogue prevents this by automatically removing the saved file
whenever play is resumed. (It also checks to ensure that no copies or links
have been made to the file.)
Consider the situation where the directory containing the hotstuff
file is set to mode 700. What do you suppose happens when
rogue tries to remove it? Right, rogue
won't be able to. It actually could use the setuid() system
call to set its effective user id back to its real user id so that it could
remove the hotstuff file and resume the game, but it would no longer
be able to change the score file since it would no longer be set-UID to
games. What rogue really needs is a split
personality allowing it to be both set-UID and not set-UID. Although there
are a number of proposals for allowing the setuid() system
call to flip the effective user back and forth between the real user id and
the original effective user id, 4.2BSD is to my knowledge the only major UNIX
version to have implemented the concept.
Fortunately, the fork() system call is just the thing for
creating split personalities. One of the processes that a fork
can create could do a setuid() back to the real user id while
its sibling could stay set-UID to games (or whatever).
Synchronization between these two processes could be done with
exit() and wait(), pipes (named or unnamed),
semaphores, or by other means.
When I fixed rogue to handle
this situation, I had the child process do a setuid() back to
the real user id, remove the file, and exit. The parent would wait for the
child to exit and then get on with the game. This isn't the most elegant
solution but the second or so consumed by the fork is trivial
compared to the hours spent by the average user rogueing.
Leave it to a game to show just how much the right amount of permissiveness --
combined with a dash of creativity and insight -- can accomplish.
Bob Toxen is the Senior Software Engineer at Stratus Computer, Inc.
He has gained a reputation as a leading expert on UUCP
communications, file system repair, and UNIX utilities.
He has also done ports of System III and System V to systems based on
the Zilog 8000 and Motorola 68010 chips.
Copyright © 1985, 2007, 2014, 2020 Robert M. Toxen. All rights reserved.
Back
|