Operation auf Ressource in falscher Lebenszyklusphase

Beschreibung

Operation auf Ressource in falscher Lebenszyklusphase tritt auf, wenn Code Operationen auf einer Ressource durchführt, die für den aktuellen Zustand der Ressource in ihrem Lebenszyklus ungeeignet sind. Dies umfasst die Verwendung einer Ressource bevor sie bereit ist, nachdem sie geschlossen wurde, während der Initialisierung oder in einem ungültigen Zustand. Häufige Beispiele sind das Lesen aus nicht geöffneten Dateien, Schreiben auf geschlossene Verbindungen oder das Aufrufen von Methoden auf teilweise konstruierten Objekten.

Risiko

Undefiniertes Verhalten durch Verwendung von Ressourcen in ungültigen Zuständen. Datenkorruption durch Schreiben auf nicht ordnungsgemäß vorbereitete Ressourcen. Sicherheitslücken durch Umgehung von Initialisierungsroutinen. Abstürze durch Operationen auf freigegebenen Ressourcen. Informationsoffenlegung durch Zugriff auf Ressourcen vor Bereinigung. Authentifizierungsumgehung durch Verwendung von Sessions vor Validierung.

Lösung

Implementieren Sie explizite Zustandsverfolgung für Ressourcen. Verwenden Sie Zustandsmaschinen für komplexe Ressourcen-Lebenszyklen. Validieren Sie den Ressourcenzustand vor Operationen. Verwenden Sie Factory-Muster, um ordnungsgemäße Initialisierung sicherzustellen. Implementieren Sie ordnungsgemäße Aufräumsequenzen. Verwenden Sie Sprachkonstrukte, die den Lebenszyklus erzwingen (RAII, try-with-resources).

Häufige Konsequenzen

AuswirkungDetails
IntegritätBereich: Datenkorruption

Operationen auf nicht vorbereiteten Ressourcen beschädigen Daten.
VerfügbarkeitBereich: Abstürze

Ungültige Zustandsoperationen verursachen Fehler.
SicherheitBereich: Umgehung

Verwendung von Ressourcen bevor Sicherheitsprüfungen abgeschlossen sind.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Operationen auf Ressourcen in falscher Lebenszyklusphase
#include <stdio.h>
#include <stdlib.h>

// VERWUNDBAR: Verwendung vor Öffnen
FILE* global_log = NULL;

void log_event_vulnerable(const char* message) {
    // VERWUNDBAR: global_log könnte noch nicht geöffnet sein
    fprintf(global_log, "%s\n", message);  // Absturz wenn NULL
}

void init_logging() {
    global_log = fopen("app.log", "a");
}

// VERWUNDBAR: Verwendung nach Schließen
void process_file_vulnerable(const char* filename) {
    FILE* file = fopen(filename, "r");
    char buffer[1024];

    fread(buffer, 1, sizeof(buffer), file);
    fclose(file);

    // VERWUNDBAR: Verwendung von file nach Schließen
    fseek(file, 0, SEEK_SET);  // Undefiniertes Verhalten
    fread(buffer, 1, sizeof(buffer), file);
}

// VERWUNDBAR: Verwendung teilweise initialisierter Struktur
typedef struct {
    int (*process)(void* data);
    void* data;
    int size;
} Handler;

void use_handler_vulnerable(Handler* h) {
    // VERWUNDBAR: process könnte noch nicht gesetzt sein
    h->process(h->data);  // Absturz wenn process NULL
}

// VERWUNDBAR: Socket-Verwendung vor Verbindung
int socket_vulnerable() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    // VERWUNDBAR: Senden vor Verbindung
    char msg[] = "Hello";
    send(sock, msg, sizeof(msg), 0);  // Fehlschlag - nicht verbunden

    // Später: connect(sock, ...);
    return sock;
}

// VERWUNDBAR: Speicherverwendung während Freigabe
void double_use_vulnerable() {
    char* buffer = malloc(100);
    strcpy(buffer, "data");

    free(buffer);

    // VERWUNDBAR: Speicher wird freigegeben, nicht sicher
    // Selbst innerhalb derselben Funktion nach free
    memset(buffer, 0, 100);  // Use after free
}
# VERWUNDBAR: Python-Ressourcen-Lebenszyklus-Probleme
class VulnerableConnection:
    def __init__(self):
        self.socket = None
        self.connected = False

    def send(self, data):
        # VERWUNDBAR: Könnte nicht verbunden sein
        self.socket.send(data)  # AttributeError oder Socket-Fehler

    def connect(self, host, port):
        self.socket = socket.socket()
        self.socket.connect((host, port))
        self.connected = True

# VERWUNDBAR: Datenbank vor Verbindung verwenden
class VulnerableDatabase:
    def __init__(self):
        self.connection = None
        self.cursor = None

    def query(self, sql):
        # VERWUNDBAR: cursor existiert möglicherweise nicht
        self.cursor.execute(sql)  # AttributeError
        return self.cursor.fetchall()

    def connect(self, connstr):
        self.connection = psycopg2.connect(connstr)
        self.cursor = self.connection.cursor()

# VERWUNDBAR: Geschlossene Ressource verwenden
def process_file_vulnerable(filename):
    f = open(filename, 'r')
    content = f.read()
    f.close()

    # VERWUNDBAR: Datei ist geschlossen
    f.seek(0)  # ValueError: I/O operation on closed file
    more_content = f.read()

# VERWUNDBAR: Session vor Authentifizierung verwenden
class VulnerableSession:
    def __init__(self):
        self.user = None
        self.permissions = None

    def get_user_data(self):
        # VERWUNDBAR: Könnte vor authenticate() aufgerufen werden
        return {
            'username': self.user.username,  # AttributeError
            'permissions': self.permissions
        }

    def authenticate(self, username, password):
        if verify_credentials(username, password):
            self.user = load_user(username)
            self.permissions = load_permissions(username)
// VERWUNDBAR: Java-Ressourcen-Lebenszyklus-Probleme
public class VulnerableLifecycle {

    private Connection connection;
    private PreparedStatement statement;

    // VERWUNDBAR: Verwendung vor Initialisierung
    public ResultSet query(String sql) throws SQLException {
        // VERWUNDBAR: connection könnte null sein
        statement = connection.prepareStatement(sql);
        return statement.executeQuery();
    }

    public void connect(String url) throws SQLException {
        connection = DriverManager.getConnection(url);
    }

    // VERWUNDBAR: Verwendung nach Schließen
    public void processAndClose() throws SQLException {
        ResultSet rs = statement.executeQuery();

        connection.close();

        // VERWUNDBAR: Verwendung von connection nach Schließen
        while (rs.next()) {  // Könnte fehlschlagen - Verbindung geschlossen
            process(rs);
        }
    }

    // VERWUNDBAR: Builder vor Fertigstellung verwendet
    public static class VulnerableBuilder {
        private String host;
        private int port;
        private Config config;

        public void connect() {
            // VERWUNDBAR: config könnte nicht gesetzt sein
            Socket socket = new Socket(host, port);
            socket.setSoTimeout(config.getTimeout());  // NPE
        }

        public VulnerableBuilder host(String h) {
            this.host = h;
            return this;
        }

        public VulnerableBuilder config(Config c) {
            this.config = c;
            return this;
        }
    }
}

// VERWUNDBAR: Objekt während Konstruktion verwenden
public class VulnerableDuringConstruction {

    private final List<String> items = new ArrayList<>();
    private Thread workerThread;

    public VulnerableDuringConstruction() {
        // VERWUNDBAR: Thread starten der 'this' verwendet bevor Konstruktion abgeschlossen
        workerThread = new Thread(() -> {
            // 'this' könnte nicht vollständig konstruiert sein
            processItems();
        });
        workerThread.start();
    }

    public void processItems() {
        for (String item : items) {  // items könnte null sein
            process(item);
        }
    }
}
// VERWUNDBAR: JavaScript-Ressourcen-Lebenszyklus-Probleme
class VulnerableService {
    constructor() {
        this.db = null;
        this.initialized = false;
    }

    // VERWUNDBAR: Verwendung vor Initialisierung
    async query(sql) {
        // VERWUNDBAR: db könnte null sein
        return await this.db.query(sql);
    }

    async initialize() {
        this.db = await connectDatabase();
        this.initialized = true;
    }

    // VERWUNDBAR: Verwendung nach Entsorgung
    async dispose() {
        await this.db.close();
        this.db = null;
    }

    async fetchData() {
        // Aufgerufen nach dispose
        // VERWUNDBAR: db ist null
        return await this.db.query('SELECT * FROM data');
    }
}

// VERWUNDBAR: WebSocket in falschem Zustand verwendet
class VulnerableWebSocket {
    constructor(url) {
        this.ws = new WebSocket(url);
    }

    // VERWUNDBAR: Senden bevor Verbindung offen
    send(message) {
        // VERWUNDBAR: WebSocket könnte noch nicht OPEN sein
        this.ws.send(JSON.stringify(message));
        // Fehler: Noch im CONNECTING-Zustand
    }
}

// VERWUNDBAR: Promise-basierte Initialisierung nicht abgewartet
class VulnerableAsync {
    constructor() {
        this.data = null;
        this.init();  // VERWUNDBAR: Nicht abgewartet
    }

    async init() {
        this.data = await fetchData();  // Braucht Zeit
    }

    process() {
        // VERWUNDBAR: Könnte aufgerufen werden bevor init abgeschlossen
        return this.data.map(item => item.value);  // data ist null
    }
}

Lösungscode

// SICHER: Ordnungsgemäßes Lebenszyklus-Management in C
#include <stdio.h>
#include <stdlib.h>

// SICHER: Zustandsverfolgung für Ressourcen
typedef enum {
    LOG_UNINITIALIZED,
    LOG_OPEN,
    LOG_CLOSED
} LogState;

FILE* global_log = NULL;
LogState log_state = LOG_UNINITIALIZED;

void init_logging() {
    global_log = fopen("app.log", "a");
    if (global_log) {
        log_state = LOG_OPEN;
    }
}

void log_event_safe(const char* message) {
    // SICHER: Zustand vor Verwendung prüfen
    if (log_state != LOG_OPEN || global_log == NULL) {
        return;  // Oder initialisieren, oder Fehler
    }
    fprintf(global_log, "%s\n", message);
}

// SICHER: Ordnungsgemäßer Ressourcen-Lebenszyklus
void process_file_safe(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (!file) return;

    char buffer[1024];
    fread(buffer, 1, sizeof(buffer), file);

    // Verarbeiten während Datei offen
    process_data(buffer);

    // SICHER: Schließen wenn fertig, nicht danach verwenden
    fclose(file);
    file = NULL;
}

// SICHER: Handler-Zustand validieren
typedef struct {
    int (*process)(void* data);
    void* data;
    int size;
    int initialized;
} SafeHandler;

int use_handler_safe(SafeHandler* h) {
    // SICHER: Vor Verwendung validieren
    if (!h || !h->initialized || !h->process) {
        return -1;
    }
    return h->process(h->data);
}

SafeHandler* create_handler(int (*proc)(void*), void* data, int size) {
    SafeHandler* h = calloc(1, sizeof(SafeHandler));
    if (!h) return NULL;

    h->process = proc;
    h->data = data;
    h->size = size;
    h->initialized = 1;  // Als bereit markieren

    return h;
}

// SICHER: Ordnungsgemäßer Socket-Lebenszyklus
int socket_safe() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) return -1;

    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

    // SICHER: Verbinden vor Senden
    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        close(sock);
        return -1;
    }

    // SICHER: Socket ist verbunden
    char msg[] = "Hello";
    send(sock, msg, sizeof(msg), 0);

    return sock;
}
# SICHER: Python mit ordnungsgemäßem Lebenszyklus-Management
from enum import Enum, auto
import contextlib

class ConnectionState(Enum):
    DISCONNECTED = auto()
    CONNECTING = auto()
    CONNECTED = auto()
    CLOSED = auto()

class SafeConnection:
    def __init__(self):
        self.socket = None
        self.state = ConnectionState.DISCONNECTED

    def send(self, data):
        # SICHER: Zustand validieren
        if self.state != ConnectionState.CONNECTED:
            raise RuntimeError(f"Kann nicht senden im Zustand {self.state}")
        self.socket.send(data)

    def connect(self, host, port):
        if self.state != ConnectionState.DISCONNECTED:
            raise RuntimeError("Bereits verbunden oder am Verbinden")

        self.state = ConnectionState.CONNECTING
        try:
            self.socket = socket.socket()
            self.socket.connect((host, port))
            self.state = ConnectionState.CONNECTED
        except Exception:
            self.state = ConnectionState.DISCONNECTED
            raise

    def close(self):
        if self.socket:
            self.socket.close()
        self.state = ConnectionState.CLOSED

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

# SICHER: Context-Manager für ordnungsgemäßen Lebenszyklus
class SafeDatabase:
    def __init__(self, connstr):
        self.connstr = connstr
        self.connection = None
        self.cursor = None

    def connect(self):
        self.connection = psycopg2.connect(self.connstr)
        self.cursor = self.connection.cursor()
        return self

    def query(self, sql, params=None):
        if not self.cursor:
            raise RuntimeError("Nicht verbunden - connect() zuerst aufrufen")
        self.cursor.execute(sql, params)
        return self.cursor.fetchall()

    def close(self):
        if self.cursor:
            self.cursor.close()
        if self.connection:
            self.connection.close()

    def __enter__(self):
        return self.connect()

    def __exit__(self, *args):
        self.close()

# SICHER: Context-Manager verwenden
def process_database():
    with SafeDatabase("host=localhost") as db:
        results = db.query("SELECT * FROM users")
        # db automatisch geschlossen nach Block

# SICHER: Session mit Zustandsvalidierung
class SafeSession:
    def __init__(self):
        self.user = None
        self.permissions = None
        self.authenticated = False

    def authenticate(self, username, password):
        if verify_credentials(username, password):
            self.user = load_user(username)
            self.permissions = load_permissions(username)
            self.authenticated = True
            return True
        return False

    def get_user_data(self):
        # SICHER: Zustand validieren
        if not self.authenticated:
            raise RuntimeError("Session nicht authentifiziert")
        return {
            'username': self.user.username,
            'permissions': self.permissions
        }
// SICHER: Java mit ordnungsgemäßem Lebenszyklus-Management
public class SafeLifecycle implements AutoCloseable {

    private Connection connection;
    private PreparedStatement statement;
    private boolean connected = false;

    public void connect(String url) throws SQLException {
        connection = DriverManager.getConnection(url);
        connected = true;
    }

    public ResultSet query(String sql) throws SQLException {
        // SICHER: Zustand validieren
        if (!connected || connection == null) {
            throw new IllegalStateException("Nicht verbunden");
        }
        statement = connection.prepareStatement(sql);
        return statement.executeQuery();
    }

    // SICHER: Vor Schließen verarbeiten
    public void processAndClose() throws SQLException {
        try {
            ResultSet rs = statement.executeQuery();
            while (rs.next()) {
                process(rs);
            }
        } finally {
            close();
        }
    }

    @Override
    public void close() throws SQLException {
        connected = false;
        if (statement != null) {
            statement.close();
        }
        if (connection != null) {
            connection.close();
        }
    }
}

// SICHER: Builder mit Validierung
public static class SafeBuilder {
    private String host;
    private int port;
    private Config config;
    private boolean hostSet = false;
    private boolean portSet = false;

    public SafeBuilder host(String h) {
        this.host = h;
        this.hostSet = true;
        return this;
    }

    public SafeBuilder port(int p) {
        this.port = p;
        this.portSet = true;
        return this;
    }

    public SafeBuilder config(Config c) {
        this.config = c;
        return this;
    }

    public Connection build() throws IllegalStateException {
        // SICHER: Validieren dass alle erforderlichen Felder gesetzt sind
        if (!hostSet || !portSet) {
            throw new IllegalStateException("Host und Port erforderlich");
        }

        Config c = config != null ? config : Config.defaults();
        return new Connection(host, port, c);
    }
}

// SICHER: 'this' während Konstruktion nicht veröffentlichen
public class SafeDuringConstruction {

    private final List<String> items = new ArrayList<>();

    public SafeDuringConstruction() {
        // Keine Threads im Konstruktor starten
    }

    // Factory-Methode für ordnungsgemäße Initialisierung
    public static SafeDuringConstruction create() {
        SafeDuringConstruction instance = new SafeDuringConstruction();
        instance.startWorker();  // Nach Konstruktion abgeschlossen
        return instance;
    }

    private void startWorker() {
        Thread workerThread = new Thread(this::processItems);
        workerThread.start();
    }
}
// SICHER: JavaScript mit ordnungsgemäßem Lebenszyklus
class SafeService {
    constructor() {
        this.db = null;
        this.state = 'uninitialized';
    }

    async initialize() {
        this.state = 'initializing';
        this.db = await connectDatabase();
        this.state = 'ready';
    }

    async query(sql) {
        // SICHER: Zustand validieren
        if (this.state !== 'ready') {
            throw new Error(`Service nicht bereit: ${this.state}`);
        }
        return await this.db.query(sql);
    }

    async dispose() {
        if (this.db) {
            await this.db.close();
            this.db = null;
        }
        this.state = 'disposed';
    }

    // Factory-Methode stellt Initialisierung sicher
    static async create() {
        const service = new SafeService();
        await service.initialize();
        return service;
    }
}

// SICHER: WebSocket mit Zustandsbehandlung
class SafeWebSocket {
    constructor(url) {
        this.ws = new WebSocket(url);
        this.ready = false;
        this.queue = [];

        this.ws.onopen = () => {
            this.ready = true;
            // Warteschlangennachrichten senden
            this.queue.forEach(msg => this.ws.send(msg));
            this.queue = [];
        };
    }

    send(message) {
        const data = JSON.stringify(message);
        if (this.ready && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(data);
        } else {
            // Für wenn bereit in Warteschlange stellen
            this.queue.push(data);
        }
    }
}

// SICHER: Async-Initialisierung mit Factory
class SafeAsync {
    constructor() {
        this.data = null;
    }

    async init() {
        this.data = await fetchData();
    }

    process() {
        if (!this.data) {
            throw new Error('Nicht initialisiert');
        }
        return this.data.map(item => item.value);
    }

    static async create() {
        const instance = new SafeAsync();
        await instance.init();
        return instance;
    }
}

Ausgenutzt in der Praxis

Use-After-Close-Exploits

Datenbankverbindungen nach Schließen verwendet, was Datenkorruption verursacht.

Pre-Initialisierungs-Umgehung

Sicherheitsprüfungen umgangen durch Verwendung von Ressourcen vor Initialisierung.

Double-Free via Lebenszyklus

Speicherkorruption durch fehlerhaftes Lebenszyklus-Management.


Werkzeuge zum Testen/Ausnutzen

  • Zustandsmaschinen-Testframeworks.

  • Statische Analyse für Lebenszyklus-Verletzungen.

  • Fuzzing mit Lebenszyklus-bewussten Orakeln.


CVE-Beispiele

  • CVE-2019-11477: TCP-Ressourcen-Lebenszyklus-Probleme (SACK-Panik).

  • CVE-2020-12654: Verwendung nicht initialisierter Wireless-Treiber-Strukturen.


Referenzen

  1. MITRE. "CWE-666: Operation on Resource in Wrong Phase of Lifetime." https://cwe.mitre.org/data/definitions/666.html

  2. Zustandsmaschinen-Designmuster.