Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: wrapped to functions

...

In this noncompliant code example, the file identified by the string filename is opened, processed, closed, and then reopened for reading:

Code Block
bgColor#FFcccc
public void fio52_nce(String filename){
	// Identify a file by its path
String filename = // Initialized
Path file1 = Paths.get(filename);
 
 
    // Open the file for writing
    try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file1))))
 {
	        // Write to file...
    }
 catch (Exception e) {
		    	System.out.println("Exception during file access" + e);
    } 
    // Close the file

    /*
     * A race condition here allows for an attacker to switch
     * out the file for another
     */


    // Reopen the file for reading
    Path file2 = Paths.get(filename);
    
    
    try(BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(file2))))
{
	        String line;
	        while ((line = br.readLine()) != null) {
		            System.out.println(line);
	        }
    }
 catch (Exception e) {
	        System.out.println("Exception during file access" + e);
    }
}

There is no guarantee that the file opened for reading is the same file that was opened for writing. An attacker can replace the original file (for example, with a symbolic link) between the first call to close() and the subsequent creation of the BufferedReader.

...

In this noncompliant code example, the programmer attempts to ensure that the file opened for reading is the same as the file previously opened for writing by calling the method isSameFile():

Code Block
bgColor#FFcccc
public void fio52_nce1(String filename){
    // Identify a file by its path
String filename = // Initialized
Path file1 = Paths.get(filename);
    
// Open the file for writing
    try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file1))))
 {
	            // Write to file
    }
 catch (Exception e) {
      System.out.println("Exception during file access" + e);
    } 

 
    // ...
    // Reopen the file for reading
    Path file2 = Paths.get(filename);
   
 if (!Files.isSameFile(file1, file2)) {
      System.out.println("File tampered with");
      // Handle error
    }
  
  try(BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(file2))))
 { 
	            String line;
 
	            while ((line = br.readLine()) != null) {
		                    System.out.println(line);
	            }
    }
 catch (Exception e) {
      System.out.println("Exception during file access" + e);
    }
}

Unfortunately, there is no guarantee that the method isSameFile() really checks that the files are the same file. The Java 7 API for isSameFile() says:

...

This compliant solution checks the creation and last-modified times of the files to ensure that the file opened for reading is the same file that was written:

Code Block
bgColor#ccccff
public void fio52_ce(String filename) throws IOException{
   // Identify a file by its path
String filename = // Initialized
Path file1 = Paths.get(filename);
   BasicFileAttributes attr1 = Files.readAttributes(file1, BasicFileAttributes.class);
   FileTime creation1 = attr1.creationTime();
   FileTime modified1 = attr1.lastModifiedTime();


   // Open the file for writing
   try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file1))))
 {
	           // Write to file...
   }
 catch (Exception e){
	           System.out.println("Exception during file access" + e);
   } 


 
   // Reopen the file for reading
   Path file2 = Paths.get(filename);
   BasicFileAttributes attr2 = Files.readAttributes(file2, BasicFileAttributes.class);
   FileTime creation2 = attr2.creationTime();
   FileTime modified2 = attr2.lastModifiedTime();
   if ( (!creation1.equals(creation2)) || (!modified1.equals(modified2)) ) {
     System.out.println("File tampered with");
     // Handle error
   }

   try(BufferedReader br = new BufferedReader( new InputStreamReader(Files.newInputStream(file2))))
{
		String line;
	        while ((line = br.readLine()) != null) {
	        	System.out.println(line);
	        }
   }
 catch (Exception e){
		       System.out.println("Exception during file access" + e);
   }
}

Although this solution is reasonably secure, a determined attacker could create a symbolic link with the same creation and last-modified times as the original file. Also, a time-of-check, time-of-use (TOCTOU) race condition occurs between the time the file's attributes are first read and the time the file is first opened. Likewise, a second TOCTOU condition occurs the second time the attributes are read and the file is reopened.

...

In environments that support the fileKey attribute, a more reliable approach is to check that the fileKey attributes of the two files are the same. The fileKey attribute is an object that "uniquely identifies the file" [API 2011], as shown in this compliant solution:

Code Block
bgColor#ccccff
public void fio52_ce1(String filename) throws IOException{
    // Identify a file by its path
String filename = // Initialized
Path file1 = Paths.get(filename);
    BasicFileAttributes attr1 = Files.readAttributes(file1, BasicFileAttributes.class);
    Object key1 = attr1.fileKey();

    // Open the file for writing
    try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file1))))
 {
	            // Write to file
    }
 catch (Exception e) {
		    	System.out.println("Exception during file access" + e);
    } 

 
    // Reopen the file for reading
    Path file2 = Paths.get(filename);
    BasicFileAttributes attr2 = Files.readAttributes(file2, BasicFileAttributes.class);
    Object key2 = attr2.fileKey();


    if ( !key1.equals(key2) ) {
      System.out.println("File tampered with");
      // Handle error
    }


    try(BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(file2))))
 {
    	String line;
	        while ((line = br.readLine()) != null) {
		        	System.out.println(line);
	        }
    }
 catch (Exception e) {
		        System.out.println("Exception during file access" + e);
    }
} 

This approach will not work on all platforms. For example, on an Intel Core i5-2400 machine running Windows 7 Enterprise, all fileKey attributes are null.

...

A better approach is to avoid reopening a file. The following compliant solution demonstrates use of a RandomAccessFile, which can be opened for both reading and writing. Since the file is never closed, no race condition is possible.

Code Block
bgColor#ccccff
public void fio52_ce2(String filename) throws IOException{
    // Identify a file by its path
String filename = // Initialized
RandomAccessFile file = new RandomAccessFile( filename, "rw");


    // Write to file...


    // Go back to beginning and read contents
    file.seek(0);
    try {
      while (true) {
        String s = file.readUTF();
        System.out.print(s);
      }
    } catch (EOFException x) {
      // Ignore, this breaks out of while loop
    }

    br.close();
}

Applicability

Many file-related vulnerabilities are exploited to cause a program to access an unintended file. Proper file identification is necessary to prevent exploitation.

...