sizeof() is likely to be misused
| Vulnerability potential | High |
| DDoS potential | Medium |
sizeof() is used on pointer rather than array
Impact
Applying sizeof to a pointer when the programmer meant the size of the
pointed-to array or object yields the size of the pointer itself — 8 bytes on a
typical 64-bit platform, 4 on 32-bit — not the size of the data. Code that uses
this value as a length is then wrong by a large factor. The two failure
directions are both dangerous: if the real object is larger than a pointer, a
memcpy/memset/snprintf bounded by sizeof(ptr) copies or clears too
little, silently truncating data or leaving a buffer un-initialized; if the
buffer is smaller, or the count is multiplied, the operation runs past the
object and corrupts adjacent memory. A particularly common form is computing an
element count as sizeof(arr)/sizeof(arr[0]) on a parameter that has decayed to
a pointer, which produces 8/4 = 2 (or 8/8 = 1) regardless of the true
length.
Vulnerability potential
This is a classic source of memory-corruption vulnerabilities (CWE-467, “Use of sizeof() on a Pointer Type”).
- Buffer overflow. When the wrong size is too large for the destination —
e.g.
memcpy(dst, src, sizeof(src))wheresrcis a pointer butdstis smaller, or a count derived fromsizeof(ptr)that exceeds the real allocation — the write goes out of bounds, enabling stack/heap corruption and potentially remote code execution. - Information disclosure / truncation. When the size is too small, a
memsetmeant to scrub a secret clears only the first few bytes, leaving key material in memory, or a length-prefixed copy truncates data in a way that confuses later parsing. - Denial of service. A grossly wrong length passed to a bulk memory operation reads or writes far outside the object and crashes the process; if reachable from input, it is a reliable DoS.
- Iteration bounds. A loop bounded by a wrong element count under-processes or over-runs the array, corrupting state.
Technical details
sizeof is evaluated at compile time from the static type of its operand. For
a pointer T *p, sizeof(p) is the pointer width; only sizeof(*p) or
sizeof(T) gives the pointee size, and that is still just one element, not the
whole buffer a pointer refers to. The amount of memory behind a pointer is simply
not recoverable from the pointer’s type — it must be tracked separately.
Array-to-pointer decay
The trap is sharpened by decay: in most expression contexts, and crucially when
an array is passed as a function argument, an array T a[N] is converted to a
T *. Inside
void f(int a[10]) { /* sizeof(a) == sizeof(int*), NOT 10*sizeof(int) */ }
the parameter a has type int * despite the [10] syntax, so the
sizeof(arr)/sizeof(arr[0]) count idiom — which is correct for a true array in
the scope where it was declared — silently breaks. The idiom only works where the
real array type is in view.
Platform dependence
Because the bug substitutes the pointer width, its magnitude and even its sign of
error change between ILP32 and LP64 targets. Code that appears to “work” on one
ABI (where sizeof(ptr) happens to be close to the intended size) can corrupt
memory on another, so the defect is also a portability landmine.
Catching the issue
Compile with warnings on: GCC and Clang emit -Wsizeof-pointer-div for the
sizeof(ptr)/sizeof(ptr[0]) count mistake and -Wsizeof-array-argument /
-Wsizeof-pointer-memaccess when a sizeof on a pointer (or array parameter)
is fed to memcpy/memset/strncpy and friends. These are part of
-Wall and catch most real cases at zero cost. clang-tidy
(bugprone-sizeof-expression), cppcheck, Coverity, and PVS-Studio (V511/V512,
V579) detect the pattern in static analysis. AddressSanitizer catches the
resulting out-of-bounds access at runtime, and fuzzing surfaces the input-driven
ones. As a defensive practice, prefer a macro such as
#define COUNTOF(a) (sizeof(a)/sizeof((a)[0])) that uses C++ templates or a
__builtin to refuse to compile on pointers, never take a length from a
pointer’s sizeof, and pass array lengths explicitly across function
boundaries.
How to reproduce
Observe that inside clear the parameter has decayed to a pointer, so
sizeof(buf) is 8 (pointer width), and only the first 8 bytes are zeroed
instead of all 64. Compile with -Wall to see -Wsizeof-pointer-memaccess.
#include <string.h>
#include <stdio.h>
void clear(char buf[64]) {
/* buf is really char*, so sizeof(buf) == sizeof(char*) == 8, not 64 */
memset(buf, 0, sizeof(buf)); /* zeroes only 8 of the 64 bytes */
}
int main(void) {
char secret[64];
memset(secret, 'A', sizeof secret); /* true array: 64 bytes */
clear(secret);
printf("byte 40 = %d\n", secret[40]); /* still 'A' (65), not 0 */
return 0;
}