Incorrect Pointer Scaling

Description

Incorrect Pointer Scaling occurs when pointer arithmetic is performed without proper consideration of the element size. In C and C++, pointer arithmetic automatically scales by the size of the pointed-to type—incrementing an int* advances by sizeof(int) bytes, not 1 byte. Errors occur when programmers manually multiply by element size (double-scaling), cast pointers to different types without adjusting arithmetic, or use byte offsets with typed pointers. This leads to accessing wrong memory locations.

Risk

Incorrect pointer scaling causes out-of-bounds memory access, buffer overflows, and data corruption. Double-scaling (multiplying by size when the compiler already does) accesses memory far beyond intended locations. Under-scaling (forgetting type size when working with byte offsets) accesses wrong array elements. These errors can lead to information disclosure by reading adjacent memory, denial of service through crashes, and potentially code execution if attacker-controlled data is at the incorrectly calculated address.

Solution

Understand that C/C++ pointer arithmetic automatically scales by element size. Never manually multiply offsets when using typed pointers—let the compiler handle scaling. When working with raw byte offsets, cast to char* or unsigned char* first, then cast back to the target type. Use array indexing (arr[i]) instead of pointer arithmetic when possible. Employ static analysis tools that detect pointer scaling issues. Prefer modern C++ containers that abstract memory access.

Common Consequences

ImpactDetails
IntegrityScope: Memory Corruption

Writing to incorrectly calculated addresses corrupts memory.
ConfidentialityScope: Information Disclosure

Reading wrong memory locations exposes adjacent data.
AvailabilityScope: Crash

Accessing invalid memory causes segmentation faults.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: Double-scaling - compiler already multiplies by sizeof(int)
void process_array_vulnerable(int* arr, int count) {
    for (int i = 0; i < count; i++) {
        // Wrong! Pointer arithmetic already scales by sizeof(int)
        // This accesses arr[i * sizeof(int)] instead of arr[i]!
        int* ptr = arr + (i * sizeof(int));  // Double-scaled!
        *ptr = i;
    }
}

// VULNERABLE: Manual offset calculation error
int* get_element_vulnerable(int* base, int index) {
    // Wrong! Adding byte offset to int pointer
    // Compiler multiplies index by sizeof(int), so this is 4x too far
    return base + (index * sizeof(int));
}

// VULNERABLE: Type confusion in pointer arithmetic
void copy_data_vulnerable(void* dest, void* src, size_t count) {
    // Wrong! void* arithmetic is undefined in standard C
    // GCC extension treats it as char*, but intention is unclear
    for (size_t i = 0; i < count; i++) {
        *((int*)dest + i * sizeof(int)) = *((int*)src + i * sizeof(int));
    }
}

// VULNERABLE: Struct array access
struct Record {
    int id;
    char name[32];
    double value;
};

struct Record* get_record_vulnerable(struct Record* base, int index) {
    // Wrong! Double-scaling by struct size
    return (struct Record*)((char*)base + index * sizeof(struct Record) * sizeof(struct Record));
}

// VULNERABLE: Buffer boundary calculation
void check_bounds_vulnerable(int* arr, size_t size, int* ptr) {
    // Wrong! Comparing byte offset with pointer arithmetic result
    int* end = arr + size * sizeof(int);  // Double-scaled!

    if (ptr < end) {
        *ptr = 0;  // May write way beyond actual buffer!
    }
}

// VULNERABLE: Mixing pointer types
void convert_data_vulnerable(short* shorts, int* ints, int count) {
    for (int i = 0; i < count; i++) {
        // Wrong! Using int index with short pointer while thinking in bytes
        shorts[i * sizeof(int)] = (short)ints[i];
        // Skips over 3 short elements each iteration!
    }
}

// VULNERABLE: Pointer subtraction error
size_t calculate_offset_vulnerable(int* base, int* current) {
    // Returns element difference, but then misused as byte offset
    size_t offset = current - base;

    // Later: wrong byte calculation
    char* byte_ptr = (char*)base + offset;  // Should be offset * sizeof(int)
    return offset;
}

// VULNERABLE: memset with wrong size calculation
void clear_array_vulnerable(int* arr, int count) {
    // Wrong! memset takes byte count, but pointer arithmetic adds scaling
    memset(arr, 0, (arr + count) - arr);  // Just clears 'count' bytes
    // Should clear count * sizeof(int) bytes!
}
// VULNERABLE: C++ with same pointer scaling issues
template<typename T>
T* offset_pointer_vulnerable(T* base, size_t byte_offset) {
    // Wrong! This scales byte_offset by sizeof(T)
    return base + byte_offset;
}

// VULNERABLE: Iterator arithmetic
class VulnerableArray {
    int* data;
    size_t size;

public:
    int* at_byte_offset_vulnerable(size_t offset) {
        // Wrong! Treated as byte offset but scaled by sizeof(int)
        return data + offset;  // Returns element at index 'offset', not byte offset
    }
};

// VULNERABLE: Reinterpret cast with arithmetic
void process_bytes_vulnerable(uint32_t* values, size_t count) {
    uint8_t* bytes = reinterpret_cast<uint8_t*>(values);

    for (size_t i = 0; i < count; i++) {
        // Wrong! Accessing bytes at i*4 when we want every byte
        uint8_t byte = *(bytes + i * sizeof(uint32_t));
    }
}

// VULNERABLE: Placement new with wrong offset
class VulnerablePool {
    char* buffer;
    size_t objectSize;

public:
    template<typename T>
    T* allocateAt_vulnerable(size_t index) {
        // Wrong if objectSize != sizeof(T*)
        T* ptr = reinterpret_cast<T*>(buffer) + index * objectSize;
        return new (ptr) T();  // Wrong address!
    }
};

Fixed Code

// SAFE: Let compiler handle scaling
void process_array_safe(int* arr, int count) {
    for (int i = 0; i < count; i++) {
        // Correct! Pointer arithmetic automatically scales
        int* ptr = arr + i;  // Equivalent to &arr[i]
        *ptr = i;
    }
}

// SAFE: Array indexing (clearer, no manual arithmetic)
void process_array_safe_v2(int* arr, int count) {
    for (int i = 0; i < count; i++) {
        arr[i] = i;  // Most readable form
    }
}

// SAFE: Simple pointer offset
int* get_element_safe(int* base, int index) {
    // Correct! No manual size multiplication needed
    return base + index;
}

// SAFE: Working with byte offsets - cast to char* first
void* byte_offset_safe(void* base, size_t byte_offset) {
    // Cast to char* for byte arithmetic, then back
    return (char*)base + byte_offset;
}

int* element_from_byte_offset_safe(int* base, size_t byte_offset) {
    return (int*)((char*)base + byte_offset);
}

// SAFE: Struct array access
struct Record* get_record_safe(struct Record* base, int index) {
    // Correct! Pointer arithmetic handles struct size
    return base + index;
}

// Or with byte offset when needed
struct Record* get_record_byte_offset_safe(struct Record* base, size_t byte_offset) {
    return (struct Record*)((char*)base + byte_offset);
}

// SAFE: Buffer boundary calculation
void check_bounds_safe(int* arr, size_t size, int* ptr) {
    // Correct! Just add element count
    int* end = arr + size;

    if (ptr >= arr && ptr < end) {
        *ptr = 0;
    }
}

// SAFE: Converting between pointer types
void convert_data_safe(short* shorts, int* ints, int count) {
    for (int i = 0; i < count; i++) {
        shorts[i] = (short)ints[i];  // Simple array indexing
    }
}

// SAFE: Pointer difference and byte calculation
size_t calculate_byte_offset_safe(int* base, int* current) {
    // Pointer difference gives element count
    ptrdiff_t element_diff = current - base;

    // Convert to bytes explicitly
    size_t byte_offset = element_diff * sizeof(int);

    return byte_offset;
}

// SAFE: memset with correct size
void clear_array_safe(int* arr, int count) {
    // Correct! Use count * sizeof(element) for byte count
    memset(arr, 0, count * sizeof(*arr));
}

// Or use explicit size
void clear_array_safe_v2(int* arr, int count) {
    memset(arr, 0, count * sizeof(int));
}

// SAFE: Generic byte-wise copy
void copy_bytes_safe(void* dest, const void* src, size_t byte_count) {
    char* d = (char*)dest;
    const char* s = (const char*)src;

    for (size_t i = 0; i < byte_count; i++) {
        d[i] = s[i];
    }
}

// SAFE: Element-wise copy with proper types
void copy_ints_safe(int* dest, const int* src, size_t count) {
    for (size_t i = 0; i < count; i++) {
        dest[i] = src[i];  // Array indexing, no pointer math needed
    }
}

// Or with memcpy
void copy_ints_memcpy_safe(int* dest, const int* src, size_t count) {
    memcpy(dest, src, count * sizeof(int));
}
// SAFE: C++ with proper pointer handling

// SAFE: Template with explicit byte offset handling
template<typename T>
T* byte_offset_pointer_safe(T* base, size_t byte_offset) {
    // Explicitly handle byte offset
    return reinterpret_cast<T*>(
        reinterpret_cast<char*>(base) + byte_offset
    );
}

// SAFE: Element offset (standard pointer arithmetic)
template<typename T>
T* element_offset_safe(T* base, size_t element_index) {
    return base + element_index;  // Compiler handles scaling
}

// SAFE: Array class with proper access
class SafeArray {
    int* data;
    size_t size;

public:
    // Element access
    int& at(size_t index) {
        if (index >= size) throw std::out_of_range("Index out of bounds");
        return data[index];  // Simple array access
    }

    // If byte offset is truly needed
    int* at_byte_offset(size_t byte_offset) {
        if (byte_offset % sizeof(int) != 0) {
            throw std::invalid_argument("Byte offset not aligned");
        }
        size_t index = byte_offset / sizeof(int);
        if (index >= size) throw std::out_of_range("Offset out of bounds");
        return reinterpret_cast<int*>(
            reinterpret_cast<char*>(data) + byte_offset
        );
    }
};

// SAFE: Process bytes of larger type
void process_bytes_safe(uint32_t* values, size_t count) {
    uint8_t* bytes = reinterpret_cast<uint8_t*>(values);
    size_t total_bytes = count * sizeof(uint32_t);

    for (size_t i = 0; i < total_bytes; i++) {
        uint8_t byte = bytes[i];  // Correct byte access
        // Process byte...
    }
}

// SAFE: Memory pool with proper alignment
class SafePool {
    alignas(std::max_align_t) char* buffer;
    size_t objectSize;
    size_t capacity;

public:
    template<typename T>
    T* allocateAt(size_t index) {
        static_assert(alignof(T) <= alignof(std::max_align_t),
                     "Type alignment too large for pool");

        size_t byte_offset = index * sizeof(T);
        if (byte_offset + sizeof(T) > capacity) {
            throw std::bad_alloc();
        }

        void* ptr = buffer + byte_offset;
        return new (ptr) T();
    }
};

// SAFE: Use std::span (C++20) or gsl::span
#include <span>

void process_span_safe(std::span<int> arr) {
    for (size_t i = 0; i < arr.size(); i++) {
        arr[i] = static_cast<int>(i);  // Bounds-checked with .at()
    }
}

// SAFE: Use iterators
void process_iterators_safe(std::vector<int>& vec) {
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        *it = 0;  // No manual pointer arithmetic
    }
}

Exploited in the Wild

Browser Memory Corruption

Browser engines have had pointer scaling bugs when processing complex data structures, leading to memory corruption and potential code execution.

Kernel Driver Vulnerabilities

Kernel drivers have been exploited through pointer scaling errors when copying data between user and kernel space.

Media Parser Exploits

Media file parsers have contained pointer arithmetic bugs exploited for code execution through malformed files.


Tools to test/exploit

  • Coverity — detects pointer scaling issues.

  • PVS-Studio — warns about suspicious pointer arithmetic.

  • AddressSanitizer — detects out-of-bounds access from scaling errors.

  • Polyspace — formal verification of pointer operations.


CVE Examples

  • CVE-2014-1266 — Apple SSL "goto fail" involving pointer issues.

  • CVE-2018-8781 — Linux kernel udl_fb pointer scaling.

  • Various buffer overflow CVEs rooted in pointer arithmetic errors.


References

  1. MITRE. "CWE-468: Incorrect Pointer Scaling." https://cwe.mitre.org/data/definitions/468.html

  2. CERT C. "ARR39-C: Do not add or subtract a scaled integer to a pointer." https://wiki.sei.cmu.edu/confluence/display/c/