Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

On Linux, it is possible to lock certain applications by attempting to read or write data on devices rather than files. Consider the following device path names:

Code Block

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

...

In this noncompliant code example, an attacker could specify the name of a locked device or a first in, first out (FIFO) file, causing the program to hang when opening the file.

Code Block
bgColor#ffcccc

String file = /* provided by user */;
InputStream in = null;
try {
  in = new FileInputStream(file);
  // ...
} finally {
  try {
    if (in !=null) { in.close();}
  } catch (IOException x) {
    // handle error
  }
}

...

This noncompliant code example uses the try-with-resources statement from Java SE 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.

Code Block
bgColor#ffcccc

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

...

This noncompliant code example first checks that the file is a regular file (using the new Java SE 7 NIO2 APIs) before opening it.

Code Block
bgColor#ffcccc

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

  // Check
  if (!attr.isRegularFile()) {
    System.out.println("Not a regular file");
    return;
  }
  // other necessary checks
  
  // Use
  try (InputStream in = Files.newInputStream(path)) {
    // read file
  }
} catch (IOException x) {
  // handle error
}

...

This noncompliant code example checks the file by calling the readAttributes() method with the NOFOLLOW_LINKS link option to prevent the method from following symbolic links. This allows the detection of symbolic links because the isRegularFile() check is carried out on the symbolic link file and not on the final target of the link.

Code Block
bgColor#ffcccc

String filename = /* provided by user */;
Path path = new File(filename).toPath();
try {
  BasicFileAttributes attr = Files.readAttributes(
      path, 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(path)) {
    // read file
  };
} catch (IOException x) {
  // handle error
}

...

As noted in the documentation, the FileKey cannot be used if it is not available. The fileKey() method returns null on Windows. Consequently this solution is only available on POSIX systems (actually any systems where fileKey() does not return null).

Code Block
bgColor#ffcccc

String filename = /* provided by user */;
Path path = new File(filename).toPath();
try {
  BasicFileAttributes attr = Files.readAttributes(
      path, 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(path)) {

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

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

...

Following is a POSIX-specific implementation of an isInSecureDir() method. This method ensures that the supplied file and all directories above it are owned by either the user or the system administrator, that each directory lacks write access for any other users, and that directories above the given file may not be deleted or renamed by any other users (except the system administrator).

Code Block
bgColor#ccccff

public static boolean isInSecureDir(Path file) {
  return isInSecureDir(file, null);
}
public static boolean isInSecureDir(Path file, UserPrincipal user) {
   return isInSecureDir(file, nulluser, 5);
}

/**
 * Indicates whether file lives in a secure directory relative
 * to the program's user
 * @param file Path to test
 * @param user User to test. If null, defaults to current user
 * @param symlinkDepth Number of symbolic links allowed
 * @return true if file's directory is secure
 */
public static boolean isInSecureDir(Path file, UserPrincipal user,
                                    int symlinkDepth) {
  if (!file.isAbsolute()) {
    file = file.toAbsolutePath();
  } if (symlinkDepth <=0) {
      // Too many levels of symbolic links
      return false;
    }

  // Get UserPincipal for specified user and superuser
  FileSystem fileSystem =
      Paths.get(file.getRoot().toString()).getFileSystem();
  UserPrincipalLookupService upls = 
      fileSystem.getUserPrincipalLookupService();
  UserPrincipal root = null;
  try {
    root = upls.lookupPrincipalByName("root");
    if (user == null) {
      user = upls.lookupPrincipalByName(System.getProperty("user.name"));
    }
    if (root == null || user == null) {
      return false;
    }
  } catch (IOException x) {
    return false;
  }

  // If any parent dirs (from root on down) are not secure,
  // dir is not secure
  for (int i = 1; i <= file.getNameCount(); i++) {
    Path partialPath = Paths.get(file.getRoot().toString(),
                                 file.subpath(0, i).toString());

    try {
      if (Files.isSymbolicLink(partialPath)) {
        if (!isInSecureDir(Files.readSymbolicLink(partialPath),)) {
                           user, symlinkDepth - 1)
          // Symbolic link, linked-to dir not secure
          return false;
        }
      } else {
        UserPrincipal owner = Files.getOwner(partialPath);
        if (!user.equals(owner) && !root.equals(owner)) {
          // dir owned by someone else, not secure
          return false;
        }
        PosixFileAttributes attr =
            Files.readAttributes(partialPath, PosixFileAttributes.class);
        Set<PosixFilePermission> perms = attr.permissions();
        if (perms.contains(PosixFilePermission.GROUP_WRITE) ||
            perms.contains(PosixFilePermission.OTHERS_WRITE)) {
          // someone else can write files, not secure
          return false;
        }
      }
    } catch (IOException x) {
      return false;
    }
  }

  return true;
}

...

The following compliant solution uses the isInSecureDir() method to ensure that an attacker cannot tamper with the file to be opened and subsequently removed. Note that once the path name of a directory has been checked using isInSecureDir(), all further file operations on that directory must be performed using the same path. This compliant solution also performs the same checks performed by the previous examples, such as making sure the requested file is a regular file, and not a symbolic link, device file, or other special file.

Code Block
bgColor#ccccff

String filename = /* provided by user */;
Path path = new File(filename).toPath();
try {
  if (!isInSecureDir(path)) {
    System.out.println("File not in secure directory");
    return;
  }

  BasicFileAttributes attr = Files.readAttributes(
      path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);

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

  try (InputStream in = Files.newInputStream(path)) {
    // read file
  }
} catch (IOException x) {
  // handle error
}

...

...

[API 2006]

Class File, methods createTempFile, delete, deleteOnExit

[Darwin 2004]

11.5, Creating a Transient File

[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()

[SDN 2008]

Bug IDs 4171239, 4405521, 4635827, 4631820

[Secunia 2008]

Secunia Advisory 20132

 

12. Input Output (FIO)      12. Input Output (FIO)