Batasan Memori dan Risiko Direct Syscall Wine ke Linux

Memanggil pustaka Linux native langsung dari proses Wine (Windows PE) secara teoritis dimungkinkan menggunakan trik transisi ABI, seperti manipulasi context switch atau bridging manual via NT syscall wrapper. Namun, pendekatan direct syscall bridge membawa risiko stabilitas dan keamanan yang signifikan:

  • Inkompatibilitas Calling Convention dan ABI: Windows x86_64 menggunakan skema RCX, RDX, R8, R9 dengan alokasi shadow stack 32-byte. Sebaliknya, System V AMD64 ABI pada Linux menggunakan RDI, RSI, RDX, RCX, R8, R9 tanpa shadow space wajib. Inkonsistensi register dan stack alignment rentan memicu general protection fault.
  • Konflik Manajemen Memori dan Threading: Wine memetakan Thread Environment Block (TEB) melalui register %gs, sedangkan Linux glibc mengelola Thread Local Storage (TLS) via %fs. Pertukaran pointer raw antar runtime berpotensi merusak internal state libc atau allocator ntdll.
  • Keamanan Trust Boundary: Memberikan akses direct memory sharing melanggar prinsip isolasi proses. Kerusakan memori di layer Wine langsung mengeksekusi crash pada daemon host.

Solusi yang teruji adalah memisahkan runtime ke dalam model client-server independen menggunakan Unix Domain Socket (UDS) melalui abstraksi Windows Sockets (Winsock) di dalam Wine.

Arsitektur IPC Wine-ke-Linux Berbasis Unix Domain Socket

Windows 10 (build 17063 ke atas) mendukung AF_UNIX native melalui Winsock2 via afunix.h. Wine mengimplementasikan mapping AF_UNIX langsung ke socket Linux lokal di filesystem. Mekanisme ini menyediakan throughput mendekati shared-memory tanpa risiko manipulasi pointer lintas ABI.

+-----------------------+              +-----------------------+
|      Wine Client      |              |      Host Worker      |
|   (Windows PE Exec)   |              |     (Linux Native)    |
|                       |              |                       |
|  +-----------------+  |              |  +-----------------+  |
|  | Winsock AF_UNIX |  |              |  | Linux AF_UNIX   |  |
|  +--------+--------+  |              |  +--------^--------+  |
+-----------|-----------+              +-----------|-----------+
            |                                      |
            +---> [/tmp/wine_ipc.sock] (UDS) ------+

Desain Kontrak Protokol dan Serialisasi Biner

Gunakan format fixed-header length-prefixed untuk mencegah parsing ambiguity pada aliran byte stream. Setiap pesan menyertakan magic bytes, message ID acak untuk korelasi idempotency, command identifier, serta panjang payload (uint32).

#pragma pack(push, 1)
typedef struct {
    uint32_t magic;      // 0x57494E45 ('WINE')
    uint64_t msg_id;     // ID unik request untuk tracking idempotent
    uint16_t cmd;        // ID Operasi (misal: 0x01 = EXEC_TASK)
    uint32_t length;     // Panjang payload yang mengikuti
} ipc_header_t;
#pragma pack(pop)

Implementasi Client (Wine / C++) dengan Timeout & Retry

Client harus memvalidasi header pesan, menerapkan timeout operasi, dan mengisolasi loop koneksi ulang ketika worker Linux mengalami crash atau restart.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <afunix.h>
#include <cstdint>
#include <vector>
#include <chrono>
#include <thread>

#pragma comment(lib, "ws2_32.lib")

static const uint32_t PROTOCOL_MAGIC = 0x57494E45;
static const char* SOCKET_PATH = "Z:\\tmp\\wine_ipc.sock"; // Path host di Wine

SOCKET connect_with_retry(const char* path, int max_retries, int delay_ms) {
    SOCKET sock = INVALID_SOCKET;
    sockaddr_un addr{};
    addr.sun_family = AF_UNIX;
    strncpy_s(addr.sun_path, path, sizeof(addr.sun_path) - 1);

    for (int attempt = 0; attempt < max_retries; ++attempt) {
        sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sock == INVALID_SOCKET) break;

        if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == 0) {
            DWORD timeout = 5000; // 5s timeout
            setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
            setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
            return sock;
        }

        closesocket(sock);
        std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
    }
    return INVALID_SOCKET;
}

bool send_exact(SOCKET sock, const uint8_t* buffer, size_t size) {
    size_t total_sent = 0;
    while (total_sent < size) {
        int sent = send(sock, (const char*)(buffer + total_sent), size - total_sent, 0);
        if (sent <= 0) return false;
        total_sent += sent;
    }
    return true;
}

bool recv_exact(SOCKET sock, uint8_t* buffer, size_t size) {
    size_t total_read = 0;
    while (total_read < size) {
        int bytes = recv(sock, (char*)(buffer + total_read), size - total_read, 0);
        if (bytes <= 0) return false;
        total_read += bytes;
    }
    return true;
}

Implementasi Host Daemon (Linux / C++) dengan Validasi Trust Boundary

Worker host memvalidasi integritas data sebelum memproses pesan guna memitigasi buffer overrun atau deserialization vulnerability dari input client.

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstdint>
#include <vector>
#include <iostream>

static const size_t MAX_PAYLOAD_SIZE = 1024 * 1024; // Limit 1MB per pesan

bool process_client_request(int client_fd) {
    ipc_header_t header;
    ssize_t bytes = read(client_fd, &header, sizeof(header));
    if (bytes != sizeof(header)) return false;

    // Validasi Trust Boundary
    if (header.magic != 0x57494E45) {
        std::cerr << "Magic bytes mismatch. Drop connection.\n";
        return false;
    }

    if (header.length > MAX_PAYLOAD_SIZE) {
        std::cerr << "Payload melebihi ambang batas aman: " << header.length << "\n";
        return false;
    }

    std::vector<uint8_t> payload(header.length);
    size_t total_read = 0;
    while (total_read < header.length) {
        ssize_t r = read(client_fd, payload.data() + total_read, header.length - total_read);
        if (r <= 0) return false;
        total_read += r;
    }

    // Eksekusi logic idempotent berdasarkan header.msg_id di sini...
    return true;
}

Strategi Penanganan Kegagalan: Idempotensi dan Debugging

  • Deteksi Connection Reset: Ketika worker crash, panggilan send() berikutnya di Wine menghasilkan error WSAECONNRESET (10054) atau WSAECONNABORTED (10053). Client harus menangani kode error ini secara eksplisit dan menutup socket lama sebelum memicu rekoneksi.
  • At-Least-Once Delivery & Idempotensi: Jika koneksi terputus saat request sedang diproses di host, client akan mengirim ulang pesan dengan msg_id yang sama. Daemon host wajib menyimpan cache LRU berisi msg_id yang sudah tereksekusi beserta hasilnya untuk menghindari eksekusi ganda pada aksi non-idempotent.
  • Validasi File Socket Permission: Socket file di Linux (misal: /tmp/wine_ipc.sock) harus memiliki permission yang tepat (misal: chmod 0600 atau 0660) agar proses Wine di bawah user yang sama dapat membaca dan menulis socket tanpa kendala EACCES.
  • Verifikasi Socket Wine: Verifikasi path mapping Wine ke Linux. Direktori /tmp di Linux umumnya dapat diakses di Wine melalui Z:\tmp. Pastikan symlink drive Z: mengarah ke root filesystem host.