SWE210 Software Security Week 3
Memory Management in Operating Systems
Process and Memory:
When a program is executed, the OS assigns it a process.
Process = Instance of a program executed by CPU, consists of:
Memory allocation
Processor state
Resources associated with execution
Memory Segmentation:
Each process has memory divided into segments:
Text Segment (Code Segment):
Contains executable instructions.
Typically read-only and shareable across processes.
Data Segment:
Stores global/static variables initialized by the program.
Stack:
Stores temporary variables and function call information (return addresses, local variables).
Operates in a Last-In-First-Out (LIFO) manner.
Heap:
For dynamic memory allocation during runtime.
Memory must be explicitly requested and deallocated.
Example of Memory Segments
#include <stdio.h>
#include <stdlib.h>
int global_var = 10; // Stored in Data Segment
int main() {
int local_var = 5; // Stored in Stack
int *ptr = malloc(sizeof(int)); // Allocated in Heap
*ptr = 20;
printf("Sum = %d\n", local_var + global_var + *ptr);
free(ptr); // Free Heap memory
return 0;
}
Memory Injection Attacks
Definition:
Attacker exploits program/system vulnerabilities to inject malicious code into the memory space of a running process.
Common types: Array indexing, pointer subterfuge, stack smashing, heap smashing, etc.
Array Indexing Attacks
Array Access Vulnerabilities:
Provides arbitrary access to memory.
Risk of Buffer Overflow:
An index larger than the buffer size or less than zero can lead to buffer overflow.
Example Vulnerable Code:
int array[4];
bool authenticated = false;
int index;
cin >> index;
array[index] = -1; // If index == 5, it's a problem!
Mitigations:
Implement bounds checking to prevent vulnerabilities.
Array Indexing Attacks Mitigation Code:
#include <iostream>
#include <stdexcept>
using namespace std;
int main() {
int array[4];
bool authenticated = false;
int index;
cin >> index;
// Implementing bounds checking
if (index < 0 || index >= 4) {
throw out_of_range("Index is out of bounds!");
}
array[index] = -1;
}Pointer Subterfuge
Definition:
Overwriting a pointer to refer to different data than intended.
Risks:
Attacker may input values to alter pointers and display sensitive data.
Example Code:
long buffer[1];
char *p1 = "Safe";
char *p2 = "Rosebud";
cin >> buffer[1];
cout << p1;
Pointer Subterfuge Mitigation Code:
#include <iostream>
using namespace std;
int main() {
long buffer[1];
char *p1 = "Safe";
char *p2 = "Rosebud";
cin >> buffer[0]; // Restricting input to only buffer[0]
// Check for valid inputs before use\ // This prevents exploitation
if (buffer[0] < 0 || buffer[0] >= 1) {
cout << "Invalid input!";
return 1;
}
cout << p1;
}ARC Injection
Definition:
Overwriting a function pointer to execute unintended functions.
Example Code:
long buffer[1];
void (*pointerFunction)() = safe;
cin >> buffer[1];
pointerFunction();
ARC Injection Mitigation Code:
#include <iostream>
using namespace std;
void safe() {
cout << "Safe Function Executed";
}
int main() {
long buffer[1];
void (*pointerFunction)() = safe;
cin >> buffer[0]; // Restricting function pointer modifications
// Check if buffer is within safe limits before allowing any assignment
if (buffer[0] == 0) {
pointerFunction(); // Call safe function
}
}Stack Management
Stack Overview:
Abstract data type for ordered storage and retrieval (LIFO).
Operations: push() adds, pop() removes the top element.
Example Function Call:
void bar() {}
void foo() { bar(); }
int main() { foo(); }
Stack Smashing
Definition:
Exploiting stack buffer overruns to overwrite return pointers, redirecting control flow.
Example Attacker Input:
Entering carefully crafted input to manipulate the stack.
Example Vulnerable Code
void prompt()
char text[8];
cin >> text; //No size check (overfLows possible)
}Mitigated Code
void prompt (){
char text[8];
cin.getline(text, sizeof(text)); //Safe input with size check
}Comparison: Stack vs Heap
Aspect | Stack | Heap |
|---|---|---|
Allocation Mechanism | Static at compile-time | Dynamic at runtime |
Memory Management | LIFO | Not constrained by access order |
Memory Usage | Limited size | Larger size |
Access Speed | Faster | Slower compared to stack |
Deallocation | Automatic upon function return | Manual deallocation required |
Data Structure | Frame-based, smaller structures | Suitable for larger, dynamic variables |
Heap Smashing
Definition:
Exploiting vulnerabilities in dynamic memory allocation to overwrite critical heap metadata, leading to arbitrary code execution.
Logic of the Attack:
An attacker can manipulate data in the heap, causing overflow and modifying the control structures that manage memory. This could allow them to redirect execution flow to malicious code.
Example Vulnerable Code:
#include <stdlib.h>
void vulnerableFunction() {
int *ptr = malloc(10 * sizeof(int));
ptr[10] = 42; // Overflowing heap memory, writing beyond allocated space.
}
Mitigation:
Implement proper sizing and bounds checking during memory allocation and deallocation.
Heap Smashing Mitigation Code:
#include <stdlib.h>
#include <iostream>
void safeFunction() {
int *ptr = (int*)malloc(10 * sizeof(int));
if (ptr == NULL) {
std::cerr << "Memory allocation failed" << std::endl;
return;
}
// Proper bounds checking
if (10 < 10) {
ptr[10] = 42; // No overflow
}
// Free allocated memory
free(ptr);
}References
https://eecs280staff.github.io/notes/02ProceduralAbstractionT
Helfrich, J. N. (2018). Security for Software Engineers. CRC Press