Are security risks lurking within your filesystems? If so, they can
be hard to detect, especially if you must search through mountains of
data. Fortunately, Linux provides the powerful tools
find and xargs to help with the
task. These tools have so many options, however, that their
flexibility can make them seem daunting to use. We recommend the
following good practices:
- Know your filesystems
-
Linux supports a wide range of filesystem types. To see the ones
configured in your kernel, read the file
/proc/filesystems. To see
which filesystems are currently mounted (and their types),
run:
$ mount
/dev/hda1 on / type ext2 (rw)
/dev/hda2 on /mnt/windows type vfat (rw)
remotesys:/export/spool/mail on /var/spool/mail type nfs
(rw,hard,intr,noac,addr=192.168.10.13)
//MyPC/C$ on /mnt/remote type smbfs (0)
none on /proc type proc (rw)
...
with no options or arguments. We see a traditional Linux ext2
filesystem (/dev/hda1), a Windows FAT32
filesystem (/dev/hda2), a remotely mounted NFS
filesystem (remotesys:/export/spool/mail), a
Samba filesystem (//MyPC/C$) mounted remotely,
and the proc filesystem provided by the kernel.
See mount(8) for more details.
- Know which filesystems are
local and which are remote
-
Searching network
filesystems like NFS partitions can be quite slow. Furthermore, NFS
typically maps your local root account to an unprivileged user on the
mounted filesystem, so some files or directories might be
inaccessible even to root. To avoid these problems when searching a
filesystem, run
find locally on the server that physically
contains it.
Be aware that some filesystem types (e.g., for Microsoft Windows) use
different models for owners, groups, and permissions, while other
filesystems (notably some for CD-ROMs) do not support these file
attributes at all. Consider scanning
"foreign" filesystems on servers
that recognize them natively, and just skip read-only filesystems
like CD-ROMs (assuming you know and trust the source).
The standard Linux filesystem type is ext2. If your local filesystems
are of this type only, you can scan them all with a
command like:
# find / ! -fstype ext2 -prune -o ... (other find options) ...
This can be readily extended to multiple local filesystem types
(e.g., ext2 and ext3):
# find / ! \( -fstype ext2 -o -fstype ext3 \) -prune -o ...
The find -prune
option causes
directories to be skipped, so we prune
any filesystems that do not match our desired
types (ext2 or ext3). The following -o
("or") operator causes the
filesystems that survive the pruning to be scanned.
The find -xdev option prevents crossing
filesystem boundaries,
and can be useful for avoiding uninteresting filesystems that might
be mounted. Our recipes use this option as a reminder to be conscious
of filesystem types.
- Carefully examine permissions
-
The find -perm option can conveniently select a subset
of the permissions, optionally ignoring the rest. In the most common
case, we are interested in testing for any of
the permissions in the subset: use a
"+" prefix with the permission
argument to specify this. Occasionally, we want to test
all of the permissions: use a
"-" prefix instead. If no prefix is used, then the entire set of
permissions is tested; this is rarely useful.
- Handle filenames safely
-
If you scan enough
filesystems, you will eventually
encounter filenames with embedded spaces or unusual characters like
newlines, quotation marks, etc. The null character, however,
never appears in filenames, and is therefore the
only safe separator to use for lists of filenames that are passed
between programs.
The find -print0 option produces null-terminated
filenames; xargs
and perl both
support a -0 (zero) option to read them. Useful
filters like sort and grep also
understand a -z option to use null separators when
they read and write data, and grep has a separate
-Z option that produces null-terminated filenames
(with the -l or -L options).
Use these options whenever possible to avoid misinterpreting
filenames, which can be disastrous when modifying filesystems as
root!
- Avoid long command lines
-
The Linux kernel imposes a 128 KB limit on the combined size of
command-line arguments and the environment. This limit can be
exceeded by using shell command
substitution, e.g.:
$ mycommand `find ...`
Use the xargs program instead to collect
filename arguments and run commands repeatedly, without exceeding
this limit:
$ find ... -print0 | xargs -0 -r mycommand
The xargs -r option avoids running the command if
the output of find is empty, i.e., no filenames
were found. This is usually desirable, to prevent errors like:
$ find ... -print0 | xargs -0 rm
rm: too few arguments
It can occasionally be useful to connect multiple
xargs invocations in a pipeline, e.g.:
$ find ... -print0 | xargs -0 -r grep -lZ pattern | xargs -0 -r mycommand
The first xargs collects filenames from
find and passes them to grep,
as command-line arguments. grep then searches the
file contents (which find cannot do) for the
pattern, and writes another list of filenames to stdout. This list is
then used by the second xargs to collect
command-line arguments for mycommand.
If you want grep to select filenames (instead of
contents), insert it directly into the pipe:
$ find ... -print0 | grep -z pattern | xargs -0 -r mycommand
In most cases, however, find -regex
pattern is a more direct way to
select filenames using a regular expression.
Note how grep -Z refers to writing filenames, while
grep -z refers to reading and writing data.
xargs is typically much faster than find
-exec, which runs the command separately for each file and
therefore incurs greater start-up costs. However, if you need to run
a command that can process only one file at a time, use either
find -exec or xargs -n
1:
$ find ... -exec mycommand '{}' \;
$ find ... -print0 | xargs -0 -r -n 1 mycommand
These two forms have a subtle difference, however: a command run by
find -exec uses the standard input inherited from
find, while a command run by
xargs uses the pipe as its standard input (which
is not typically useful).
find(1), xargs(1), mount(8).