You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 8 Next »

File names on many operating systems, including Windows and UNIX, may be used to access special files, which are actually devices. Reserved MS-DOS device names include AUX, CON, PRN, COM1, and LPT1. Device files on UNIX systems are used to apply access rights and to direct operations on the files to the appropriate device drivers.

Performing operations on device files that are intended for ordinary character or binary files can result in crashes and denial-of-service attacks. For example, when Windows attempts to interpret the device name as a file resource, it performs an invalid resource access that usually results in a crash [[Howard 2002]].

Device files in UNIX can be a security risk when an attacker can 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 or simply crash the system. Similarly, access to disk devices, tape devices, network devices, and terminals being used by other processes all can lead to problems [[Garfinkel 1996]].

On Linux, it is possible to lock certain applications by attempting to open devices rather than files. Consider the following example:

/dev/mouse
/dev/console
/dev/tty0
/dev/zero

A Web browser that failed to check for these devices would allow an attacker to create a Web site with image tags such as <IMG src="file:///dev/mouse"> that would lock the user's mouse.

More information on validating untrusted file names is available at IDS21-J. Canonicalize path names before validating them

Noncompliant Code Example

In this noncompliant code example, the user can specify a locked device or a FIFO file name, causing the program to hang on the file open.

String file = /* specified by user */
InputStream in = new FileInputStream( file);
// ...
in.close();

Noncompliant Code Example (Java 1.7)

This noncompliant code example uses the try-with-resources statement from Java 1.7 to open the file. While this guarantees the file's successful closure if an exception is thrown, it is subject to the same vulnerabilities as the previous example.

String filename = /* provided by user */
Path file = new File(filename).toPath();
try (InputStream in = Files.newInputStream( file)) {
   // read file
} catch (IOException x) {
  // handle error
}

Noncompliant Code Example (Java 1.7: isRegularFile())

This noncompliant code example first checks that the file is a regular file (rather than a directory, special file, etc), before opening the file.

String filename = /* provided by user */
Path file = new File(filename).toPath();
try {
  BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class);

  // Check
  if (!attr.isRegularFile()) {
    System.out.println("Not a regular file");
    return;
  }
  // other necessary checks

  // Use
  try (InputStream in = Files.newInputStream( file)) {

      // read file
    };
} catch (IOException x) {
  // handle error
}

This code can still be foiled by a symbolic link. The readAttributes() method, like most methods that operate on paths, will happily follow a symbolic link to check the file the link points to. This permits an attacker to provide a symbolic link that points to a forbidden file.

Noncompliant Code Example (Java 1.7: NOFOLLOW)

This noncompliant code example first checks the file, using the NOFOLLOW link option to check a symbolic links, rather than checking the linked-to file. So the check will corectly identify symbolic links.

String filename = /* provided by user */
Path file = new File(filename).toPath();
try {
  BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);

  // Check
  if (!attr.isRegularFile()) {
    System.out.println("Not a regular file");
    return;
  }
  // other necessary checks

  // Use
  try (InputStream in = Files.newInputStream( file)) {

      // read file
    };
} catch (IOException x) {
  // handle error
}

This code is still subject to a TOCTOU race condition. If an attacker swaps a malicious file for the benign file after the code has concluded its checks but before it opens the file, the code may still open the malicious file. While this requires precise timing on the part of the attacker, it is still a possibility.

Compliant Solution (Java 1.7: Check-Use-Check)

This compliant solution performs necessary checks and then opens the file. After opening the file, it does a second check to make sure that the file has not been moved, and that the file it opened is the same one it checked. This minimizes the chance that an attacker has changed the file between the check and the file's opening. In both checks, the file's fileKey attribute is examined. This serves as a unique key for identifying files, and is a more reliable indicator of a file's identity than its path.

[[J2SE 2011]] documents the fileKey attribute as follows:

Returns an object that uniquely identifies the given file, or null if a file key is not available. On some platforms or file systems it is possible to use an identifier, or a combination of identifiers to uniquely identify a file. Such identifiers are important for operations such as file tree traversal in file systems that support symbolic links or file systems that allow a file to be an entry in more than one directory. On UNIX file systems, for example, the device ID and inode are commonly used for such purposes.

The file key returned by this method can only be guaranteed to be unique if the file system and files remain static. Whether a file system re-uses identifiers after a file is deleted is implementation dependent and therefore unspecified.

File keys returned by this method can be compared for equality and are suitable for use in collections. If the file system and files remain static, and two files are the same with non-null file keys, then their file keys are equal.

String filename = /* provided by user */
Path file = new File(filename).toPath();
try {
  BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
  Object fileKey = attr.fileKey();

  // Check
  if (!attr.isRegularFile()) {
    System.out.println("Not a regular file");
    return;
  }
  // other necessary checks

  // Use
  try (InputStream in = Files.newInputStream( file)) {

      // Check
      BasicFileAttributes attr2 = Files.readAttributes(file, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
      Object fileKey2 = attr2.fileKey();
      if (fileKey != fileKey2) {
        System.out.println("File has been tampered with");
      }

      // read file
    };
} catch (IOException x) {
  // handle error
}

This code is still not perfectly secure. First, it has a TOCTOU race condition between the first check and open. During this race window, an attacker can substitute the file with a malicious file. The second check detects this race condition but does not eliminate it; an attacker could submit a malicious file that causes the system to block upon opening the file.

Furthermore, an attacker could subvert this code by letting the check operate on a benign file, substituting the malicious file for the open, and then substituting the benign file for the malicious file to circumvent the second check. This vulnerability exists because Java lacks any mechanism to obtain file attributes from a file by any means other than the file's path. That is, it lacks the equivalent of C's fstat(3) system call.

Essentially, an attacker can switch out a file for one of the file types shown in the table with the specified effect.

Table - File types and effect

Type

Note on effect

another regular file

The 2nd check fails

FIFO

Either the file open fails, or the 2nd check fails

symbolic link

The 1st check fails, but only if NOFOLLOW is functional

special device

Either the file open fails, or the 2nd check fails. This can still be a problem if the device is one for which just opening (or closing) it causes something to happen.

These vulnerabilities can be prevented if the effected files are maintained in a secure directory.

Risk Assessment

Allowing operations to be performed on devices that are only appropriate for files can result in denial-of-service attacks or more serious exploits depending on the platform.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

FIO04-J

medium

unlikely

medium

P4

L3

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

CERT C++ Secure Coding Standard: FIO32-CPP. Do not perform operations on devices that are only appropriate for files

CERT C Secure Coding Standard: FIO32-C. Do not perform operations on devices that are only appropriate for files

MITRE CWE: CWE-67, "Failure to Handle Windows Device Names"

Bibliography

[[Garfinkel 1996]] Section 5.6, "Device Files"
[[Howard 2002]] Chapter 11, "Canonical Representation Issues"
[[J2SE 2011]] The try-with-resources Statement
[[Open Group 2004]] open()


FIO01-J. Do not expose buffers created using the wrap() or duplicate() methods to untrusted code      12. Input Output (FIO)      FIO05-J. Do not create multiple buffered wrappers on a single InputStream

  • No labels