Race Conditions
Some of the information of the information has come from pwn.college's lecture's as this was the resource that helped me the most learning about race conditions
Initalize()
do_action_1()
do_action_2()
do_action_3()
finalize()
Initalize()
do_action_1()
do_action_2()
do_action_3()
finalize()
pInitalize()
do_action_1()
do_action_2()
do_action_3()
finalize()
p2_Initalize()
p2_do_action_1()
p2_do_action_2()
p2_finalize()
p1_Initalize()
p1_do_action_1()
p1_do_action_2()
p1_finalize()
p2_Initalize()
p2_do_action_1()
p2_do_action_2()
p2_finalize()
p1_Initalize()
p1_do_action_1()
p1_do_action_2()
p1_finalize()
p2_Initalize()
p2_do_action_1()
p2_do_action_2()
p2_finalize()
p1_Initalize()
p1_do_action_1()
p1_do_action_2()
p1_finalize()
p2_Initalize()
p2_do_action_1()
p2_do_action_2()
p2_finalize()
p1_Initalize()
p1_do_action_1()
p1_do_action_2()
p1_finalize()
Application
Write()
Read()
sys_write
sys_read
Firmware
EXT4, FAT32 ...
Disk
Partition
Block
Block
Block
Block
https://en.wikipedia.org/wiki/Inode_pointer_structure
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd = open(
argv[1],
O_WRONLY | O_CREAT | O_TRUNC,
0755);
write(fd, "#!/bin/sh\necho SAFE\n", 20);
close(fd);
execl("/bin/sh", "/bin/sh", argv[1], NULL);
return 0;
}while /bin/true; do cp -v ./catflag asdf; donewittsend2@wittsend2-virtual-machine:[~/Documents/race_condition_test]
$ ./main asdf
SAFE
wittsend2@wittsend2-virtual-machine:[~/Documents/race_condition_test]
$ ./main asdf
SAFE
wittsend2@wittsend2-virtual-machine:[~/Documents/race_condition_test]
$ ./main asdf
SAFE
wittsend2@wittsend2-virtual-machine:[~/Documents/race_condition_test]
$ ./main asdf
SAFE
wittsend2@wittsend2-virtual-machine:[~/Documents/race_condition_test]
$ ./main asdf
FLAG
Source pwn.college
#!/bin/sh
# catflag script
cat /flagThe first classwork is an example of this.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int echo_fd = open("/bin/echo", O_RDONLY);
int fd = open(
argv[1],
O_WRONLY | O_CREAT,
0755);
sendfile(fd, echo_fd, 0, 1024*1024);
close(fd);
execl(argv[1], argv[1], "SAFE", NULL);
return 0;
}Source pwn.college
// Check if file is safe to write
if (access("/tmp/myfile", W_OK) == 0) {
// Time gap here - attacker can act!
FILE *f = fopen("/tmp/myfile", "w");
fprintf(f, "sensitive data");
fclose(f);
}Scheduling Priorities:
Path Complexity:
if (access("file", W_OK) != 0) {
exit(1);
}
fd = open("file", O_WRONLY);
write(fd, buffer, sizeof(buffer));What is wrong here?
if (access("file", W_OK) != 0) {
exit(1);
}
fd = open("file", O_WRONLY);
write(fd, buffer, sizeof(buffer));symlink("/etc/passwd", "file");Victim
Attacker
Every process has atleast one thread
Threads:
void *threadFunc(int arg)
{
printf("Thread %d, PID %d, TID %d, UID %d\n", arg, getpid(), gettid(), getuid());
}
void main()
{
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, threadFunc, 1);
pthread_create(&thread1, NULL, threadFunc, 2);
printf("Main thread PID %d, TID %d, UID %d", getpid(), gettid(), getuid());
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
}clone(
child_stack = 0x7f81afdc7fb0,
flags = CLONE_VM| // parent and child will run in the same memory space
CLONE_FS| // parent and child will share filesystem info (chroot(), chdir(), and similar effects will be shared between parent and child)
CLONE_FILES| // parent and child will share file descriptors
CLONE_SIGHAND| // parent and child will share signal handlers
CLONE_THREAD| // signify that the child is a fellow thread of the parent
CLONE_SYSVSEM| // parent and child will share semaphore information
CLONE_SETTLS| // set a unique "Thread Local Storage" area for the new thread
CLONE_PARENT_SETTID| // store the new thread ID at the memory location pointed to by the tid arg below
CLONE_CHILD_CLEARTID, // zero out the thread ID at the location pointed to by the tid arg below when the child exits
parent_tid=[1926535],
tls=0x7f81afdc8700,
child_tidptr=0x7f81afdc89d0
)unsigned int size = 128;
void read_data()
{
char buffer[16];
if (buffer < 16)
{
printf("Valid size!");
read(0, buffer, 16);
printf("%s\n", buffer);
}
}
void *start_thread()
{
while (true)
{
read_data();
}
}
void main()
{
pthread_t thread;
pthread_create(&thread, NULL, thread_allocator, 0);
while(size != 0)
{
read(0, &size, 1);
}
return;
}#define SECRET "123456"
bool canAccess = false;
char readData[64];
void *accessSensitiveInfo()
{
char password[64];
printf("Enter password: ");
scanf("%s", buffer);
if (password == SECRET)
{
canAccess = true;
}
if (canAccess)
{
int fd = open("/path/to/sensitiveFile", O_RDONLY);
read(fd, readData, 64);
close(fd);
}
}
void main()
{
pthread_t user1, user2, user3;
pthread_create(&user1, NULL, accessSensitiveInfo, 0);
pthread_create(&user2, NULL, accessSensitiveInfo, 0);
pthread_create(&user3, NULL, accessSensitiveInfo, 0);
pthread_join(&user1, NULL);
pthread_join(&user2, NULL);
pthread_join(&user3, NULL);
exit(0);
}int check_safety(char *user_buffer, int maximum_size) {
int size;
copy_from_user(&size, user_buffer, sizeof(size));
return size <= maximum_size;
}
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long user_buffer) {
int size;
char buffer[16];
if (!check_safety(user_buffer, 16)) return;
copy_from_user(&size, user_buffer, sizeof(size));
copy_from_user(buffer, user_buffer+sizeof(size), size);
}
int check_safety(char *user_buffer, int maximum_size) {
int size;
copy_from_user(&size, user_buffer, sizeof(size));
return size <= maximum_size;
}
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long user_buffer) {
int size;
char buffer[16];
if (!check_safety(user_buffer, 16)) return;
copy_from_user(&size, user_buffer, sizeof(size));
copy_from_user(buffer, user_buffer+sizeof(size), size);
}
Example of Time of Check to Time of Use race condition in the Linux kernel
#include <stdio.h>
#include <pthread.h>
unsigned int n = 0;
void *thread_function()
{
while(1)
{
n++;
n--;
printf("Num = %d\n", n);
}
}
int main()
{
pthread_t thread1;
pthread_t thread2;
pthread_t thread3;
pthread_create(&thread1, NULL, thread_function, 0);
pthread_create(&thread2, NULL, thread_function, 0);
pthread_create(&thread3, NULL, thread_function, 0);
getchar();
return 0;
}Without Mutex
unsigned int n = 0;
pthread_mutex_t mutex l;
void *thread_function()
{
while(1)
{
pthread_mutex_lock(&l);
n++;
n--;
printf("Num = %d\n", n);
pthread_mutext_unlock(&l);
}
}
int main()
{
pthread_t thread1;
pthread_t thread2;
pthread_t thread3;
pthread_create(&thread1, NULL, thread_function, 0);
pthread_create(&thread2, NULL, thread_function, 0);
pthread_create(&thread3, NULL, thread_function, 0);
getchar();
return 0;
}With Mutex
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t mutex;
void* thread(void* arg)
{
//wait
sem_wait(&mutex);
printf("\nEntered..\n");
//critical section
sleep(4);
//signal
printf("\nJust Exiting...\n");
sem_post(&mutex);
}
int main()
{
sem_init(&mutex, 0, 1);
pthread_t t1,t2;
pthread_create(&t1,NULL,thread,NULL);
sleep(2);
pthread_create(&t2,NULL,thread,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
sem_destroy(&mutex);
return 0;
}With Semaphores
bool adminUser = false;
void signal_handler(int signum) {
adminUser = true;
}
int main() {
signal(SIGUSR1, signal_handler);
while (1) {
if (adminUser) printf("Key is abcd1234\n");
}
}
int hold;
void swap(int *x, int *y)
{
hold = *x;
*x = *y;
*y = tmp;
}
int call_swap()
{
int x = 1; y = 2;
swap(x, y);
}
int main()
{
signal(SIGUSER1, call_swap);
call_swap();
}Reentrant function are functions that would operate properly even when interrupted with an instance of themselves.
Are any of these functions reentrant?
No! All of these functions call swap(), which is a non-reentrant function. The signal handler is call_swap too!
void swap(int *x, int *y)
{
int hold = *x;
*x = *y;
*y = tmp;
}
int call_swap()
{
int x = 1; y = 2;
swap(x, y);
}
int main()
{
signal(SIGUSER1, call_swap);
call_swap();
}Reentrant function are functions that would operate properly even when interrupted with an instance of themselves.
Are any of these functions reentrant?
Yes they are! All variables are non-global and no global variables are modified by the signal handler
Note: malloc and free are non reentrantn
man signal-safety