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
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 */
}
Countermeasures
- A stack canary can be inserted into the stack to detect buffer overflows. The canary is a random value that is inserted:
- Prevent code execution in data segments (stack, part of Linux kernel since 2.6.7)
- 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;
}