CPU-Hardware nicht konfiguriert zur Unterstützung der Exklusivität von Schreib- und Ausführungsoperationen
Beschreibung
CPU-Hardware nicht konfiguriert zur Unterstützung der Exklusivität von Schreib- und Ausführungsoperationen tritt auf, wenn der CPU Hardware-Unterstützung fehlt oder Funktionen deaktiviert sind, die gleichzeitigen Schreib- und Ausführungszugriff auf Speicherbereiche verhindern. Moderne CPUs enthalten typischerweise ein spezielles Bit (oft NX/XD-Bit genannt), das Speicher in Code- (ausführbar) und Daten- (nicht ausführbar) Regionen trennt. Einige Prozessoren implementieren dies über Memory Protection Units (MPU) oder Memory Management Units (MMU). CPUs ohne diese Funktionen -- oder mit deaktiviertem Schutz -- können jedoch keine Schreib-/Ausführungsexklusivität erzwingen, wodurch Angreifer bösartigen Code einschleusen und ausführen können.
Risiko
Fehlende Schreib-/Ausführungsexklusivität hat schwerwiegende Sicherheitsauswirkungen. Angreifer können bösartigen Code in beschreibbaren Speicher einschleusen. Eingeschleuster Code kann ohne Einschränkungen ausgeführt werden. Buffer-Overflow-Exploits werden trivial. Code-Injection-Angriffe gelingen ohne zusätzliche Umgehungen. Return-Oriented Programming ist möglicherweise nicht erforderlich. Shellcode-Injection ist unkompliziert. Speicherbeschädigung führt direkt zur Codeausführung. Standard-Sicherheitsmaßnahmen sind unwirksam.
Lösung
Implementieren Sie ein dediziertes Bit zur Markierung von Daten als nicht-ausführbar oder integrieren Sie MMU/MPU-Unterstützung falls nicht verfügbar. Setzen Sie SoC-Interconnect-Firewalls ein, um Schreibexklusivität zu emulieren, wenn Hardware-Unterstützung fehlt. Aktivieren Sie DEP/NX-Bit in der Betriebssystemkonfiguration. Verwenden Sie Memory Protection Units auf Mikrocontrollern. Implementieren Sie Software-basierte W^X-Durchsetzung, wenn Hardware-Unterstützung begrenzt ist. Erwägen Sie Hardware-Upgrades für sicherheitskritische Anwendungen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Vertraulichkeit Unbefugte Codeausführung - Angreifer können beliebigen Code einschleusen und ausführen, was potenziell Zugriff auf sensible Daten ermöglicht. |
| Integrität | Umfang: Integrität Unbefugte Codeausführung - Bösartige Codeausführung ermöglicht die Änderung von Systemdaten und -verhalten. |
Beispielcode und Lösung
Verwundbarer Code
// VERWUNDBAR: System ohne W^X-Durchsetzung
#include <stdint.h>
#include <string.h>
// Beispiel: Mikrocontroller ohne MPU
// Gesamter Speicher ist sowohl beschreibbar als auch ausführbar
char data_buffer[256]; // Dieser Puffer ist ausführbar!
void vulnerable_input_handler(const char* input, size_t len) {
// VERWUNDBAR: Kopiert Benutzereingabe in ausführbaren Speicher
if (len < sizeof(data_buffer)) {
memcpy(data_buffer, input, len);
}
// Angreifer kann Shellcode in data_buffer einschleusen
// und dorthin zur Ausführung springen
}
// VERWUNDBAR: Funktionszeiger in beschreibbarem Speicher
typedef void (*callback_t)(void);
callback_t user_callback = NULL;
void vulnerable_set_callback(callback_t cb) {
// VERWUNDBAR: Angreifer kann dies mit Shellcode-Adresse überschreiben
user_callback = cb;
}
void vulnerable_execute_callback(void) {
if (user_callback) {
// VERWUNDBAR: Führt jede gespeicherte Adresse aus
user_callback();
}
}
// Angriffsszenario
void exploit_no_wxe(void) {
// Auszuführender Shellcode
unsigned char shellcode[] = {
0x31, 0xc0, // xor eax, eax
0x50, // push eax
0x68, 0x2f, 0x2f, 0x73, 0x68, // push "//sh"
0x68, 0x2f, 0x62, 0x69, 0x6e, // push "/bin"
0x89, 0xe3, // mov ebx, esp
0x50, // push eax
0x53, // push ebx
0x89, 0xe1, // mov ecx, esp
0xb0, 0x0b, // mov al, 0x0b
0xcd, 0x80 // int 0x80
};
// Shellcode in beschreibbaren Puffer kopieren
memcpy(data_buffer, shellcode, sizeof(shellcode));
// Zum Shellcode springen - funktioniert weil Puffer ausführbar ist
((void(*)())data_buffer)();
}
// VERWUNDBAR: Speichercontroller ohne Ausführungsschutz
module vulnerable_memory_controller (
input wire clk,
input wire reset_n,
input wire [31:0] addr,
input wire [31:0] write_data,
input wire write_enable,
input wire read_enable,
input wire fetch_enable, // Instruktionsabruf
output reg [31:0] read_data,
output reg operation_complete
);
// Speicher-Array
reg [31:0] memory [0:16383];
// VERWUNDBAR: Keine Trennung zwischen Code und Daten
// Alle Speicherbereiche können sowohl beschrieben als auch ausgeführt werden
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
operation_complete <= 1'b0;
end
else begin
if (write_enable) begin
// VERWUNDBAR: Schreiben an jede Adresse erlaubt
// Einschließlich Code-Regionen
memory[addr[15:2]] <= write_data;
operation_complete <= 1'b1;
end
else if (read_enable || fetch_enable) begin
// VERWUNDBAR: Ausführung (Abruf) von jeder Adresse erlaubt
// Einschließlich Datenregionen
read_data <= memory[addr[15:2]];
operation_complete <= 1'b1;
end
else begin
operation_complete <= 1'b0;
end
end
end
// Keine Zugriffskontrolle basierend auf:
// - Speicherregion-Attributen
// - Ausführungsberechtigungs-Bits
// - Schreibberechtigung für Code-Regionen
endmodule
// VERWUNDBAR: MPU nicht konfiguriert
module vulnerable_mpu_unused (
input wire clk,
input wire reset_n,
// MPU-Konfigurationsports vorhanden aber ungenutzt
input wire mpu_enable,
input wire [31:0] mpu_region_base,
input wire [31:0] mpu_region_size,
input wire [7:0] mpu_region_attr,
// Speicherzugriff
input wire [31:0] access_addr,
input wire access_write,
input wire access_execute,
output reg access_allowed
);
// VERWUNDBAR: MPU existiert aber ist nicht aktiviert
// Alle Zugriffe werden unabhängig von Attributen erlaubt
always @(*) begin
// Sollte MPU-Regionen und Berechtigungen prüfen
// Aber MPU ist deaktiviert, daher immer erlauben
access_allowed = 1'b1;
end
endmodule
Sichere Lösung
// SICHER: System mit W^X-Durchsetzung
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
// SICHER: Datenpuffer als nicht-ausführbar markiert
__attribute__((section(".data.noexec")))
char data_buffer[256];
void secure_init_memory_protection(void) {
// SICHER: Datensektionen als nicht-ausführbar markieren
extern char __data_start, __data_end;
size_t data_size = &__data_end - &__data_start;
// Datensegment als lesen-schreiben aber NICHT ausführbar setzen
if (mprotect(&__data_start, data_size, PROT_READ | PROT_WRITE) < 0) {
// Fehler behandeln - ausfallsicher
abort();
}
// SICHER: Code-Sektionen als nicht-beschreibbar markieren
extern char __text_start, __text_end;
size_t text_size = &__text_end - &__text_start;
if (mprotect(&__text_start, text_size, PROT_READ | PROT_EXEC) < 0) {
abort();
}
}
void secure_input_handler(const char* input, size_t len) {
// SICHER: Datenpuffer ist nicht ausführbar
// Selbst wenn Angreifer Code einschleust, kann er nicht ausgeführt werden
if (len < sizeof(data_buffer)) {
memcpy(data_buffer, input, len);
}
// Versuch data_buffer auszuführen wird einen Fehler verursachen
}
// SICHER: Funktionszeiger mit Validierung
typedef void (*callback_t)(void);
static callback_t registered_callbacks[16] = {0};
static size_t num_callbacks = 0;
int secure_register_callback(callback_t cb) {
// SICHER: Validieren, dass Callback im Code-Bereich liegt
extern char __text_start, __text_end;
if ((char*)cb < &__text_start || (char*)cb >= &__text_end) {
// Callback nicht im Code-Bereich - ablehnen
return -1;
}
if (num_callbacks < 16) {
registered_callbacks[num_callbacks++] = cb;
return 0;
}
return -1;
}
void secure_execute_callback(size_t index) {
if (index < num_callbacks && registered_callbacks[index]) {
// SICHER: Nur validierte Callbacks ausführen
registered_callbacks[index]();
}
}
// SICHER: MPU-Konfiguration für Cortex-M
void secure_configure_mpu(void) {
// MPU während Konfiguration deaktivieren
MPU->CTRL = 0;
// Region 0: Flash (Code) - Nur-Lesen, Ausführbar
MPU->RNR = 0;
MPU->RBAR = FLASH_BASE;
MPU->RASR = MPU_RASR_ENABLE_Msk |
(0x11 << MPU_RASR_SIZE_Pos) | // 256KB
(0x6 << MPU_RASR_AP_Pos) | // Nur-Lesen
(0 << MPU_RASR_XN_Pos); // Ausführbar
// Region 1: RAM (Daten) - Lesen-Schreiben, Nicht-Ausführbar
MPU->RNR = 1;
MPU->RBAR = SRAM_BASE;
MPU->RASR = MPU_RASR_ENABLE_Msk |
(0x10 << MPU_RASR_SIZE_Pos) | // 128KB
(0x3 << MPU_RASR_AP_Pos) | // Lesen-Schreiben
(1 << MPU_RASR_XN_Pos); // SICHER: Nicht-Ausführbar
// Region 2: Peripherie - Lesen-Schreiben, Nicht-Ausführbar
MPU->RNR = 2;
MPU->RBAR = PERIPH_BASE;
MPU->RASR = MPU_RASR_ENABLE_Msk |
(0x1C << MPU_RASR_SIZE_Pos) | // 512MB
(0x3 << MPU_RASR_AP_Pos) |
(1 << MPU_RASR_XN_Pos); // Nicht-Ausführbar
// MPU mit Standard-Speicherkarte für privilegierten Zugriff aktivieren
MPU->CTRL = MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk;
// Speicherbarriere
__DSB();
__ISB();
}
// SICHER: Speichercontroller mit W^X-Durchsetzung
module secure_memory_controller (
input wire clk,
input wire reset_n,
input wire [31:0] addr,
input wire [31:0] write_data,
input wire write_enable,
input wire read_enable,
input wire fetch_enable,
output reg [31:0] read_data,
output reg operation_complete,
output reg access_violation
);
// Speicher-Array
reg [31:0] memory [0:16383];
// SICHER: Speicherregion-Attribute
// Bit 0: Beschreibbar
// Bit 1: Ausführbar
// W^X: Diese Bits sollten niemals beide gesetzt sein
reg [1:0] region_attr [0:15]; // 16 Regionen je 4KB
// Regionsberechnung
wire [3:0] region_index = addr[15:12];
wire region_writable = region_attr[region_index][0];
wire region_executable = region_attr[region_index][1];
// SICHER: Regionen mit W^X-Durchsetzung initialisieren
initial begin
// Code-Region (0-3): Ausführbar, NICHT beschreibbar
region_attr[0] = 2'b10;
region_attr[1] = 2'b10;
region_attr[2] = 2'b10;
region_attr[3] = 2'b10;
// Daten-Region (4-11): Beschreibbar, NICHT ausführbar
region_attr[4] = 2'b01;
region_attr[5] = 2'b01;
region_attr[6] = 2'b01;
region_attr[7] = 2'b01;
region_attr[8] = 2'b01;
region_attr[9] = 2'b01;
region_attr[10] = 2'b01;
region_attr[11] = 2'b01;
// Stack-Region (12-13): Beschreibbar, NICHT ausführbar
region_attr[12] = 2'b01;
region_attr[13] = 2'b01;
// Reserviert (14-15): Weder beschreibbar noch ausführbar
region_attr[14] = 2'b00;
region_attr[15] = 2'b00;
end
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
operation_complete <= 1'b0;
access_violation <= 1'b0;
end
else begin
access_violation <= 1'b0;
if (write_enable) begin
// SICHER: Schreibberechtigung prüfen
if (region_writable) begin
memory[addr[15:2]] <= write_data;
operation_complete <= 1'b1;
end
else begin
// SICHER: Schreiben in nicht-beschreibbare Region verweigern
access_violation <= 1'b1;
operation_complete <= 1'b0;
end
end
else if (fetch_enable) begin
// SICHER: Ausführungsberechtigung prüfen
if (region_executable) begin
read_data <= memory[addr[15:2]];
operation_complete <= 1'b1;
end
else begin
// SICHER: Ausführung aus nicht-ausführbarer Region verweigern
access_violation <= 1'b1;
operation_complete <= 1'b0;
end
end
else if (read_enable) begin
// Datenlesen aus jeder lesbaren Region erlaubt
read_data <= memory[addr[15:2]];
operation_complete <= 1'b1;
end
else begin
operation_complete <= 1'b0;
end
end
end
endmodule
// SICHER: MPU korrekt konfiguriert und aktiviert
module secure_mpu_enabled (
input wire clk,
input wire reset_n,
input wire mpu_enable,
input wire [31:0] access_addr,
input wire access_write,
input wire access_execute,
output reg access_allowed,
output reg access_fault
);
// MPU-Regionskonfiguration
reg [31:0] region_base [0:7];
reg [31:0] region_limit [0:7];
reg region_writable [0:7];
reg region_executable [0:7];
reg region_enabled [0:7];
// Passende Region finden
reg region_found;
reg [2:0] matched_region;
reg match_writable;
reg match_executable;
integer i;
always @(*) begin
region_found = 1'b0;
matched_region = 3'h0;
match_writable = 1'b0;
match_executable = 1'b0;
// Regionen durchsuchen (höhere Priorität zuerst)
for (i = 7; i >= 0; i = i - 1) begin
if (region_enabled[i] &&
access_addr >= region_base[i] &&
access_addr < region_limit[i]) begin
region_found = 1'b1;
matched_region = i;
match_writable = region_writable[i];
match_executable = region_executable[i];
end
end
end
always @(*) begin
access_fault = 1'b0;
if (!mpu_enable) begin
// SICHER: Standardmäßig verweigern wenn MPU deaktiviert
access_allowed = 1'b0;
access_fault = 1'b1;
end
else if (!region_found) begin
// Keine passende Region - verweigern
access_allowed = 1'b0;
access_fault = 1'b1;
end
else begin
// SICHER: Berechtigungen prüfen
if (access_write && !match_writable) begin
access_allowed = 1'b0;
access_fault = 1'b1;
end
else if (access_execute && !match_executable) begin
access_allowed = 1'b0;
access_fault = 1'b1;
end
// SICHER: W^X-Prüfung
else if (access_write && access_execute) begin
// Gleichzeitiges Schreiben und Ausführen niemals erlauben
access_allowed = 1'b0;
access_fault = 1'b1;
end
else begin
access_allowed = 1'b1;
end
end
end
endmodule
CVE-Beispiele
- MCS51 Mikrocontroller: Fehlt sowohl dediziertes Exklusivitäts-Bit als auch MMU/MPU-Unterstützung, wodurch gesamter Speicher sowohl beschreibbar als auch ausführbar ist.
- Cortex-M ohne MPU-Konfiguration: MPU existiert aber bleibt unkonfiguriert, was Code-Injection-Angriffe ermöglicht.
- Verschiedene eingebettete Systeme und IoT-Geräte ohne W^X-Durchsetzung.
Verwandte CWEs
- CWE-284: Unsachgemäße Zugriffskontrolle (übergeordnet)
- CWE-119: Unsachgemäße Beschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers (verwandt)
- CWE-94: Unsachgemäße Kontrolle der Codegenerierung ('Code Injection') (verwandt)
- CAPEC-679: Ausnutzung unsachgemäß konfigurierter Speicherschutzmaßnahmen (Angriffsmuster)
Referenzen
- MITRE Corporation. "CWE-1252: CPU Hardware Not Configured to Support Exclusivity of Write and Execute Operations." https://cwe.mitre.org/data/definitions/1252.html
- ARM. "Cortex-M MPU Configuration Guide"
- Intel. "Execute Disable Bit Functionality"