Skip to main content
  1. Posts/

CWE-787 - The Bird-Eye View for Java Developers

Sven Ruppert
Author
Sven Ruppert
20+ years of Java, specialised in Security, Vaadin and Developer Relations. When not coding, you’ll find me in the woods with an axe.
Table of Contents

The term “CWE-787: Out-of-bounds Write " likely refers to a specific security vulnerability or error in software systems. Let’s break down what it means:

Out-of-bounds Write : This is a type of vulnerability where a program writes data outside the boundaries of pre-allocated fixed-length buffers. This can corrupt data, crash the program, or lead to the execution of malicious code.

CWE-787 : This is a specific identifier for the vulnerability. Identifiers like these are often used in vulnerability databases or bug-tracking systems. They help uniquely identify and reference a particular issue.

  1. Explanation of Out-of-bounds Write
  2. Impact of Out-of-bounds Write
  3. Examples and Mitigation
    1. Example in C:
    2. Mitigation:
    3. Example of Safe Code:
  4. And how is it done in Java?
    1. Understanding Out-of-bounds Write in Java
    2. Example of Out-of-bounds Write
    3. Mitigation Strategies
    4. Corrected Example
  5. Common Causes of Out-of-bounds Write in Java
  6. Advanced Example
    1. Mitigated Version
  7. CWE 787 - Based on OffHeap techniques in Java 8
    1. Off-heap Memory Management in Java
    2. Example of Out-of-bounds Write with Off-heap Memory
      1. Using sun.misc.Unsafe
      2. Mitigating Out-of-bounds Write with Off-heap Memory
      3. Corrected Example with sun.misc.Unsafe
      4. Using java.nio.ByteBuffer
    3. Handling Larger Data Structures
      1. Example with IntBuffer
      2. Explanation
  8. OffHeap usage in Java 21
    1. Critical Features for Off-Heap Usage in Java 21
    2. Foreign Function & Memory API
    3. Example Using Foreign Function & Memory API
    4. Enhanced ByteBuffer Operations
    5. Memory Segments and Access VarHandles
      1. Example Using Memory Segments
  9. Differences in OffHeap usage between Java 17 and Java 21
    1. Foreign Function & Memory API
    2. Enhanced ByteBuffer Operations
    3. Memory Segments and VarHandles
    4. Summary of Differences

Explanation of Out-of-bounds Write
#

An out-of-bounds write occurs when a program writes data past the end, or before the beginning, of the buffer it was allocated. This usually happens due to programming errors such as:

  • Incorrectly calculating buffer sizes.
  • Failing to check the bounds before writing data.
  • Using unsafe functions that do not perform bounds checking.

Impact of Out-of-bounds Write
#

The consequences of an out-of-bounds write can be severe, including:

Data Corruption : Overwriting adjacent memory locations can corrupt other data.

Program Crashes : Writing to illegal memory addresses can cause the program to crash.

Security Vulnerabilities : Attackers can exploit these vulnerabilities to execute arbitrary code, leading to potential breaches, data leaks, or system compromise.

Examples and Mitigation
#

Example in C:
#

#include <string.h>
#include <stdio.h>
int main() {
    char buffer[10];
    strcpy(buffer, "This string is too long for the buffer");
    printf("%s\n", buffer);
    return 0;
}

In this example, the string copied into buffer exceeds its allocated size, causing an out-of-bounds write.

Mitigation:
#

Bounds Checking : Ensure that any write operations do not exceed the allocated buffer size.

Use Safe Functions : Utilise safer library functions like strncpy instead of strcpy.

Static Analysis : Use static analysis tools to detect out-of-bounds writes during development.

Runtime Protection : Employ runtime protections such as address space layout randomisation (ASLR) and stack canaries.

Example of Safe Code:
#

#include <string.h>
#include <stdio.h>
int main() {
    char buffer[10];
    strncpy(buffer, "This string is too long for the buffer", sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';  // Ensure null termination
    printf("%s\n", buffer);
    return 0;
}

In this corrected version, strncpy ensures that no more than the allocated size of buffer is written, and null-terminating the string prevents buffer overflow.

Understanding and preventing out-of-bounds writes is crucial in developing secure and stable software. Proper coding practices, safe functions, and security measures can mitigate these vulnerabilities effectively.

And how is it done in Java?
#

Out-of-bounds write vulnerabilities are less common in Java than in languages like C or C++ due to built-in safety features, such as automatic bounds checking for arrays. However, certain situations can still lead to such vulnerabilities, often related to misusing arrays or improper handling of indices.

Understanding Out-of-bounds Write in Java
#

In Java, out-of-bounds write errors typically occur when you try to access an array or a list with an index that is either negative or greater than or equal to the size of the array or list. This usually results in an ArrayIndexOutOfBoundsException.

Example of Out-of-bounds Write
#

public class OutOfBoundsWriteExample {
    public static void main(String[] args) {
        int[] numbers = new int[5];
        for (int i = 0; i <= numbers.length; i++) { // Off-by-one error
            numbers[i] = i * 2; // This will throw an ArrayIndexOutOfBoundsException
        }
    }
}

In this example, the loop runs from 0 to numbers.length, one past the last valid index. This causes an ArrayIndexOutOfBoundsException.

Mitigation Strategies
#

Proper Bounds Checking : Always ensure that any access to arrays or lists is within valid bounds.

Enhanced for Loop : Use enhanced for loops when possible to avoid index errors.

Libraries and Functions : Use Java’s standard libraries and methods that handle bounds checking automatically.

Corrected Example
#

public class OutOfBoundsWriteExample {
    public static void main(String[] args) {
        int[] numbers = new int[5];
        for (int i = 0; i < numbers.length; i++) { // Correct bounds
            numbers[i] = i * 2;
        }
        for (int number : numbers) {
            System.out.println(number);
        }
    }
}

In this corrected version, the loop runs from 0 to numbers.length—1, which prevents the ArrayIndexOutOfBoundsException.

Common Causes of Out-of-bounds Write in Java
#

Off-by-one Errors : These occur when loops iterate one time too many or too few.

Incorrect Index Calculation : When the calculation of an index is erroneous due to logic errors.

Manual Array Management : Errors often happen when manipulating arrays directly rather than using collections like ArrayList.

Advanced Example
#

Consider a scenario where an attempt to manage a buffer directly leads to an out-of-bounds write:

public class BufferOverflowExample {
    public static void main(String[] args) {
        int[] buffer = new int[10];
        try {
            writeToBuffer(buffer, 10, 42); // Trying to write outside the buffer
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Caught exception: " + e);
        }
    }
    public static void writeToBuffer(int[] buffer, int index, int value) {
        buffer[index] = value; // Potential out-of-bounds write
    }
}

In this example, writeToBuffer is called with an index that is out of bounds, leading to an ArrayIndexOutOfBoundsException.

Mitigated Version
#

public class BufferOverflowExample {
    public static void main(String[] args) {
        int[] buffer = new int[10];
        try {
            writeToBuffer(buffer, 10, 42); // Trying to write outside the buffer
        } catch (IllegalArgumentException e) {
            System.out.println("Caught exception: " + e);
        }
    }
    public static void writeToBuffer(int[] buffer, int index, int value) {
        if (index < 0 || index >= buffer.length) {
            throw new IllegalArgumentException("Index out of bounds: " + index);
        }
        buffer[index] = value;
    }
}

Here, writeToBuffer checks the index before writing to the buffer, preventing an out-of-bounds write.

While Java provides many safeguards against out-of-bounds writes, developers must still practice diligent bounds checking and utilise safe coding practices to prevent these errors. Handling array indices and leveraging Java’s robust standard libraries can help maintain secure and stable code.

CWE 787 - Based on OffHeap techniques in Java 8
#

“off-heap” in Java refers to memory allocated outside the Java heap, typically managed by native code. Working with off-heap memory can provide performance benefits, especially for large data sets or when interacting with native libraries. Still, it also introduces risks such as out-of-bounds writes.

Off-heap Memory Management in Java
#

Java provides several ways to work with off-heap memory, including the sun.misc.Unsafe class and java.nio.ByteBuffer. The sun.misc.Unsafe class allows low-level, unsafe operations, and should be used with caution. The java.nio.ByteBuffer class is safer but requires careful bounds checking.

Example of Out-of-bounds Write with Off-heap Memory
#

Using sun.misc.Unsafe
#

import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class OffHeapExample {
    private static final Unsafe unsafe;
    private static final long BYTE_ARRAY_BASE_OFFSET;
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            BYTE_ARRAY_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        long size = 10;
        long memoryAddress = unsafe.allocateMemory(size);
        try {
            for (int i = 0; i <= size; i++) { // Intentional off-by-one error
                unsafe.putByte(memoryAddress + i, (byte) i);
            }
        } finally {
            unsafe.freeMemory(memoryAddress);
        }
    }
}

In this example, the loop iterates one time too many, causing an out-of-bounds write when i equals size.

Mitigating Out-of-bounds Write with Off-heap Memory
#

Bounds Checking : Always ensure that memory accesses are within the allocated bounds.

Abstraction : Use higher-level abstractions that handle bounds checking automatically, such as java.nio.ByteBuffer.

Corrected Example with sun.misc.Unsafe
#

import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class OffHeapExample {
    private static final Unsafe unsafe;
    private static final long BYTE_ARRAY_BASE_OFFSET;
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            BYTE_ARRAY_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        long size = 10;
        long memoryAddress = unsafe.allocateMemory(size);
        try {
            for (int i = 0; i < size; i++) { // Correct bounds
                unsafe.putByte(memoryAddress + i, (byte) i);
            }
        } finally {
            unsafe.freeMemory(memoryAddress);
        }
    }
}

Here, the loop correctly iterates from 0 to size - 1, preventing the out-of-bounds write.

Using java.nio.ByteBuffer
#

A safer alternative involves using java.nio.ByteBuffer for off-heap memory management. ByteBuffer provides bounds checking, reducing the risk of out-of-bounds writes.

import java.nio.ByteBuffer;
public class OffHeapByteBufferExample {
    public static void main(String[] args) {
        int size = 10;
        ByteBuffer buffer = ByteBuffer.allocateDirect(size);
        for (int i = 0; i < size; i++) { // Correct bounds
            buffer.put((byte) i);
        }
        buffer.flip(); // Prepare the buffer for reading
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());
        }
    }
}

In this example, ByteBuffer manages off-heap memory safely with built-in bounds checking.

Managing off-heap memory in Java can offer performance benefits but requires careful handling to avoid out-of-bounds writes. Using sun.misc.Unsafe provides powerful capabilities but comes with significant risks. For safer memory management, java.nio.ByteBuffer is recommended due to its automatic bounds checking. Always perform thorough bounds checking and prefer higher-level abstractions to ensure memory safety. But there are changes in newer Java releases.

Handling Larger Data Structures
#

For more complex data structures, you can use methods like asIntBuffer, asLongBuffer, etc., which provide views of the buffer with different data types.

Example with IntBuffer
#

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
public class OffHeapIntBufferExample {
    public static void main(String[] args) {
        int size = 10;
        // Allocate off-heap memory
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size * Integer.BYTES);
        IntBuffer intBuffer = byteBuffer.asIntBuffer();
        // Writing data to the buffer
        for (int i = 0; i < size; i++) {
            intBuffer.put(i); // Proper bounds checking is inherent
        }
        // Preparing the buffer to read the data
        intBuffer.flip();
        // Reading data from the buffer
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

Explanation
#

Allocation : ByteBuffer.allocateDirect(size * Integer.BYTES) allocates a direct buffer with enough space for size integers.

IntBuffer View : byteBuffer.asIntBuffer() creates an IntBuffer view of the ByteBuffer, directly allowing you to work with integers.

Writing and Reading : The put and get methods of IntBuffer are used for writing and reading integers, with bounds checking.

Using java.nio.ByteBuffer for off-heap memory management in Java provides a safer alternative to sun.misc.Unsafe, ensuring automatic bounds checking and reducing the risk of out-of-bounds writes. Following the outlined examples and strategies, you can effectively manage off-heap memory while maintaining code safety and integrity.

OffHeap usage in Java 21
#

Java 21 introduces several enhancements and features for off-heap memory management, focusing on improved performance, safety, and ease of use. While the core approach using java.nio.ByteBuffer remains prevalent, new features and improvements in the Java ecosystem help with off-heap usage.

Critical Features for Off-Heap Usage in Java 21
#

Foreign Function & Memory API (Preview): Java 21 includes a preview of the Foreign Function & Memory API, designed to facilitate interactions with native code and memory. This API allows for safe and efficient off-heap memory access and manipulation.

Enhanced ByteBuffer Operations : The ByteBuffer class has been optimised and extended with more operations, making it more efficient for handling off-heap memory.

Memory Segments and Access VarHandles : Java 21 introduces memory segments and access varhandles for structured memory access. This provides a safer and more structured way to handle off-heap memory.

Foreign Function & Memory API
#

The Foreign Function & Memory API allows Java programs to interoperate with native libraries and manage off-heap memory more safely and efficiently. This API includes classes such as MemorySegment, MemoryAddress, and MemoryLayout to represent and manipulate memory.

Example Using Foreign Function & Memory API
#

Here is an example of how you might use the Foreign Function & Memory API to allocate and manipulate off-heap memory:

import jdk.incubator.foreign.*;
public class OffHeapExample {
    public static void main(String[] args) {
        // Allocate 100 bytes of off-heap memory
        try (MemorySegment segment = MemorySegment.allocateNative(100)) { 
            MemoryAddress baseAddress = segment.baseAddress();
            // Write data to off-heap memory
            for (int i = 0; i < 100; i++) {
                segment.set(ValueLayout.JAVA_BYTE, i, (byte) i);
            }
            // Read data from off-heap memory
            for (int i = 0; i < 100; i++) {
                byte value = segment.get(ValueLayout.JAVA_BYTE, i);
                System.out.println("Value at index " + i + ": " + value);
            }
        }
    }
}

Enhanced ByteBuffer Operations
#

While ByteBuffer remains a crucial class for off-heap memory management, Java 21’s performance improvements make it even more suitable for high-performance applications.

Memory Segments and Access VarHandles
#

The new memory segment API provides a safer and more structured way to handle off-heap memory, replacing the need for sun.misc.Unsafe.

Example Using Memory Segments
#

import jdk.incubator.foreign.*;
public class MemorySegmentExample {
    public static void main(String[] args) {
        // Allocate 10 integers worth of off-heap memory
        try (MemorySegment segment = MemorySegment.allocateNative(10 * 4)) { 
            VarHandle intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
            // Writing data to the memory segment
            for (int i = 0; i < 10; i++) {
                intHandle.set(segment.baseAddress().addOffset(i * 4), i);
            }
            // Reading data from the memory segment
            for (int i = 0; i < 10; i++) {
                int value = (int) intHandle.get(segment.baseAddress().addOffset(i * 4));
                System.out.println("Value at index " + i + ": " + value);
            }
        }
    }
}

Java 21 enhances off-heap memory management with new and improved features such as the Foreign Function & Memory API and enhanced ByteBuffer operations. These features provide safer, more efficient, and more flexible ways to interact with off-heap memory. By leveraging these new capabilities, developers can write high-performance, memory-safe applications that interact seamlessly with native libraries and off-heap memory.

Differences in OffHeap usage between Java 17 and Java 21
#

Java 21 introduces several enhancements and new features for off-heap memory management compared to Java 17. Here’s a detailed comparison highlighting the key differences:

Foreign Function & Memory API
#

Java 17 :

Incubator Stage : The Foreign Function & Memory API was in an incubator stage, which was experimental and subject to change. The API provided the foundations for off-heap memory access and foreign function calls but lacked maturity and stability.

Java 21 :

Preview Stage : The Foreign Function & Memory API is now in a preview stage, indicating it has become more stable and mature. This API allows for safer, more efficient off-heap memory management and interoperability with native libraries.

MemorySegment and MemoryAddress : These abstractions provide structured and safe access to off-heap memory, reducing the risks associated with manual memory management.

MemoryLayout and VarHandles : These additions enable developers to describe the layout of memory segments and access them in a type-safe manner using varhandles.

Enhanced ByteBuffer Operations
#

Java 17 :

Basic Operations : The ByteBuffer class in Java 17 supports basic operations for off-heap memory allocation and access through allocateDirect.

Performance : While effective, the performance optimisations were less advanced than in Java 21.

Java 21 :

Performance Improvements : Java 21 includes performance optimisations for ByteBuffer, making it more efficient for high-performance applications.

Extended Operations : Additional operations and methods may have been introduced or optimised to enhance functionality and ease of use.

Memory Segments and VarHandles
#

Java 17 :

Limited Usage : Memory segments and varhandles were part of the incubator Foreign Memory Access API, not widely adopted or stable.

Java 21 :

Stabilised and Enhanced : These concepts have been further developed and stabilised, providing a more robust way to handle off-heap memory.

Structured Access : Memory segments offer a structured way to allocate, access, and manage off-heap memory, reducing risks and improving safety.

VarHandles : Provide a type-safe mechanism to manipulate memory, akin to low-level pointers but with safety checks.

Summary of Differences
#

API Maturity : The Foreign Function & Memory API has evolved from an incubator stage in Java 17 to a preview stage in Java 21, providing a more stable and feature-rich environment.

Performance : Java 21 offers performance improvements and more optimised operations for ByteBuffer and other memory-related classes.

Memory Management : Java 21 introduces more structured and safer ways to handle off-heap memory with memory segments and varhandles.

Safety and Efficiency : Java 21’s improvements emphasise safety and efficiency, reducing the risks associated with manual off-heap memory management.

These advancements in Java 21 enhance the capabilities and safety of off-heap memory management, making it a more robust choice for developers needing high-performance memory operations.

Related

Secure Coding Practices - Input Validation

What is - Input Validation? # Input validation is a process used to ensure that the data provided to a system or application meets specific criteria or constraints before it is accepted and processed. The primary goal of input validation is to improve the reliability and security of a system by preventing invalid or malicious data from causing errors or compromising the system’s integrity.

Mastering Secure Error Handling in Java: Best Practices and Strategies

What is ErrorHandling? # Error handling refers to the programming practice of anticipating, detecting, and responding to exceptions or errors in software during its execution. Errors may occur for various reasons, such as invalid user inputs, hardware failures, or bugs in the code. Proper error handling helps ensure that the program can handle such situations gracefully by resolving the Error, compensating for it, or failing safely.

What is a Common Weakness Enumeration - CWE

CWE stands for Common Weakness Enumeration. It is a community-developed list of software and hardware weakness types that can serve as a common language for describing, sharing, and identifying security vulnerabilities in software systems. CWE aims to provide a standardized way of identifying and categorizing vulnerabilities, making it easier for software developers, testers, and security professionals to discuss and address security issues.