Insufficient Encapsulation of Machine-Dependent Functionality
Description
Insufficient Encapsulation of Machine-Dependent Functionality occurs when a product or code uses machine-dependent functionality without sufficiently encapsulating or isolating it from the rest of the codebase. This includes direct use of hardware-specific features, memory layout assumptions, architecture-specific instructions, or platform-specific system calls without proper abstraction layers. When machine-dependent code is scattered throughout the application, it becomes difficult to port, maintain, and ensure consistent security behavior.
Risk
Insufficiently encapsulated machine-dependent functionality has security implications. Code that assumes specific memory layouts can have buffer overflows on different architectures. Pointer arithmetic based on memory layout assumptions can cause memory corruption. Architecture-specific optimizations may introduce vulnerabilities on other platforms. Security-sensitive operations may behave differently on different machines. Code review for security is complicated by scattered machine-dependent code. Testing cannot cover all architecture-specific variations.
Solution
Encapsulate all machine-dependent functionality in dedicated modules or abstraction layers. Use compiler-provided abstractions for architecture-specific features. Avoid assumptions about memory layout, data alignment, or structure padding. Use standard library functions instead of machine-specific implementations. Document any necessary machine dependencies clearly. Test on multiple architectures when possible. Use static analysis tools to detect machine-dependent code patterns. Apply the adapter pattern to isolate platform differences.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Maintainability - Machine-dependent code scattered throughout is hard to maintain. |
| Integrity | Scope: Integrity Memory Corruption - Memory layout assumptions may not hold on all architectures. |
| Other | Scope: Other Reduce Portability - Code cannot run correctly on different machines. |
Example Code
Vulnerable Code
// Vulnerable: Direct reliance on memory layout
#include <stdio.h>
void vulnerable_memory_assumption() {
char a;
char b;
// Vulnerable: Assumes b is adjacent to a in memory
// This is NOT guaranteed across different compilers/architectures
*(&a + 1) = 'X'; // May corrupt random memory!
// On some architectures:
// - Variables may be reordered
// - Different alignment padding may exist
// - Stack may grow in different directions
}
// Vulnerable: Structure with assumed layout
struct VulnerablePacket {
char type;
// Padding assumed to be here on some architectures, not others
int length;
char data[100];
};
void vulnerable_struct_access(char* buffer) {
// Vulnerable: Assumes specific memory layout of struct
struct VulnerablePacket* packet = (struct VulnerablePacket*)buffer;
// This assumes no padding between type and length
// May read wrong data on architectures with different alignment
printf("Length: %d\n", packet->length);
}
// Vulnerable: Endianness assumption mixed with business logic
uint32_t vulnerable_parse_network_int(unsigned char* buffer) {
// Vulnerable: Assumes little-endian everywhere
// Directly accessing buffer as int* without encapsulation
return *(uint32_t*)buffer;
}
// Vulnerable: Architecture-specific optimization without encapsulation
void vulnerable_fast_copy(void* dest, void* src, size_t size) {
// Vulnerable: x86-specific code scattered in application
#ifdef __x86_64__
// Use SSE instructions directly
__asm__ volatile (
"rep movsb"
: "+D"(dest), "+S"(src), "+c"(size)
:
: "memory"
);
#else
// Fallback is inconsistent with optimized path
memcpy(dest, src, size);
#endif
}
// Vulnerable: C++ with scattered machine-dependent code
class VulnerableSerializer {
public:
// Vulnerable: Pointer size assumption
void serialize_pointer(std::ostream& out, void* ptr) {
// Assumes pointers are 4 bytes
uint32_t addr = (uint32_t)(uintptr_t)ptr; // Truncates on 64-bit!
out.write(reinterpret_cast<char*>(&addr), sizeof(addr));
}
// Vulnerable: Float representation assumption
void serialize_float(std::ostream& out, float value) {
// Assumes IEEE 754 representation everywhere
// Assumes same endianness as reading system
out.write(reinterpret_cast<char*>(&value), sizeof(float));
}
// Vulnerable: Struct packing assumption
template<typename T>
void serialize_struct(std::ostream& out, const T& obj) {
// Assumes no padding in struct T
// Assumes same alignment as reading system
out.write(reinterpret_cast<const char*>(&obj), sizeof(T));
}
};
// Vulnerable: Platform-specific intrinsics without encapsulation
class VulnerableBitOperations {
public:
int count_leading_zeros(uint32_t value) {
// Vulnerable: Architecture-specific code in application layer
#if defined(__GNUC__)
return __builtin_clz(value);
#elif defined(_MSC_VER)
unsigned long index;
_BitScanReverse(&index, value);
return 31 - index;
#else
// Fallback may have different edge case behavior
int count = 0;
while (!(value & 0x80000000) && count < 32) {
value <<= 1;
count++;
}
return count;
#endif
}
};
Fixed Code
// Fixed: Properly encapsulated machine-dependent functionality
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// Encapsulated platform detection
typedef struct {
int is_little_endian;
int pointer_size;
int int_size;
} PlatformInfo;
static PlatformInfo get_platform_info() {
PlatformInfo info;
// Detect endianness
uint16_t test = 1;
info.is_little_endian = (*(uint8_t*)&test == 1);
// Record sizes
info.pointer_size = sizeof(void*);
info.int_size = sizeof(int);
return info;
}
// Fixed: Memory access through proper interfaces
typedef struct {
uint8_t type;
uint8_t padding[3]; // Explicit padding
uint32_t length;
uint8_t data[100];
} FixedPacket;
// Fixed: Use explicit byte access, not assumptions
FixedPacket fixed_parse_packet(const uint8_t* buffer) {
FixedPacket packet;
packet.type = buffer[0];
// Fixed: Explicit byte-order handling, not memory layout assumption
packet.length = ((uint32_t)buffer[4] << 24) |
((uint32_t)buffer[5] << 16) |
((uint32_t)buffer[6] << 8) |
((uint32_t)buffer[7]);
memcpy(packet.data, buffer + 8, sizeof(packet.data));
return packet;
}
// Fixed: Encapsulated endianness handling
// In a dedicated header: byte_order.h
uint32_t read_uint32_be(const uint8_t* buffer) {
return ((uint32_t)buffer[0] << 24) |
((uint32_t)buffer[1] << 16) |
((uint32_t)buffer[2] << 8) |
((uint32_t)buffer[3]);
}
uint32_t read_uint32_le(const uint8_t* buffer) {
return ((uint32_t)buffer[3] << 24) |
((uint32_t)buffer[2] << 16) |
((uint32_t)buffer[1] << 8) |
((uint32_t)buffer[0]);
}
void write_uint32_be(uint8_t* buffer, uint32_t value) {
buffer[0] = (value >> 24) & 0xFF;
buffer[1] = (value >> 16) & 0xFF;
buffer[2] = (value >> 8) & 0xFF;
buffer[3] = value & 0xFF;
}
// Fixed: Encapsulated memory copy - platform-specific in one place
// In a dedicated file: platform_memory.c
void platform_fast_memcpy(void* dest, const void* src, size_t size) {
#if defined(__x86_64__) && defined(USE_SIMD_OPTIMIZATIONS)
// Optimized x86-64 implementation
if (size >= 128) {
x86_simd_memcpy(dest, src, size);
return;
}
#elif defined(__aarch64__) && defined(USE_SIMD_OPTIMIZATIONS)
// Optimized ARM64 implementation
if (size >= 128) {
arm_neon_memcpy(dest, src, size);
return;
}
#endif
// Portable fallback
memcpy(dest, src, size);
}
// Fixed: C++ with properly encapsulated machine-dependent code
// Byte order utilities - in dedicated header
namespace ByteOrder {
inline uint32_t read_be32(const uint8_t* buffer) {
return (static_cast<uint32_t>(buffer[0]) << 24) |
(static_cast<uint32_t>(buffer[1]) << 16) |
(static_cast<uint32_t>(buffer[2]) << 8) |
static_cast<uint32_t>(buffer[3]);
}
inline void write_be32(uint8_t* buffer, uint32_t value) {
buffer[0] = static_cast<uint8_t>(value >> 24);
buffer[1] = static_cast<uint8_t>(value >> 16);
buffer[2] = static_cast<uint8_t>(value >> 8);
buffer[3] = static_cast<uint8_t>(value);
}
inline uint64_t read_be64(const uint8_t* buffer) {
return (static_cast<uint64_t>(read_be32(buffer)) << 32) |
read_be32(buffer + 4);
}
} // namespace ByteOrder
// Fixed: Portable serializer
class FixedSerializer {
public:
// Fixed: Use fixed-width integers with explicit byte order
void serialize_uint64(std::ostream& out, uint64_t value) {
uint8_t buffer[8];
ByteOrder::write_be64(buffer, value);
out.write(reinterpret_cast<char*>(buffer), 8);
}
// Fixed: Serialize pointer as address (with explicit size)
void serialize_address(std::ostream& out, uintptr_t addr) {
// Always serialize as 64-bit for compatibility
serialize_uint64(out, static_cast<uint64_t>(addr));
}
// Fixed: Float with explicit representation
void serialize_float(std::ostream& out, float value) {
static_assert(std::numeric_limits<float>::is_iec559,
"Requires IEEE 754 floats");
uint32_t bits;
std::memcpy(&bits, &value, sizeof(bits));
uint8_t buffer[4];
ByteOrder::write_be32(buffer, bits);
out.write(reinterpret_cast<char*>(buffer), 4);
}
// Fixed: Struct serialization through explicit field access
template<typename T>
void serialize_fields(std::ostream& out, const T& obj) {
// Each type must provide its own serialize method
obj.serialize(out, *this);
}
};
// Fixed: Encapsulated bit operations
// In dedicated header: bit_ops.h
class BitOperations {
public:
static int count_leading_zeros(uint32_t value) {
if (value == 0) return 32;
#if defined(__GNUC__) || defined(__clang__)
return __builtin_clz(value);
#elif defined(_MSC_VER)
unsigned long index;
_BitScanReverse(&index, value);
return 31 - static_cast<int>(index);
#else
return portable_clz(value);
#endif
}
private:
static int portable_clz(uint32_t value) {
// de Bruijn sequence method - portable and consistent
static const int debruijn32[32] = {
0, 31, 9, 30, 3, 8, 18, 29, 2, 5, 7, 14, 12, 17,
22, 28, 1, 10, 4, 19, 6, 15, 13, 23, 11, 20, 16,
24, 21, 25, 26, 27
};
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value++;
return debruijn32[value * 0x076be629 >> 27];
}
};
// Fixed: Platform abstraction interface
class IPlatformMemory {
public:
virtual ~IPlatformMemory() = default;
virtual void fast_copy(void* dest, const void* src, size_t size) = 0;
virtual void secure_zero(void* ptr, size_t size) = 0;
};
class PortablePlatformMemory : public IPlatformMemory {
public:
void fast_copy(void* dest, const void* src, size_t size) override {
std::memcpy(dest, src, size);
}
void secure_zero(void* ptr, size_t size) override {
volatile unsigned char* p = static_cast<volatile unsigned char*>(ptr);
while (size--) {
*p++ = 0;
}
}
};
#ifdef __x86_64__
class X86PlatformMemory : public IPlatformMemory {
public:
void fast_copy(void* dest, const void* src, size_t size) override {
if (size >= 256 && supports_avx()) {
avx_memcpy(dest, src, size);
} else {
std::memcpy(dest, src, size);
}
}
void secure_zero(void* ptr, size_t size) override {
// Use x86-specific secure zero with memory barrier
std::memset(ptr, 0, size);
std::atomic_thread_fence(std::memory_order_seq_cst);
}
private:
bool supports_avx() { /* Check CPUID */ return false; }
void avx_memcpy(void* dest, const void* src, size_t size) { }
};
#endif
// Factory to get appropriate implementation
std::unique_ptr<IPlatformMemory> create_platform_memory() {
#ifdef __x86_64__
return std::make_unique<X86PlatformMemory>();
#else
return std::make_unique<PortablePlatformMemory>();
#endif
}
CVE Examples
This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality concern rather than a direct security vulnerability.
Related CWEs
- CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior (parent)
- CWE-1061: Insufficient Encapsulation (parent)
- CWE-188: Reliance on Data/Memory Layout (child)
- CWE-1102: Reliance on Machine-Dependent Data Representation (peer)
References
- MITRE Corporation. "CWE-1105: Insufficient Encapsulation of Machine-Dependent Functionality." https://cwe.mitre.org/data/definitions/1105.html
- CERT C Coding Standard. "DCL39-C: Avoid information leakage when passing a structure across a trust boundary."