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

ImpactDetails
ConfidentialityScope: 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.

  • 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

  1. MITRE Corporation. "CWE-1303: Non-Transparent Sharing of Microarchitectural Resources." https://cwe.mitre.org/data/definitions/1303.html
  2. Kocher, P., et al. "Spectre Attacks: Exploiting Speculative Execution"
  3. Lipp, M., et al. "Meltdown: Reading Kernel Memory from User Space"