...
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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
}
|
...
FIO32-C. Do not perform operations on devices that are only appropriate for files | |
FIO32-CPP. Do not perform operations on devices that are only appropriate for files | |
CWE-67. Improper handling of windows device names |
...
[API 2006] | Class |
11.5, Creating a Transient File | |
Section 5.6, Device Files | |
Chapter 11, Canonical Representation Issues | |
The try-with-resources Statement | |
[SDN 2008] | Bug IDs 4171239, 4405521, 4635827, 4631820 |