• Aucun résultat trouvé

Using fchown() and fchmod() for Security

Dans le document What You Will Learn (Page 162-165)

Indirect System Calls

5.5. Changing Ownership, Permission, and Modification Times

5.5.4. Using fchown() and fchmod() for Security

The original Unix systems had only chown() and chmod() system calls. However, on heavily loaded systems, these system calls are subject to race conditions, by which an attacker could arrange to replace with a different file the file whose ownership or permissions were being changed.

However, once a file is opened, race conditions aren't an issue anymore. A program can use stat() on a pathname to obtain information about the file. If the information is what's expected, then after the file is opened,

fstat() can verify that the file is the same (by comparing the st_dev and st_ino fields of the "before" and

"after" struct stat structures).

Once the program knows that the files are the same, the ownership or permissions can then be changed with

fchown() or fchmod().

These system calls, as well as lchown(), are of relatively recent vintage;[11] older Unix systems won't have them, although modern, POSIX-compliant systems do.

[11]fchown() and fchmod() were introduced in 4.2 BSD but not picked up for System V until System V Release 4.

There are no corresponding futime() or lutime() functions. In the case of futime(), this is (apparently) because the file timestamps are not critical to system security in the same way that ownership and permissions are.

There is no lutime(), since the timestamps are irrelevant for symbolic links.

5.6. Summary

The file and directory hierarchy as seen by the user is one logical tree, rooted at /. It is made up of one or more storage partitions, each of which contains a filesystem. Within a filesystem, inodes store information about files (metadata), including the location of file data blocks.

Directories make the association between filenames and inodes. Conceptually, directory contents are just sequences of (inode, name) pairs. Each directory entry for a file is called a (hard) link, and files can have many links. Hard links, because they work only by inode number, must all be on the same filesystem.

Symbolic (soft) links are pointers to files or directories that work based on filename, not inode number, and thus are not restricted to being on the same filesystem.

Hard links are created with link(), symbolic links are created with symlink(), links are removed with

unlink(), and files are renamed (possibly being moved to another directory) with rename(). A file's data blocks are not reclaimed until the link count goes to zero and the last open file descriptor for the file is closed.

Directories are created with mkdir() and removed with rmdir(); a directory must be empty (nothing left but '.' and '..') before it can be removed. The GNU/Linux version of the ISO C remove() function calls

unlink() or rmdir() as appropriate.

Directories are processed with the opendir(), readdir(), rewinddir(), and closedir() functions.

A struct dirent contains the inode number and the file's name. Maximally portable code uses only the filename in the d_name member. The BSD telldir() and seekdir() functions for saving and restoring the current position in a directory are widely available but are not as fully portable as the other directory processing functions.

File metadata are retrieved with the stat() family of system calls; the struct stat structure contains all the information about a file except the filename. (Indeed, since a file may have many names or may even be completely unlinked, it's not possible to make the name available.)

The S_ISxxx() macros in <sys/stat.h> make it possible to determine a file's type. The major() and

minor() functions from <sys/sysmacros.h> make it possible to decode the dev_t values that represent block and character devices.

Symbolic links can be checked for using lstat(), and the st_size field of the struct stat for a symbolic link returns the number of bytes needed to hold the name of the pointed-to file. The contents of a symbolic link are read with readlink(). Care must be taken to get the buffer size correct and to terminate the retrieved filename with a trailing zero byte so that it can be used as a C string.

Several miscellaneous system calls update other information: the chown() family for the owner and group, the chmod() routines for the file permissions, and utime() to change file access and modification times.

Exercises

Write a routine 'const char *fmt_mode(mode_t mode)'. The input is a mode_t value as provided by the st_mode field in the struct stat; that is, it contains both the permission bits and the file type.

The output should be a 10-character string identical to the first field of output from 'ls -l'. In other words, the first character identifies the file type, and the other nine the permissions.

When the S_ISUID and S_IXUSR bits are set, use an s instead of an x; if only the I_ISUID bit is set, use an

S. Similarly for the S_ISGID and S_IXGRP bits. If both the S_ISVTX and S_IXOTH bits are set, use t; for

S_ISVTX alone, use T.

For simplicity, you may use a static buffer whose contents are overwritten each time the routine is called.

1.

Extend ch05-catdir.c to call stat() on each file name found. Then print the inode number, the result of

fmt_mode(), the link count, and the file's name.

2.

Extend ch05-catdir.c further such that if a file is a symbolic link, it will also print the name of the pointed-to file.

3.

Add an option such that if a filename is that of a subdirectory, the program recursively enters the subdirectory and prints information about the subdirectory's files (and directories). Only one level of recursion is needed.

4.

If you're not using a GNU/Linux system, run ch05-trymkdir (see Section 5.2, "Creating and Removing Directories," page 130) on your system and compare the results to those we showed.

5.

Write the mkdir program. See your local mkdir(1) manpage and implement all its options.

6.

In the root directory, /, both the device and inode numbers for '.' and '..' are the same. Using this bit of information, write the pwd program.

The program has to start by finding the name of the current directory by reading the contents of the parent directory. It must then continue, working its way up the filesystem hierarchy, until it reaches the root directory.

Printing the directory name backwards, from the current directory up to the root, is easy. How will your version of pwd manage to print the directory name in the correct way, from the root on down?

7.

If you wrote pwd using recursion, write it again, using iteration. If you used iteration, write it using recursion.

Which is better? (Hint: consider very deeply nested directory trees.) 8.

Examine the rpl_utime() function (see Section 5.5.3.1, "Faking utime(file, NULL)," page 159) closely.

What resource is not recovered if one of the tests in the middle of the if fails? (Thanks to Geoff Collyer.) 9.

(Hard.) Read the chmod(1) manpage. Write code to parse the symbolic options argument, which allows adding, removing, and setting permissions based on user, group, other, and "all."

Once you believe it works, write your own version of chmod that applies the permission specification to each file or directory named on the command line.

Which function did you use, chmod()—or open() and fchmod()—and why?

10.

Chapter 6. General Library Interfaces — Part

Dans le document What You Will Learn (Page 162-165)