Hardware Logic with Insecure De-Synchronization between Control and Data Channels

Description

Hardware Logic with Insecure De-Synchronization between Control and Data Channels occurs when hardware error handling and security check logic allow data to propagate forward before security validations complete. High-performance on-chip bus protocols use separate control and data channels for efficiency. Logic bugs can permit data forwarding before security checks finish. If attackers can observe this data in hardware state, confidentiality breaches result. The Meltdown vulnerability exemplifies this—privileged data was speculatively loaded and returned regardless of privilege level, with traces remaining in microarchitectural state despite exception handling.

Risk

Control/data de-synchronization has severe security implications. Privileged data may be leaked speculatively. Security checks may be bypassed through timing. Cache side-channels may reveal protected data. Microarchitectural state may retain secrets. Transient execution attacks become possible. Memory isolation may be violated. Kernel memory may be readable from user space. Cryptographic keys may be exposed.

Solution

Thoroughly verify the data routing logic to ensure that any error handling or security checks effectively block illegal dataflows. Buffer all data until security authorization completes. Implement speculation barriers. Clear microarchitectural state on security faults. Use constant-time implementations. Ensure control and data paths remain synchronized. Validate security checks complete before data forwarding.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Memory - Protected data may be read through transient execution and side-channel analysis.
ConfidentialityScope: Confidentiality

Read Application Data - Application secrets may leak through microarchitectural side-channels.

Example Code

Vulnerable Code

// Vulnerable: Data forwarding before security check completion

module vulnerable_bus_bridge (
    input wire clk,
    input wire reset_n,
    // Request from untrusted CPU
    input wire [31:0] req_addr,
    input wire [31:0] req_data,
    input wire req_read,
    input wire req_write,
    input wire req_valid,
    // Firewall interface
    output reg [31:0] fw_addr,
    output reg fw_check_request,
    input wire fw_access_allowed,
    input wire fw_check_complete,
    // Memory interface
    output reg [31:0] mem_addr,
    output reg mem_read,
    output reg mem_write,
    input wire [31:0] mem_read_data,
    // Response to CPU
    output reg [31:0] resp_data,
    output reg resp_valid
);

    reg [31:0] pending_addr;
    reg pending_read;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            mem_read <= 1'b0;
            mem_write <= 1'b0;
            resp_valid <= 1'b0;
        end
        else begin
            // Start firewall check
            if (req_valid) begin
                fw_addr <= req_addr;
                fw_check_request <= 1'b1;
                pending_addr <= req_addr;
                pending_read <= req_read;
            end

            // VULNERABLE: Forward read to memory BEFORE firewall completes
            // This is done for performance, but creates side-channel
            if (req_valid && req_read) begin
                mem_addr <= req_addr;
                mem_read <= 1'b1;  // VULNERABLE: Speculative read
            end

            // When firewall completes, check result
            if (fw_check_complete) begin
                if (fw_access_allowed) begin
                    // Access allowed - return data
                    resp_data <= mem_read_data;
                    resp_valid <= 1'b1;
                end
                else begin
                    // VULNERABLE: Data already read, may be in cache
                    // Even though we don't return it, attacker can
                    // observe it through cache timing
                    resp_data <= 32'h0;
                    resp_valid <= 1'b1;
                end
                mem_read <= 1'b0;
            end
        end
    end

    // Attack (Meltdown-style):
    // 1. Request read to protected address
    // 2. Memory read happens speculatively
    // 3. Protected data loads into cache
    // 4. Firewall denies access, data not returned
    // 5. But cache state reveals the data through timing

endmodule

// Vulnerable: Processor with speculative execution leak
module vulnerable_speculative_load (
    input wire clk,
    input wire reset_n,
    input wire [31:0] load_addr,
    input wire load_request,
    input wire [1:0] load_privilege,
    input wire [1:0] current_privilege,
    output reg [31:0] load_data,
    output reg load_complete,
    output reg load_fault
);

    reg [31:0] speculative_data;
    reg speculative_load_done;
    reg privilege_check_done;
    reg privilege_ok;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            speculative_load_done <= 1'b0;
            privilege_check_done <= 1'b0;
            load_complete <= 1'b0;
            load_fault <= 1'b0;
        end
        else if (load_request) begin
            // VULNERABLE: Load data speculatively
            speculative_data <= memory_read(load_addr);
            speculative_load_done <= 1'b1;

            // Privilege check happens in parallel but takes longer
            privilege_ok <= (current_privilege >= load_privilege);
            privilege_check_done <= 1'b1;
        end
        else if (speculative_load_done && privilege_check_done) begin
            if (privilege_ok) begin
                load_data <= speculative_data;
                load_complete <= 1'b1;
            end
            else begin
                // VULNERABLE: Data was already loaded and may have
                // affected cache state before we get here
                load_fault <= 1'b1;
                load_data <= 32'h0;
            end
            speculative_load_done <= 1'b0;
            privilege_check_done <= 1'b0;
        end
    end

endmodule
// Vulnerable: CPU code susceptible to Meltdown

#include <stdint.h>

// Attacker's array for cache side-channel
uint8_t probe_array[256 * 4096];

void vulnerable_meltdown_gadget(uint8_t* kernel_addr) {
    // This code is VULNERABLE to Meltdown attack

    uint8_t kernel_byte;

    // VULNERABLE: Speculative read of kernel memory
    // Even though this will fault, the speculative execution
    // will read the byte and affect cache state
    kernel_byte = *kernel_addr;

    // VULNERABLE: Dependent load based on kernel byte
    // This loads probe_array[kernel_byte * 4096] into cache
    volatile uint8_t dummy = probe_array[kernel_byte * 4096];

    // The fault happens here, but cache state already leaked
}

// Attacker can then measure cache timing to determine kernel_byte
void meltdown_attack(uint8_t* kernel_addr) {
    // Flush probe array from cache
    for (int i = 0; i < 256; i++) {
        _mm_clflush(&probe_array[i * 4096]);
    }

    // Execute vulnerable gadget (will fault)
    try_execute(vulnerable_meltdown_gadget, kernel_addr);

    // Measure cache timing for each probe_array element
    for (int i = 0; i < 256; i++) {
        uint64_t start = rdtsc();
        volatile uint8_t dummy = probe_array[i * 4096];
        uint64_t time = rdtsc() - start;

        if (time < CACHE_HIT_THRESHOLD) {
            // This index was in cache - it's the kernel byte value!
            printf("Kernel byte: %d\n", i);
        }
    }
}

Fixed Code

// Fixed: Data blocked until security check completes

module secure_bus_bridge (
    input wire clk,
    input wire reset_n,
    // Request from untrusted CPU
    input wire [31:0] req_addr,
    input wire [31:0] req_data,
    input wire req_read,
    input wire req_write,
    input wire req_valid,
    // Firewall interface
    output reg [31:0] fw_addr,
    output reg fw_check_request,
    input wire fw_access_allowed,
    input wire fw_check_complete,
    // Memory interface
    output reg [31:0] mem_addr,
    output reg mem_read,
    output reg mem_write,
    input wire [31:0] mem_read_data,
    // Response to CPU
    output reg [31:0] resp_data,
    output reg resp_valid
);

    // State machine
    parameter IDLE = 2'd0;
    parameter WAIT_FIREWALL = 2'd1;
    parameter DO_ACCESS = 2'd2;
    parameter RESPOND = 2'd3;

    reg [1:0] state;
    reg [31:0] pending_addr;
    reg pending_read;
    reg pending_write;
    reg [31:0] pending_data;
    reg access_allowed_reg;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            state <= IDLE;
            mem_read <= 1'b0;
            mem_write <= 1'b0;
            resp_valid <= 1'b0;
            fw_check_request <= 1'b0;
        end
        else begin
            case (state)
                IDLE: begin
                    resp_valid <= 1'b0;
                    if (req_valid) begin
                        // Save request
                        pending_addr <= req_addr;
                        pending_read <= req_read;
                        pending_write <= req_write;
                        pending_data <= req_data;

                        // Start firewall check
                        fw_addr <= req_addr;
                        fw_check_request <= 1'b1;
                        state <= WAIT_FIREWALL;

                        // FIXED: Do NOT access memory yet
                        mem_read <= 1'b0;
                        mem_write <= 1'b0;
                    end
                end

                WAIT_FIREWALL: begin
                    fw_check_request <= 1'b0;

                    if (fw_check_complete) begin
                        access_allowed_reg <= fw_access_allowed;

                        if (fw_access_allowed) begin
                            // FIXED: Only access memory AFTER firewall approves
                            mem_addr <= pending_addr;
                            mem_read <= pending_read;
                            mem_write <= pending_write;
                            state <= DO_ACCESS;
                        end
                        else begin
                            // FIXED: No memory access at all for denied requests
                            state <= RESPOND;
                        end
                    end
                end

                DO_ACCESS: begin
                    // Wait for memory operation
                    mem_read <= 1'b0;
                    mem_write <= 1'b0;
                    state <= RESPOND;
                end

                RESPOND: begin
                    if (access_allowed_reg) begin
                        resp_data <= mem_read_data;
                    end
                    else begin
                        resp_data <= 32'h0;
                    end
                    resp_valid <= 1'b1;
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule

// Fixed: Processor with serialized privilege check
module secure_load_unit (
    input wire clk,
    input wire reset_n,
    input wire [31:0] load_addr,
    input wire load_request,
    input wire [1:0] addr_privilege,     // Required privilege for address
    input wire [1:0] current_privilege,  // Current execution privilege
    output reg [31:0] load_data,
    output reg load_complete,
    output reg load_fault
);

    parameter IDLE = 2'd0;
    parameter CHECK_PRIVILEGE = 2'd1;
    parameter DO_LOAD = 2'd2;
    parameter COMPLETE = 2'd3;

    reg [1:0] state;
    reg [31:0] saved_addr;
    reg privilege_ok;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            state <= IDLE;
            load_complete <= 1'b0;
            load_fault <= 1'b0;
        end
        else begin
            case (state)
                IDLE: begin
                    load_complete <= 1'b0;
                    load_fault <= 1'b0;

                    if (load_request) begin
                        saved_addr <= load_addr;
                        state <= CHECK_PRIVILEGE;
                    end
                end

                CHECK_PRIVILEGE: begin
                    // FIXED: Complete privilege check BEFORE any memory access
                    privilege_ok = (current_privilege >= addr_privilege);

                    if (privilege_ok) begin
                        state <= DO_LOAD;
                    end
                    else begin
                        // FIXED: Fault without ever touching memory
                        load_fault <= 1'b1;
                        load_data <= 32'h0;
                        state <= COMPLETE;
                    end
                end

                DO_LOAD: begin
                    // FIXED: Only reach here if privilege check passed
                    load_data <= memory_read(saved_addr);
                    load_complete <= 1'b1;
                    state <= COMPLETE;
                end

                COMPLETE: begin
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule

// Fixed: Speculative execution with barriers
module secure_speculative_unit (
    input wire clk,
    input wire reset_n,
    input wire [31:0] addr,
    input wire load_request,
    input wire [1:0] required_privilege,
    input wire [1:0] current_privilege,
    output reg [31:0] data,
    output reg complete,
    output reg fault
);

    // FIXED: Use speculation barrier
    reg speculation_allowed;
    reg [31:0] speculative_result;
    reg privilege_verified;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            speculation_allowed <= 1'b0;
            complete <= 1'b0;
            fault <= 1'b0;
        end
        else begin
            // FIXED: Privilege check gates speculation
            privilege_verified <= (current_privilege >= required_privilege);

            // FIXED: Only allow speculative execution if privilege is verified
            speculation_allowed <= privilege_verified;

            if (load_request && speculation_allowed) begin
                speculative_result <= memory_read(addr);
                complete <= 1'b1;
                data <= speculative_result;
            end
            else if (load_request && !speculation_allowed) begin
                // FIXED: Block speculative access to protected memory
                fault <= 1'b1;
                data <= 32'h0;
            end
        end
    end

    // FIXED: Clear microarchitectural state on fault
    always @(posedge clk) begin
        if (fault) begin
            // Flush any speculative state
            flush_speculation_state();
        end
    end

endmodule
// Fixed: Meltdown mitigations

#include <stdint.h>

// FIXED: Kernel page table isolation (KPTI)
// Kernel pages are not mapped in user page tables
// Prevents speculative access to kernel memory

void setup_kpti(void) {
    // Create separate page tables for user and kernel mode
    // User page tables don't contain kernel mappings

    user_pgd = create_user_page_tables();
    kernel_pgd = create_kernel_page_tables();

    // On syscall entry, switch to kernel page tables
    // On syscall exit, switch to user page tables
}

// FIXED: Serialize speculation on privilege boundary
void secure_syscall_entry(void) {
    // FIXED: Speculation barrier before accessing kernel data
    __asm__ volatile("lfence" ::: "memory");

    // Now safe to access kernel memory
}

// FIXED: Clear microarchitectural state
void secure_syscall_exit(void) {
    // Clear L1 data cache
    flush_l1d_cache();

    // Clear branch prediction state
    clear_branch_history();

    // Speculation barrier
    __asm__ volatile("lfence" ::: "memory");

    // Switch to user page tables
    switch_to_user_pgd();
}

// FIXED: Bounds checking with speculation barrier
int secure_array_access(int index, int* array, int array_size) {
    // Check bounds
    if (index >= array_size) {
        return -1;
    }

    // FIXED: Speculation barrier after bounds check
    // Prevents speculative access past the check
    __asm__ volatile("lfence" ::: "memory");

    // Now safe to access
    return array[index];
}

CVE Examples

  • CVE-2017-5754 (Meltdown): Microprocessors with speculative execution allowed unauthorized information disclosure via cache side-channel analysis

  • CWE-821: Incorrect Synchronization (parent)
  • CWE-1037: Processor Optimization Removal or Modification of Security-critical Code (peer)
  • CAPEC-233: Privilege Escalation (attack pattern)
  • CAPEC-663: Exploitation of Transient Instruction Execution (attack pattern)

References

  1. MITRE Corporation. "CWE-1264: Hardware Logic with Insecure De-Synchronization between Control and Data Channels." https://cwe.mitre.org/data/definitions/1264.html
  2. Lipp et al. "Meltdown: Reading Kernel Memory from User Space"
  3. Intel. "Speculative Execution Side Channel Mitigations"