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()
p2_Initalize()
p2_do_action_1()
p2_do_action_2()
p2_finalize()
p1_Initalize()
p1_do_action_1()
p1_do_action_2()
p1_finalize()
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
)
#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; done
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
SAFE
wittsend2@wittsend2-virtual-machine:[~/Documents/race_condition_test]
$ ./main asdf
FLAG
Source pwn.college
#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
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
Scheduling Priorities:
Path Complexity:
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
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