Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Brought back the GetFileType example, as an NCCE

...

This TOCTOU race condition can be prevented if the effected files are maintained in a secure directory. (See FIO15-C. Ensure that file operations are performed in a secure directory.)

...

Noncompliant Code Example (Windows)

Microsoft documents a list of reserved identifiers that represent devices, as well as have a device namespace to be used specifically by devices [MSDN].  This compliant solution tests the given file name against these constructs:This noncompliant code example uses the GetFileType() API: 

Code Block
bgColor#ccccff#ffcccc
langc
#include <ctype<Windows.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
static bool isReservedNamevoid func(const charTCHAR *pathfile_name) {
  /* ThisHANDLE listhFile of reserved names comes from MSDN. */
  static const char *reserved[] = { "nul", "con", "prn", "aux",
                                    "com1", "com2", "com3",
                                    "com4", "com5", "com6",
                                    "com7", "com8", "com9",
                                    "lpt1", "lpt2", "lpt3",
                                    "lpt4", "lpt5", "lpt6",
                                    "lpt7", "lpt8", "lpt9" };
  char *lower;
  char *temp;
  bool ret = false;
  
  /* First, check to see if this is a device namespace, which
     always starts with \\.\, since device namespaces are not
     legal file paths. */
  temp = strstr(path, "\\\\.\\");
  if (temp == path) {
    return true;
  }
 
  /* Since Windows uses a case insensitive file system, operate
     on a lowercase version of the given filename. Note: this
     ignores globalization issues and assumes ASCII
     characters. */
  lower = (char *)malloc(strlen(path) + 1);
  if (!lower) {
    return false;
  }
  
  temp = lower;
  while (*path) {
    *lower++ = tolower(*path++);
  }
  lower = temp;
 
  /* Compare against the list of ancient reserved names. */
  for (size_t i = 0; !ret &&
       i < sizeof(reserved) / sizeof(*reserved); ++i) {    
    if (0 == strcmp(lower, reserved[i])) {
      ret = true;
    }
  }
 
  free(lower);
  return ret;
} 

...

= CreateFile(file_name,
                            GENERIC_READ | GENERIC_WRITE, 0, 
                            NULL, OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == INVALID_HANDLE_VALUE) {
    /* Handle error */
  } else if (GetFileType(hFile) != FILE_TYPE_DISK) {
    /* Handle error */
    CloseHandle(hFile);
  } else {
    /* Operate on the file. */
    CloseHandle(hFile);
  }
}

Compliant Solution (Windows)

While it may be tempting to use the Win32 GetFileType() function, it is a dangerous API to use in this case. If the file name given identifies a named pipe, and that pipe is currently blocking on a read request, the call to GetFileType() will block until the read request completes. That allows an attacker to effectively launch a denial-of-service attack on your application. Furthermore, the act of opening a file handle may cause further action to be taken, such as line states being set to their default voltage when opening a serial device.

Microsoft documents a list of reserved identifiers that represent devices, as well as have a device namespace to be used specifically by devices [MSDN].  This compliant solution tests the given file name against these constructs:

Code Block
bgColor#ccccff
langc
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
static bool isReservedName(const char *path) {
  /* This list of reserved names comes from MSDN. */
  static const char *reserved[] = { "nul", "con", "prn", "aux",
                                    "com1", "com2", "com3",
                                    "com4", "com5", "com6",
                                    "com7", "com8", "com9",
                                    "lpt1", "lpt2", "lpt3",
                                    "lpt4", "lpt5", "lpt6",
                                    "lpt7", "lpt8", "lpt9" };
  char *lower;
  char *temp;
  bool ret = false;
  
  /* First, check to see if this is a device namespace, which
     always starts with \\.\, since device namespaces are not
     legal file paths. */
  temp = strstr(path, "\\\\.\\");
  if (temp == path) {
    return true;
  }
 
  /* Since Windows uses a case insensitive file system, operate
     on a lowercase version of the given filename. Note: this
     ignores globalization issues and assumes ASCII
     characters. */
  lower = (char *)malloc(strlen(path) + 1);
  if (!lower) {
    return false;
  }
  
  temp = lower;
  while (*path) {
    *lower++ = tolower(*path++);
  }
  lower = temp;
 
  /* Compare against the list of ancient reserved names. */
  for (size_t i = 0; !ret &&
       i < sizeof(reserved) / sizeof(*reserved); ++i) {    
    if (0 == strcmp(lower, reserved[i])) {
      ret = true;
    }
  }
 
  free(lower);
  return ret;
} 

Risk Assessment

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

...