C Language Experience Discussion (Part 7): Array Out-of-Bounds, the Number One Killer of Memory Safety

C Language Experience Discussion (Part 7): Array Out-of-Bounds, the Number One Killer of Memory Safety

πŸ“Œ Application Scenarios & Problem Description

Array out-of-bounds is the most common and dangerous memory safety issue in C language. In scenarios such as “string processing”, “buffer operations”, and “dynamic array management”, a slight oversight can lead to accessing memory beyond the array boundaries. This is as dangerous as crossing lane lines while drivingβ€”there may be no immediate consequences, but it can lead to serious accidents at any moment.

Common Scenarios:

  • Forgetting to consider the target buffer size when copying strings
  • Incorrect boundary conditions when iterating through arrays
  • Using incorrect indices after dynamically allocating memory
  • Loss of array size information when passing function parameters

Hazards: Array out-of-bounds can lead to program crashes, data corruption, security vulnerabilities, and even exploitation by malicious actors.

⚠️ Error Code & Output Results

Code Function Description: The following code demonstrates three common scenarios of array out-of-bounds:

  • Scenario 1: Target buffer too small during string copy
  • Scenario 2: Incorrect boundary conditions during iteration
  • Scenario 3: Loss of array size information in functions
#include <stdio.h>
#include <string.h>

// Scenario 1: String copy out-of-bounds
void string_copy_demo() {
    char small_buffer[5];  // Can only hold 4 characters + null terminator
    char source[] = "Hello World";  // 11 characters

    printf("=== String Copy Out-of-Bounds Demonstration ===\n");
    printf("Target buffer size: %zu\n", sizeof(small_buffer));
    printf("Source string length: %zu\n", strlen(source));

    strcpy(small_buffer, source);  // Dangerous: out-of-bounds write!
    printf("Buffer content after copy: %s\n", small_buffer);
}

// Scenario 2: Loop boundary error
void loop_boundary_demo() {
    int arr[5] = {1, 2, 3, 4, 5};
    int sum = 0;

    printf("\n=== Loop Boundary Error Demonstration ===\n");
    printf("Array size: 5\n");

    // Error: i <= 5 leads to out-of-bounds access arr[5]
    for (int i = 0; i <= 5; i++) {
        printf("Accessing arr[%d] = %d\n", i, arr[i]);
        sum += arr[i];
    }
    printf("Incorrect total sum: %d\n", sum);
}

// Scenario 3: Array decay in function
void process_array(int arr[]) {  // Array decays to pointer
    printf("\n=== Array Decay in Function Demonstration ===\n");
    printf("Size of arr in function: %zu (actually pointer size)\n", sizeof(arr));

    // Dangerous: unknown real size of array, may go out-of-bounds
    for (int i = 0; i < 10; i++) {  // Assuming there are 10 elements
        printf("Accessing arr[%d] = %d\n", i, arr[i]);
    }
}

int main() {
    string_copy_demo();
    loop_boundary_demo();

    int test_arr[3] = {10, 20, 30};
    process_array(test_arr);

    return 0;
}

Output Results:

=== String Copy Out-of-Bounds Demonstration ===
Target buffer size: 5
Source string length: 11
Buffer content after copy: Hello World
=== Loop Boundary Error Demonstration ===
Array size: 5
Accessing arr[0] = 1
Accessing arr[1] = 2
Accessing arr[2] = 3
Accessing arr[3] = 4
Accessing arr[4] = 5
Accessing arr[5] = 32767
Incorrect total sum: 32782
=== Array Decay in Function Demonstration ===
Size of arr in function: 8 (actually pointer size)
Accessing arr[0] = 10
Accessing arr[1] = 20
Accessing arr[2] = 30
Accessing arr[3] = 32767
Accessing arr[4] = 0
Accessing arr[5] = 0
Accessing arr[6] = 0
Accessing arr[7] = 0
Accessing arr[8] = 0
Accessing arr[9] = 0

πŸ“ Explanation of Incorrect Output:

  • Lines 1-4: String copy out-of-bounds, although the complete string was output, memory has already been corrupted.
  • Lines 5-12: Loop out-of-bounds access arr[5], reading uninitialized memory value 32767.
  • Lines 13-24: Array decayed to pointer in function, sizeof returns pointer size 8, loop accessed memory beyond array bounds.

πŸ‘‰ Error Analysis:

  1. Memory Corruption: Out-of-bounds writes can overwrite memory of other variables.
  2. Data Pollution: Out-of-bounds reads can retrieve meaningless data.
  3. Program Instability: May lead to program crashes or abnormal behavior.
  4. Security Vulnerabilities: May be exploited for attacks.

βœ… Correct Code & Output Results

Code Function Description: The following code demonstrates how to safely handle array operations:

  • Scenario 1: Use strncpy to limit copy length and ensure string termination
  • Scenario 2: Correct loop boundary conditions
  • Scenario 3: Pass array size parameters to avoid array decay issues
#include <stdio.h>
#include <string.h>

// Scenario 1: Safe string copy
void safe_string_copy_demo() {
    char small_buffer[5];
    char source[] = "Hello World";

    printf("=== Safe String Copy ===\n");
    printf("Target buffer size: %zu\n", sizeof(small_buffer));
    printf("Source string length: %zu\n", strlen(source));

    // Safe: limit copy length, ensure string termination
    strncpy(small_buffer, source, sizeof(small_buffer) - 1);
    small_buffer[sizeof(small_buffer) - 1] = '\0';  // Ensure string termination

    printf("After safe copy: %s\n", small_buffer);
    printf("Actual length: %zu\n", strlen(small_buffer));
}

// Scenario 2: Correct loop boundary
void correct_loop_demo() {
    int arr[5] = {1, 2, 3, 4, 5};
    int sum = 0;

    printf("\n=== Correct Loop Boundary ===\n");
    printf("Array size: 5\n");

    // Correct: i < 5 ensures no out-of-bounds
    for (int i = 0; i < 5; i++) {
        printf("Accessing arr[%d] = %d\n", i, arr[i]);
        sum += arr[i];
    }
    printf("Correct total sum: %d\n", sum);
}

// Scenario 3: Pass array size
void safe_process_array(int arr[], size_t size) {
    printf("\n=== Safe Array Processing ===\n");
    printf("Array size: %zu\n", size);

    // Safe: use passed size parameter
    for (size_t i = 0; i < size; i++) {
        printf("Accessing arr[%zu] = %d\n", i, arr[i]);
    }
}

int main() {
    safe_string_copy_demo();
    correct_loop_demo();

    int test_arr[3] = {10, 20, 30};
    safe_process_array(test_arr, 3);

    return 0;
}

Output Results:

=== Safe String Copy ===
Target buffer size: 5
Source string length: 11
After safe copy: Hell
Actual length: 4
=== Correct Loop Boundary ===
Array size: 5
Accessing arr[0] = 1
Accessing arr[1] = 2
Accessing arr[2] = 3
Accessing arr[3] = 4
Accessing arr[4] = 5
Correct total sum: 15
=== Safe Array Processing ===
Array size: 3
Accessing arr[0] = 10
Accessing arr[1] = 20
Accessing arr[2] = 30

πŸ“ Explanation of Correct Output:

  • Lines 1-5: Safe copy only copied 4 characters “Hell”, no out-of-bounds.
  • Lines 6-12: Loop correctly accessed arr[0] to arr[4], total sum is 15.
  • Lines 13-17: Function correctly used passed size parameter, only accessed 3 elements.

✨ Comparison Points:

  • Boundary Control: Always check array boundaries, do not access out-of-bounds.
  • Size Passing: Use size_t type when passing array size in functions.
  • String Safety: Use strncpy and ensure string termination.
  • Loop Conditions: Use <span><</span> instead of <span><=</span> as boundary condition.

🌟 Best Practices

1. Boundary Checking (Highly Recommended)

What is Boundary Checking? Boundary checking refers to verifying that the index is within a valid range before accessing array elements. This is a fundamental method to prevent array out-of-bounds.

// Recommended: Always check boundaries
if (index >= 0 && index < array_size) {
    value = array[index];
} else {
    // Handle out-of-bounds situation
    printf("Error: Index %d out of range [0, %zu)\n", index, array_size);
}

Why is it Important? Boundary checking can detect out-of-bounds access early, preventing program crashes and data corruption.

2. Safe String Functions

// Recommended: Use safe string functions
char buffer[10];
strncpy(buffer, source, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';  // Ensure string termination

// Or use snprintf
snprintf(buffer, sizeof(buffer), "%s", source);

3. Compiler Configuration

# Enable warnings when compiling with GCC
gcc -Wall -Wextra -Werror -fsanitize=address -o program program.c

# Enable warnings when compiling with Clang
clang -Wall -Wextra -Werror -fsanitize=address -o program program.c

4. Team Code Standards

  • Mandatory Boundary Checking: All array accesses must validate indices.
  • Function Design: Use size_t type when passing array sizes.
  • Code Review: Focus on loop boundary conditions and string operations.
  • Test Coverage: Include boundary condition test cases.

5. Recommended Tools

  • AddressSanitizer: Runtime detection of memory errors.
  • Valgrind: Memory error detection tool.
  • Static Analysis Tools: Clang Static Analyzer, Cppcheck.
  • Code Checking: Use lint tools to check for potential issues.

πŸ—οΈ Expanding Ideas

Defensive Programming: Smart Array Wrapper

// Safe array wrapper
typedef struct {
    int* data;
    size_t size;
} SafeArray;

SafeArray* create_safe_array(size_t size) {
    SafeArray* arr = malloc(sizeof(SafeArray));
    arr->data = calloc(size, sizeof(int));
    arr->size = size;
    return arr;
}

int safe_array_get(SafeArray* arr, size_t index) {
    if (arr == NULL || index >= arr->size) {
        fprintf(stderr, "Error: Array access out of bounds index=%zu, size=%zu\n", 
                index, arr->size);
        return 0;  // Return default value
    }
    return arr->data[index];
}

void safe_array_set(SafeArray* arr, size_t index, int value) {
    if (arr == NULL || index >= arr->size) {
        fprintf(stderr, "Error: Array set out of bounds index=%zu, size=%zu\n", 
                index, arr->size);
        return;
    }
    arr->data[index] = value;
}

Design Pattern: RAII Concept

// Macro for automatic management of array lifecycle
#define AUTO_ARRAY(type, name, size) \
    type name[size]; \
    size_t name##_size = size

#define SAFE_ACCESS(array, index) \
    ((index) < array##_size ? array[index] : (fprintf(stderr, "Out-of-bounds access\n"), 0))

πŸ“‹ Summary Checklist

  • βœ… Always Check Array Boundaries: Validate index range before access.
  • βœ… Use Safe String Functions: strncpy + manually add terminator.
  • βœ… Pass Array Size Parameters: Avoid array decay issues.
  • βœ… Enable Compiler Warnings: -Wall -Wextra -fsanitize=address.
  • βœ… Use Static Analysis Tools: Detect potential issues early.

One-Sentence Summary: Array out-of-bounds is the number one killer of memory safety; through boundary checking, safe functions, and tool assistance, these issues can be effectively avoided.

Next Issue Preview: We will explore the dangling pointer issue and learn how to avoid accessing freed memory.

Leave a Comment