6.6 Device Files
Computer
systems usually have peripheral devices
attached to them. These devices may be involved with I/O (terminals,
printers, modems), they may involve mass storage (disks, tapes), and
they may have other specialized functions. The Unix paradigm for
devices is to treat each one as a file, some with special
characteristics.
Unix devices are represented as inodes, identical to files. The
inodes represent
either a character device or a
block
device (described in the sidebar). Each device
is also designated by a major device number, indicating the type of
device, and a minor device number, indicating which one of many
similar devices the inode represents. For instance, the partitions of
a physical disk will all have the same major device number, but
different minor device numbers. For a serial card, the minor device
number may represent which port number is in use. When a program
reads from or writes to a device file, the kernel turns the request
into an I/O operation with the appropriate device, using the
major/minor device numbers as parameters to indicate which device to
access.
Most devices in Unix are referenced as character
devices. These are also known as raw
devices because that is what you get—raw
access to the device. You must make your read and write calls to the
device file in the natural transfer units of the device. Thus, you
probably read and write single characters at a time to a terminal
device, but you need to read and write sectors to a disk device.
Attempts to read fewer (or more) bytes than the natural block size
results in an error, because the raw device doesn't
work that way.
When accessing the filesystem, we often want to read or write only
the next few bytes of a file at a time. If we used the raw device, it
would mean that to write a few bytes to a file, we would need to read
in the whole sector off disk containing those bytes, modify the ones
we want to write, and then write the whole sector back out. Now
consider every user doing that as they update each file. That would
be a lot of disk traffic!
The solution is to make efficient use of
caching. Block devices
are cached versions of character
devices. When we refer to a few bytes of the block device, the kernel
reads the corresponding sector into a buffer in memory, and then
copies the characters out of the buffer that we wanted. The next time
we reference the same sector, to read from or write to, the access
goes to the cached version in memory. If we have enough memory, most
of the files we will access can all be kept in buffers, resulting in
much better performance.
There is a drawback to block devices, however. If the system crashes
before modified buffers are written back out to disk, the changes our
programs made won't be there when the system
reboots. Thus, we need to periodically flush the modified buffers out
to disk. That is effectively what the
sync( ) system call
does: schedule the buffers to be flushed to disk. Most systems have a
sync or fsflush daemon that
issues a sync( ) call every 30 or 60 seconds to
make sure the disk is mostly up to date. If the system goes down
between sync( ) calls, we need to run a program
such as fsck or checkfsys
to make certain that no directories with buffers in memory were left
in an inconsistent state.
|
Unix usually has some special device files that
don't correspond to physical devices. The
/dev/null device simply discards anything written
to it, and nothing can ever be read from it—a process that
attempts to do so gets an immediate end-of-file condition. Writing to
the /dev/console device results in output being
printed on the system console terminal. And reading or writing to the
/dev/kmem device accesses the
kernel's memory. Devices such as these are often
referred to as
pseudo-devices.
Device files are one of the reasons Unix is so flexible—they
allow programmers to write their programs in a general way without
having to know the actual type of device being used. Unfortunately,
they can also present a major security hazard when an attacker is
able to access them in an unauthorized way.
For instance, if attackers can read or write to the
/dev/kmem device, they may be able to alter
their priority, UID, or other attributes of their process. They could
also scribble garbage data over important data structures and crash
the system. Similarly, access to disk devices, tape devices, network
devices, and terminals being used by others can lead to problems.
Access to your screen buffer might allow an attacker to read what is
displayed on your screen. Access to your audio devices might allow an
attacker to eavesdrop on your office without your knowing about it.
In standard configurations of Unix, all the standard device files are
located in the directory /dev. There is usually
a script (e.g., MAKEDEV) in that directory that
can be run to create the appropriate device files and set the correct
permissions.
A few devices, such as /dev/null,
/dev/tty, and /dev/console,
should always be world-writable, but most of the rest should be
unreadable and unwritable by regular users. Note that on some
System V-derived systems, many of
the files in /dev are symbolic links to files in
the /devices directory, which are the files
whose permissions you need to check.
Check the permissions on these files when you install the system, and
periodically thereafter. If any permission is changed, or if any
device is accessible to all users, you should investigate. This
research should be included as part of your checklists.
6.6.1 Unauthorized Device Files
Although device files are normally located in the
/dev directory, they can, in fact, be anywhere
on your system. A not uncommon method used by system crackers is to
access the system as the superuser and then create a writable device
file in a hidden directory, such as the
/dev/kmem device hidden in
/usr/lib and named to resemble one of the
libraries. Later, if they wish to become superuser again, they know
the locations in /dev/kmem that they can alter
with a symbolic debugger or custom program to allow them that access.
For instance, by changing the code for a certain routine to always
return true, they can execute su to become
root without needing a password. Then, they set
the routine back to normal.
You should periodically scan your disks for unauthorized device
files. The ncheck command, mentioned earlier, will print
the names of all device files when run with the
-s option. Alternatively, you can execute the
following:
# find / \( -type c -o -type b \) -exec ls -l {} \;
If you have NFS-mounted directories, use this version of the script:
# find / \( -local -o -prune \) \( -type c -o -type b \) -exec ls -l {} \;
Note that some versions of NFS allow users on client machines running
as root to create device files on exported
volumes. This
is a major problem. Be very careful when
exporting writable directories using NFS (see Chapter 15 for
more information).
The two commands:
find / \! -type f -a \! -type d -exec ls -l {} \;
and:
find / \( -type c -o -type b \) -exec ls -l {} \;
are not equivalent!
The first command prints all of the entries in the filesystem that
are not files or directories. The second prints all of the entries in
the filesystem that are either character or block devices.
Why aren't these commands the same? Because there
are other things that can be in a filesystem that are neither files
nor directories. These include:
Symbolic links
Sockets
Named pipes (FIFOs)
|
|