Buffer Overflows / Race Conditions

Buffer Overflows and Race Conditions

Buffer Overflows

Buffer overflows are a type of memory corruption vulnerability. They occur when a program writes more data to a memory buffer than it was allocated to hold. This can overwrite adjacent memory locations.

Stack layout

image image image

Example of vulnerable code

void processData(int socket) {
    char buffer[256], tempBuffer[12]; 
    int count = 0, position = 0;
    
    /* Read data from socket and copy it into buffer */ 
    count = recv(socket, tempBuffer, 12, 0);
    while (count > 0) {
        memcpy(buffer + position, tempBuffer, count) position += count;
        count = recv(socket, tempBuffer, 12, 0);
    }
    /* Do something with the data in buffer */
}
image

Countermeasures

  1. A stack canary can be inserted into the stack to detect buffer overflows. The canary is a random value that is inserted:
image
  1. Prevent code execution in data segments (stack, part of Linux kernel since 2.6.7)
  2. Address Space Layout Randomization (ASLR) - randomizes the location of the stack and heap in memory

Race Conditions

Part of the Time and State kingdom

File Access Race Condition

e.g. There's a check of a file property before access. When using the file, it is assumed that the property is still valid. Such defects are called time-of-check-time-of-use (TOCTOU) defects.

For example:

// access checks if the user that started the program has write access to file, 
// returns 0 if true
if (!access(file, W_OK)) {
   printf("Enter data to write to file: ");
   fgets(data, 100, stdin);
   fd = fopen(file, "w+"); /* open the file for writing */ 
   if (fd != NULL) {
      fprintf(fd, "%s", data); 
   }
} else { /* user has no write access */
   fprintf(stderr, "Permission denied when trying to open %s.\n", file);
}

If the attacker has access to the local file system, he can use a symbolic link to change the file property between the check and the use: The attacker can create a symbolic link to a file he has access to, and then exchange the symbolic link with a file he doesn't have access to, but the running process has.

Countermeasures

  • Open the file once and then do all operations on the file descriptor
  • Avoid checking of file access rights and let the operating system handle it

Read Example

int doLogin(char usernames[][8], char passwords[][8]);

int main(int argc, char *argv[]) {
  char usernames[USERS][8] = {"root", "john", "tom"};       // There are 3 registered users...
  char passwords[USERS][8] = {"master", "doe", "qwertz"};   // ... with their passwords
  char* command = malloc(INPUT_MAX);
  
  while(1) {
    if (doLogin(usernames, passwords)) {
      printf("Login successful, terminal access granted, enter 'exit' to exit\n");
      while (1) {
        printf("$> ");
        fgets(command, INPUT_MAX, stdin);
        command[strlen(command)-1] = 0;   // remove newline character
        if (!strcmp(command, "exit")) {
          break;
        }
      }
    } 
  }
  return 0;
}

int doLogin(char usernames[][8], char passwords[][8]) {
  char* username = malloc(INPUT_MAX);
  char* password = malloc(INPUT_MAX);
  printf("Enter username: ");
  fgets(username, INPUT_MAX, stdin);
  username[strlen(username)-1] = 0;   // remove newline character
  printf("Enter password: ");
  fgets(password, INPUT_MAX, stdin);
  password[strlen(password)-1] = 0;   // remove newline character
  for (int i=0; i<USERS; i++) {
    if(!strcmp(username, usernames[i]) && !strcmp(password, passwords[i])) {
      free(username);
      free(password);
      return 1;   // Login successful
    }
  }
  printf("Sorry ");   // Login failed
  printf(username);
  printf(", try again\n");
  free(username);
  free(password);
  return 0;
}
image