Kerentanan Use-After-Free (UAF) pada parsing kriptografi format PKCS#7/CMS (Cryptographic Message Syntax) umumnya berakar dari manajemen lifecycle pointer internal yang tidak sinkron saat verifikasi gagal di tengah jalan. Ketika fungsi seperti turunan PKCS7_verify() memproses signer info, parser mengekstrak struktur bersarang seperti sertifikat X.509, atribut terotentikasi, dan rantai signer ke dalam heap. Jika input tidak valid memicu pembebasan memori parsial tanpa mengosongkan referensi pointer di konteks pemanggil, operasi lanjutan akan mengakses heap yang telah dilepas.

Akar Masalah: Lifecycle Pointer SignerInfo dan Asymmetric Free

Struktur PKCS#7 memuat array atau stack dari penanda tangan (PKCS7_SIGNER_INFO). Lifecycle pointer elemen-elemen ini sering kali terikat langsung pada struktur induk PKCS7. Kerentanan muncul dari pola destruksi asimetris:

  • Parser mengalokasikan konteks internal untuk memvalidasi atribut signer.
  • Jika salah satu signature signer gagal diverifikasi atau payload ASN.1 rusak, fungsi parsing membebaskan memori PKCS7_SIGNER_INFO terkait.
  • Struktur konteks tingkat atas masih menyimpan referensi raw pointer ke alamat memori yang sudah di-free tersebut.
  • Pemanggil (caller) kemudian mengeksekusi fungsi cleanup global seperti PKCS7_free() pada struktur kontainer, menghasilkan kondisi double-free atau use-after-free saat mencoba membaca pointer dangling.

Boundary Validation saat Parsing Payload ASN.1/DER

Sebelum memanggil fungsi verifikasi kriptografi tingkat tinggi, input buffer wajib melewati validasi batas (boundary validation) eksplisit. Payload DER/BER tidak tepercaya dapat direkayasa dengan panjang field negatif, nested depth berlebih, atau offset pointer melampaui batas buffer.

Prinsip utama boundary validation PKCS#7 meliputi:

  1. Strict Buffer Bounds: Jangan pernah mengasumsikan parser internal memvalidasi panjang buffer secara atomik. Verifikasi bahwa total panjang DER sesuai dengan ukuran data aktual sebelum parsing.
  2. Validasi Jumlah Signer: Tolak payload yang melaporkan 0 signer atau signer info yang melampaui limit wajar sistem untuk mencegah de-alokasi loop tak terduga.
  3. Single-Ownership Pass-Through: Jangan menduplikasi raw pointer dari koleksi signer internal ke variabel lokal tanpa meningkatkan reference count objek tersebut.

Pola Implementasi C Aman: Ownership Eksplisit dan Pembersihan Aman

Kode di bawah ini mengilustrasikan mitigasi UAF menggunakan pola ownership eksplisit, penataan cleanup terpusat (RAII style via idiom goto cleanup), serta penanganan error tanpa double-free.

#include <stdio.h>
#include <stdlib.h>
#include <openssl/pkcs7.h>
#include <openssl/x509.h>
#include <openssl/bio.h>

typedef enum {
    VERIFY_SUCCESS = 0,
    VERIFY_ERR_PARSE = 1,
    VERIFY_ERR_NO_SIGNERS = 2,
    VERIFY_ERR_FAILED = 3
} verify_result_t;

verify_result_t verify_pkcs7_safe(const unsigned char *der_bytes, size_t der_len, X509_STORE *ca_store) {
    BIO *in_bio = NULL;
    PKCS7 *p7 = NULL;
    STACK_OF(PKCS7_SIGNER_INFO) *signers = NULL;
    verify_result_t result = VERIFY_ERR_FAILED;

    if (!der_bytes || der_len == 0 || !ca_store) {
        return VERIFY_ERR_PARSE;
    }

    /* Inisialisasi BIO dengan batas memori ketat */
    in_bio = BIO_new_mem_buf(der_bytes, (int)der_len);
    if (!in_bio) {
        return VERIFY_ERR_PARSE;
    }

    /* Parse struktur PKCS7; p7 memiliki kepemilikan seluruh subtree ASN.1 */
    p7 = d2i_PKCS7_bio(in_bio, NULL);
    if (!p7) {
        result = VERIFY_ERR_PARSE;
        goto cleanup;
    }

    /* Pastikan payload bertipe Signed Data */
    if (!PKCS7_type_is_signed(p7)) {
        result = VERIFY_ERR_PARSE;
        goto cleanup;
    }

    /* Ekstraksi stack signer - p7 tetap bertindak sebagai owner tunggal */
    signers = PKCS7_get_signer_info(p7);
    if (!signers || sk_PKCS7_SIGNER_INFO_num(signers) <= 0) {
        result = VERIFY_ERR_NO_SIGNERS;
        goto cleanup;
    }

    /* Validasi signature tanpa mengekspos pointer internal ke free parsial */
    if (PKCS7_verify(p7, NULL, ca_store, NULL, NULL, PKCS7_NOINTERN) != 1) {
        result = VERIFY_ERR_FAILED;
        goto cleanup;
    }

    result = VERIFY_SUCCESS;

cleanup:
    /* 
     * Pembersihan deterministik: 
     * Jangan pernah memanggil PKCS7_SIGNER_INFO_free pada elemen di dalam STACK,
     * karena PKCS7_free akan membersihkan seluruh graph memori secara rekursif.
     */
    signers = NULL; /* Hilangkan dangling pointer referensial */

    if (p7) {
        PKCS7_free(p7);
        p7 = NULL;
    }
    if (in_bio) {
        BIO_free(in_bio);
        in_bio = NULL;
    }

    return result;
}

Aturan Penanganan Dangling Pointer

Guna mencegah pointer liar setelah de-alokasi atau parsing error:

  • Zeroing After Free: Selalu atur pointer ke NULL setelah memanggil fungsi dealokasi. Pola ini mencegah de-referensi atau eksekusi free() berulang pada blok memori yang sama.
  • Prinsip Owner Tunggal: Jika fungsi eksternal mengambil referensi struktur internal PKCS#7 (misalnya sertifikat leaf via PKCS7_get0_signers), perlakukan objek tersebut sebagai borrowed reference. Jangan mendealokasikannya secara independen tanpa memutus tautannya dari hierarki induk via sk_pop atau modul ref counting (X509_up_ref).

Deteksi Dinamis Menggunakan AddressSanitizer (ASan) pada CI

Analisis statis sering kali gagal melacak perubahan state lifecycle pointer di dalam loop ASN.1. Pipeline pengujian harus mengompilasi parser PKCS#7/CMS dengan AddressSanitizer untuk mendeteksi memory corruption secara presisi pada runtime.

Gunakan konfigurasi build berikut:

# Kompilasi parser dengan instrumentation ASan dan penanganan pointer dangling
clang -fsanitize=address,undefined -fno-omit-frame-pointer -g -O1 \
    -I/usr/include/openssl test_pkcs7_parser.c -lcrypto -o test_parser

# Eksekusi unit test dengan leak detection diaktifkan
export ASAN_OPTIONS="detect_leaks=1:abort_on_error=1:strict_string_checks=1"
./test_parser

Tambahkan target fuzzer pada CI menggunakan LLVMFuzzerTestOneInput yang dihubungkan dengan libFuzzer untuk memasukkan mutasi DER acak ke fungsi parser. Kombinasi fuzz testing dan ASan akan mengidentifikasi crash dereferensi pointer bebas (UAF) seketika sebelum kode dideploy ke lingkungan produksi.