Non-Transparent Sharing of Microarchitectural Resources
Description
Non-Transparent Sharing of Microarchitectural Resources occurs when hardware structures shared across execution contexts (e.g., caches and branch predictors) violate the expected architecture isolation between contexts. Modern processors employ performance optimization techniques like out-of-order execution, speculation, and caching that operate transparently to programmers. When hardware implementations share these resources across isolated execution contexts, they create covert channels exploitable by attackers. Specific vulnerable shared resources include caches, branch prediction logic, and load/store buffers. The combination of speculative execution and out-of-order processing amplifies attacker control over data leakage through these channels.
Risk
Non-transparent resource sharing has severe security implications. Cryptographic keys extractable through cache timing. ASLR offsets leaked via branch predictors. Arbitrary memory contents readable. Cross-process information disclosure. Speculative execution attacks (Spectre, Meltdown). Covert channels between security domains. Undocumented sharing makes protection extremely difficult.
Solution
Implement partitioned caches to prevent cross-context data sharing during architecture and design phase. Deploy new barrier and flush instructions for cache control. Disable high-resolution performance counters and timers that enable timing side-channels. Implement speculative execution barriers. Use separate branch prediction state per security domain. Consider hardware partitioning of shared resources.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Sensitive information including cryptographic keys, ASLR offsets, and arbitrary memory contents extractable through microarchitectural side-channels. |
Example Code
Vulnerable Code
// Vulnerable: Code susceptible to Spectre-style attacks
#include <stdint.h>
#include <string.h>
// VULNERABLE: Array bounds check can be bypassed via speculative execution
uint8_t array1[16];
uint8_t array2[256 * 512]; // Probe array for cache timing
size_t array1_size = 16;
// VULNERABLE: Secret data in adjacent memory
uint8_t secret_data[256] = "SECRET_ENCRYPTION_KEY_12345678";
uint8_t vulnerable_bounds_check(size_t x) {
// VULNERABLE: Speculative execution ignores bounds check
if (x < array1_size) {
// During speculative execution, CPU may execute this
// even when x >= array1_size
// VULNERABLE: Out-of-bounds read into secret_data
uint8_t value = array1[x]; // x could be large, reading secrets
// VULNERABLE: Dependent load brings secret into cache
return array2[value * 512]; // Creates cache side-channel
}
return 0;
}
// Attack:
// 1. Train branch predictor to expect (x < array1_size) is true
// 2. Call with x = offset to secret_data (e.g., x = secret_data - array1)
// 3. Branch mispredicts, speculatively executes body
// 4. array1[x] reads secret byte, even though x is out of bounds
// 5. array2[secret_byte * 512] loads into cache
// 6. Execution rolls back, but cache state remains
// 7. Time access to array2 to determine which line is cached
// 8. Cached line reveals secret byte value
// VULNERABLE: Shared branch predictor state
void vulnerable_indirect_branch(int index) {
// VULNERABLE: Indirect branch can be trained by attacker
static void (*handlers[4])(void) = {
handler0, handler1, handler2, handler3
};
if (index >= 0 && index < 4) {
handlers[index](); // VULNERABLE: Speculative indirect branch
}
// Attack: Attacker in different thread trains branch target buffer
// Victim's indirect branch speculatively goes to attacker-chosen address
// Attacker can cause victim to speculatively execute gadgets
}
// VULNERABLE: Cache timing side-channel
int vulnerable_cache_timing_leak(const uint8_t* secret_key, const uint8_t* input) {
uint8_t result = 0;
for (int i = 0; i < 16; i++) {
// VULNERABLE: Table lookup depends on secret key
// Cache miss/hit patterns reveal key bits
result ^= sbox[secret_key[i] ^ input[i]];
}
return result;
}
// Vulnerable: Hardware with shared microarchitectural resources
module vulnerable_shared_cache (
input wire clk,
input wire rst_n,
// Core 0 interface (trusted)
input wire [31:0] core0_addr,
input wire core0_read,
input wire [1:0] core0_security_level,
// Core 1 interface (untrusted)
input wire [31:0] core1_addr,
input wire core1_read,
input wire [1:0] core1_security_level,
// Cache outputs
output reg [31:0] cache_data,
output reg cache_hit
);
// VULNERABLE: Shared cache between cores
// No partitioning between security levels
reg [31:0] cache_data_array [0:255];
reg [23:0] cache_tag_array [0:255];
reg cache_valid [0:255];
wire [7:0] core0_index = core0_addr[11:4];
wire [7:0] core1_index = core1_addr[11:4];
// VULNERABLE: Both cores share same cache lines
// Core 1 can evict Core 0's cache lines
// Cache timing reveals Core 0's access patterns to Core 1
always @(posedge clk) begin
// Core 0 access
if (core0_read) begin
// VULNERABLE: Access affects shared cache state
// Core 1 can observe timing differences
end
// Core 1 access
if (core1_read) begin
// VULNERABLE: Can probe to detect Core 0's accesses
// Prime+Probe attack possible
end
end
endmodule
// Vulnerable: Shared branch predictor
module vulnerable_branch_predictor (
input wire clk,
input wire rst_n,
// Core 0 inputs
input wire [31:0] core0_pc,
input wire core0_branch_taken,
input wire core0_update,
// Core 1 inputs
input wire [31:0] core1_pc,
input wire core1_branch_taken,
input wire core1_update,
// Predictions
output wire core0_prediction,
output wire core1_prediction
);
// VULNERABLE: Shared branch history table
reg [1:0] branch_history_table [0:1023];
wire [9:0] core0_index = core0_pc[11:2];
wire [9:0] core1_index = core1_pc[11:2];
// VULNERABLE: Core 1 can train predictor state used by Core 0
// Spectre v2 (Branch Target Injection) attack possible
always @(posedge clk) begin
if (core1_update) begin
// VULNERABLE: Core 1's branches affect shared predictor
// Can poison predictions for Core 0
if (core1_branch_taken)
branch_history_table[core1_index] <= branch_history_table[core1_index] + 1;
else
branch_history_table[core1_index] <= branch_history_table[core1_index] - 1;
end
end
// VULNERABLE: Core 0 uses poisoned predictions
assign core0_prediction = branch_history_table[core0_index][1];
assign core1_prediction = branch_history_table[core1_index][1];
endmodule
Fixed Code
// Fixed: Mitigations against microarchitectural attacks
#include <stdint.h>
#include <string.h>
// FIXED: Spectre mitigation with speculation barrier
#if defined(__x86_64__)
#define speculation_barrier() __asm__ __volatile__("lfence" ::: "memory")
#elif defined(__aarch64__)
#define speculation_barrier() __asm__ __volatile__("csdb" ::: "memory")
#else
#define speculation_barrier() __asm__ __volatile__("" ::: "memory")
#endif
uint8_t array1[16];
uint8_t array2[256 * 512];
size_t array1_size = 16;
uint8_t secure_bounds_check(size_t x) {
if (x < array1_size) {
// FIXED: Speculation barrier prevents speculative execution
speculation_barrier();
uint8_t value = array1[x];
return array2[value * 512];
}
return 0;
}
// FIXED: Index masking (bounds clipping)
uint8_t secure_bounds_mask(size_t x) {
// FIXED: Create mask that is 0 if x >= array1_size
size_t mask = ~(x - array1_size) >> (sizeof(size_t) * 8 - 1);
mask = mask - 1; // All 1s if valid, all 0s if invalid
// FIXED: Apply mask to index - out of bounds becomes 0
x = x & mask;
uint8_t value = array1[x];
return array2[value * 512];
}
// FIXED: Retpoline for indirect branches (Spectre v2 mitigation)
#define RETPOLINE_CALL(func, arg) \
__asm__ __volatile__( \
"call retpoline_call_target\n" \
: : "D" (arg), "S" (func) : "memory")
void secure_indirect_branch(int index) {
static void (*handlers[4])(void) = {
handler0, handler1, handler2, handler3
};
if (index >= 0 && index < 4) {
// FIXED: Use retpoline to prevent speculative indirect branch attacks
// Or use IBRS (Indirect Branch Restricted Speculation) if available
speculation_barrier();
handlers[index]();
}
}
// FIXED: Constant-time table lookup (no cache timing side-channel)
uint8_t secure_constant_time_lookup(const uint8_t* table, size_t index, size_t table_size) {
uint8_t result = 0;
// FIXED: Access ALL table entries, select the right one with masking
for (size_t i = 0; i < table_size; i++) {
// Constant-time comparison: mask is all 1s if i == index, else all 0s
uint8_t mask = -((i ^ index) == 0);
result |= table[i] & mask;
}
return result;
}
// FIXED: Cache-timing resistant AES (bit-sliced implementation)
void secure_aes_no_cache_timing(const uint8_t* key, const uint8_t* input,
uint8_t* output) {
// FIXED: Use bit-sliced implementation with no table lookups
// All operations are arithmetic/logical, no memory access patterns
aes_bitsliced_encrypt(key, input, output);
}
// Fixed: Hardware with partitioned microarchitectural resources
module secure_partitioned_cache (
input wire clk,
input wire rst_n,
// Core 0 interface (trusted)
input wire [31:0] core0_addr,
input wire core0_read,
input wire [1:0] core0_security_level,
// Core 1 interface (untrusted)
input wire [31:0] core1_addr,
input wire core1_read,
input wire [1:0] core1_security_level,
// Cache outputs
output reg [31:0] core0_data,
output reg core0_hit,
output reg [31:0] core1_data,
output reg core1_hit
);
// FIXED: Separate cache partitions per security domain
// Way partitioning: Core 0 uses ways 0-1, Core 1 uses ways 2-3
reg [31:0] cache_data_way0 [0:127]; // Core 0 partition
reg [31:0] cache_data_way1 [0:127];
reg [31:0] cache_data_way2 [0:127]; // Core 1 partition
reg [31:0] cache_data_way3 [0:127];
reg [23:0] cache_tag_way0 [0:127];
reg [23:0] cache_tag_way1 [0:127];
reg [23:0] cache_tag_way2 [0:127];
reg [23:0] cache_tag_way3 [0:127];
// FIXED: Each core can only affect its own partition
// No cross-domain cache interference
wire [6:0] core0_index = core0_addr[10:4];
wire [6:0] core1_index = core1_addr[10:4];
always @(posedge clk) begin
// FIXED: Core 0 only accesses ways 0-1
if (core0_read) begin
// Cache lookup in partitioned ways
// Cannot evict Core 1's lines
end
// FIXED: Core 1 only accesses ways 2-3
if (core1_read) begin
// Cannot observe Core 0's access patterns
end
end
endmodule
// Fixed: Per-context branch predictor
module secure_branch_predictor (
input wire clk,
input wire rst_n,
// Core 0 inputs
input wire [31:0] core0_pc,
input wire core0_branch_taken,
input wire core0_update,
input wire [7:0] core0_context_id,
// Core 1 inputs
input wire [31:0] core1_pc,
input wire core1_branch_taken,
input wire core1_update,
input wire [7:0] core1_context_id,
// Predictions
output wire core0_prediction,
output wire core1_prediction
);
// FIXED: Separate branch predictor state per context
reg [1:0] bht_context0 [0:1023]; // Core 0 / trusted
reg [1:0] bht_context1 [0:1023]; // Core 1 / untrusted
wire [9:0] core0_index = core0_pc[11:2];
wire [9:0] core1_index = core1_pc[11:2];
// FIXED: Updates only affect own context's predictor
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// Initialize predictors
end else begin
// FIXED: Core 0 updates only its predictor
if (core0_update) begin
if (core0_branch_taken)
bht_context0[core0_index] <= bht_context0[core0_index] + 1;
else
bht_context0[core0_index] <= bht_context0[core0_index] - 1;
end
// FIXED: Core 1 updates only its predictor
if (core1_update) begin
if (core1_branch_taken)
bht_context1[core1_index] <= bht_context1[core1_index] + 1;
else
bht_context1[core1_index] <= bht_context1[core1_index] - 1;
end
end
end
// FIXED: Each core uses only its own prediction state
assign core0_prediction = bht_context0[core0_index][1];
assign core1_prediction = bht_context1[core1_index][1];
// FIXED: Context switch clears/switches predictor state
// FIXED: IBPB (Indirect Branch Prediction Barrier) support for flushes
endmodule
CVE Examples
- CVE-2017-5753: Spectre Variant 1 - Bounds Check Bypass via speculative execution.
- CVE-2017-5715: Spectre Variant 2 - Branch Target Injection.
- CVE-2017-5754: Meltdown - Rogue Data Cache Load.
- CVE-2018-3639: Speculative Store Bypass.
- CVE-2019-1125: SWAPGS Attack.
Related CWEs
- CWE-203: Observable Discrepancy (parent)
- CWE-1189: Improper Isolation of Shared Resources on System-on-a-Chip (parent)
- CWE-1198: Privilege Separation and Access Control Issues (category)
- CWE-1300: Improper Protection of Physical Side Channels (related)
References
- MITRE Corporation. "CWE-1303: Non-Transparent Sharing of Microarchitectural Resources." https://cwe.mitre.org/data/definitions/1303.html
- Kocher, P., et al. "Spectre Attacks: Exploiting Speculative Execution"
- Lipp, M., et al. "Meltdown: Reading Kernel Memory from User Space"