Artikel Teknis

Isolasi Codec Gambar PDF di Worker Process dengan HotPDF

HotPDF dapat mendekode tiga filter gambar PDF paling berisiko, yaitu DCTDecode, JPXDecode, dan JBIG2Decode, di dalam worker process terpisah yang berumur pendek, bukan di dalam aplikasi Anda. Properti yang mengaktifkan ini adalah CodecIsolationMode, dan efek praktisnya adalah codestream JPEG 2000 yang cacat, yang sebelumnya akan membuat aplikasi VCL Anda crash, sekarang hanya mematikan child process yang bisa dibuang sementara host melaporkan status code dan tetap berjalan

Perbedaan ini paling terasa di tempat PDF sebenarnya berasal: form upload, mail gateway, alat pemindai, atau folder FTP mitra. Anda tidak mengendalikan byte-byte tersebut, dan codec gambar adalah tempat kerusakan historis biasa terjadi

Mengapa satu gambar buruk bisa menjatuhkan seluruh aplikasi?

Karena codec gambar adalah satu-satunya bagian dari PDF reader yang menjalankan state machine kompleks atas data yang dikendalikan penyerang, nyaris tanpa pemeriksaan struktural lagi sebagai jaring pengaman. Pada saat byte sampai ke decoder JPEG 2000 atau JBIG2, cross-reference table sudah diparsing, object sudah diresolve, filter chain sudah diurai, dan yang tersisa hanyalah codestream mentah yang menyatakan berapa banyak tile, berapa banyak component, dan berapa bit per sample. Angka yang salah di sana bukan error parsing. Itu adalah ukuran alokasi yang salah atau index di luar jangkauan di dalam decode loop yang ketat

Batas budget membantu, dan Anda seharusnya sudah memilikinya. HotPDF membatasi ekspansi dengan DecodeBudgetBytes dan DocumentDecodeBudgetBytes, serta membatasi filter chain dengan DecodeFilterLimit dan DecodePipelineDepthLimit; alasan di balik batas-batas ini dibahas di bounded decoding untuk nested filter dan PDF bomb. Tetapi byte budget hanya menjawab satu pertanyaan, yaitu berapa banyak output yang diizinkan. Ia tidak bisa menjawab apa yang terjadi saat decoder gagal sebelum menghasilkan output sama sekali. Access violation di dalam decode loop bukan pelanggaran kebijakan yang bisa Anda tolak; itu adalah peristiwa level process, dan satu-satunya penahanan yang andal untuk peristiwa level process adalah process yang berbeda

Apa yang diisolasi HotPDF, dan apa yang tidak

HotPDF mengisolasi persis tiga jenis codec, yang dienumerasi sebagai hckDCT, hckJPX, dan hckJBIG2 di unit HPDFCodecIsolation. Semua yang lain, Flate, LZW, RunLength, ASCII85, CCITT, tetap berjalan in-process, karena decoder-decoder itu cukup sederhana untuk dibatasi dengan budget dan bukan tempat asal kegagalan yang menarik untuk dibahas

Transportnya sengaja dibuat sempit. Host mengalokasikan satu shared-memory mapping yang dibatasi, menulis THPDFCodecSharedHeader yang ukurannya tetap beserta input terkompresi dan segmen global JBIG2 apa pun, meluncurkan worker, lalu menunggu. Worker menulis kembali pixel hasil decode ke mapping yang sama dan mengatur status word. Tidak ada protokol pipe yang bisa kehilangan sinkronisasi, tidak ada format serialisasi untuk di-fuzz, dan header membawa nilai magic serta versi sehingga worker binary yang tidak cocok akan ditolak, bukan dibaca secara keliru

uses
  HPDFDoc, HPDFCodecIsolation;

var
  Pdf: THotPDF;
  Info: THPDFCodecWorkerInfo;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    // Fail closed: jangan pernah decode codec ini secara in-process
    Pdf.CodecIsolationMode := cimRequired;
    Pdf.CodecWorkerExecutable := 'HotPDFCodecWorker.exe';
    Pdf.CodecWorkerTimeoutMilliseconds := 5000;       // 1..600000
    Pdf.CodecWorkerMemoryLimitBytes := 268435456;     // 0 atau >= 64 MiB
    Pdf.DecodeBudgetBytes := 134217728;

    if Pdf.LoadFromFile('untrusted-upload.pdf') = 1 then
      if Pdf.GetLoadedImageCount > 0 then
      begin
        Bmp := Pdf.ExtractLoadedImage(0);
        try
          if Pdf.GetLastCodecWorkerInfo(Info) then
            LogCodecOutcome(Info);
        finally
          Bmp.Free;
        end;
      end;
  finally
    Pdf.Free;
  end;
end;

Biarkan CodecWorkerExecutable kosong dan HotPDF akan meresolve worker di sebelah executable Anda sendiri, sebagai HotPDFCodecWorker.exe di direktori ParamStr(0). Atur secara eksplisit saat deployment Anda menempatkan worker di lokasi lain; nilainya diperluas melalui ExpandFileName, sehingga path relatif diresolve terhadap direktori saat ini, bukan direktori aplikasi, yang jarang menjadi yang Anda inginkan pada sebuah service

Automatic atau required: kegagalan mana yang Anda pilih?

Ketiga nilai THPDFCodecIsolationMode mengkodekan tiga jawaban berbeda untuk satu pertanyaan, yaitu apa yang harus terjadi saat worker sama sekali tidak bisa berjalan. cimDisabled melewati isolasi sepenuhnya dan mendecode secara in-process, perilaku pra-3.x. cimAutomatic, nilai default, mencoba worker dan diam-diam jatuh kembali ke decoding in-process saat executable worker hilang atau gagal diluncurkan, yang dilaporkan sebagai status cwsUnavailable. cimRequired menolak fallback tersebut: worker yang tidak tersedia menandai decode sebagai ditangani dan gagal, sehingga tidak ada codestream tidak tepercaya yang pernah mencapai address space Anda

Pilih berdasarkan threat model, bukan kenyamanan. Desktop viewer yang membuka dokumen yang sudah dimiliki pengguna di disk baik-baik saja dengan cimAutomatic, di mana worker yang hilang hanya turun ke perilaku klasik alih-alih merusak produk. Layanan ingestion yang memparsing file dari internet sebaiknya menjalankan cimRequired, karena kesalahan deployment yang diam-diam menghilangkan lapisan isolasi adalah jenis regresi yang tidak disadari siapa pun sampai benar-benar bermasalah. Perhatikan asimetrinya: hanya cwsUnavailable yang memicu fallback. Worker yang berhasil diluncurkan lalu crash, timeout, atau mencapai batas adalah kegagalan decode di kedua mode, tidak pernah menjadi retry diam-diam secara in-process

Membaca hasil akhir dari THPDFCodecWorkerStatus

GetLastCodecWorkerInfo mengembalikan hasil dari decode terisolasi paling akhir, dan enumerasi status ini cukup spesifik untuk mendorong keputusan operasional nyata, bukan sekadar baris log generik "image failed". Nilai-nilainya adalah cwsNotRun, cwsSucceeded, cwsUnavailable, cwsLaunchFailed, cwsTimedOut, cwsCrashed, cwsDecodeFailed, cwsProtocolError, dan cwsOutputLimit

Perlakukan sebagai tiga kelompok. Masalah deployment adalah cwsUnavailable dan cwsLaunchFailed: seseorang merilis tanpa worker, atau produk antivirus memblokir pembuatan process. Masalah dokumen adalah cwsDecodeFailed dan cwsOutputLimit: file cacat atau lebih besar dari yang diizinkan kebijakan Anda, dan menolaknya adalah jawaban yang benar. Kelompok yang menarik adalah cwsTimedOut dan cwsCrashed, karena itu adalah peristiwa yang sebelumnya bisa membuat host process hang atau mati. Saat itu terjadi, field ProcessId, ExitCode, dan ElapsedMilliseconds yang menyertainya memberi Anda cukup informasi untuk mengorelasikan dengan entri Windows Error Reporting dan memutuskan apakah satu file pelanggan memang bermasalah atau ada pihak yang sedang menguji sistem Anda

procedure LogCodecOutcome(const Info: THPDFCodecWorkerInfo);
begin
  case Info.Status of
    cwsSucceeded:
      ; // tidak ada yang perlu dilaporkan
    cwsUnavailable, cwsLaunchFailed:
      Alert('Codec worker not deployed: ' + Info.ErrorMessage);
    cwsTimedOut, cwsCrashed:
      Quarantine(Format('pid %d exit %d after %d ms',
        [Info.ProcessId, Info.ExitCode, Info.ElapsedMilliseconds]));
  else
    RejectDocument(Info.ErrorMessage);
  end;
end;

Batas yang benar-benar berlaku

Tiga batas atas terpisah berlaku untuk setiap decode terisolasi, dan mengetahui mana yang terpicu menghemat waktu berjam-jam menebak-nebak. CodecWorkerTimeoutMilliseconds defaultnya 10.000 dan divalidasi ke rentang 1 hingga 600.000; nilai di luar itu akan raise, bukan diclamp secara diam-diam. CodecWorkerMemoryLimitBytes defaultnya 536.870.912 byte dan harus nol, yang berarti tanpa batas, atau minimal 67.108.864 byte, karena batas yang lebih kecil tidak bisa menampung working set decoder yang realistis dan akan menggagalkan setiap dokumen. Batas memori ini diberlakukan oleh Windows Job Object dengan semantik kill-on-close, sehingga worker mati bersama job-nya bahkan jika host dihentikan secara mendadak

Batas atas ketiga adalah output limit, dan nilainya diturunkan, bukan dikonfigurasi. HotPDF menghitung byte yang dibutuhkan dari region yang diminta, atau dari geometri gambar yang diharapkan, sebagai lebar dikali tinggi dikali tiga untuk output 24-bit, lalu meng-clamp nilai itu turun ke DecodeBudgetBytes saat budget diatur. Decoder yang melaporkan header yang tampak masuk akal lalu mencoba mengeluarkan pixel jauh lebih banyak daripada yang diizinkan geometri akan dihentikan oleh mapping itu sendiri, dan host melihat cwsOutputLimit. Inilah sebabnya lapisan isolasi dan decode budget saling melengkapi: budget mendefinisikan seberapa besar gambar boleh berukuran, dan batas isolasi memastikan kebohongan soal ukuran itu tidak bisa berubah menjadi out-of-bounds write di process Anda

Di mana ini cocok dalam intake path yang diperkeras

Isolasi process adalah lapisan terluar dari rantai pertahanan yang dimulai jauh lebih awal. Batas struktural menolak dokumen yang tidak masuk akal saat parsing. Filter budget membatasi ekspansi. Isolasi menahan apa pun yang lolos dari keduanya. Untuk dokumen yang sampai ke lapisan gambar, ada baiknya mengetahui codec mana yang sebenarnya sedang Anda jalankan, karena penanganan JPXDecode dan symbol dictionary JBIG2 memiliki profil kegagalan yang sangat berbeda, dan JBIG2 khususnya membawa segmen global lintas halaman yang akan merusak sandbox per-gambar yang naif

Biayanya jujur dan layak dinyatakan: meluncurkan satu process per gambar terisolasi menambah beberapa milidetik, dan dokumen dengan ratusan halaman hasil scan akan merasakannya. Ukur itu terhadap apa yang didapatkan. Pada batch converter yang berjalan tanpa pengawasan semalaman, hilangnya throughput tidak terlihat dan penahanan crash adalah keseluruhan tujuannya. Pada interactive viewer yang membuka dokumen yang sudah dipercaya pengguna, cimDisabled atau cimAutomatic adalah default yang masuk akal. Mode ini hanyalah properti biasa, jadi tidak ada yang menghalangi Anda memilih per kelas dokumen saat runtime

HotPDF menyediakan lapisan isolasi, decode budget, dan batas structural parser sebagai satu komponen VCL native untuk Delphi dan C++Builder, tanpa runtime eksternal yang perlu di-deploy selain worker executable itu sendiri. Dokumentasi API lengkap dan build trial tersedia di halaman komponen PDF Delphi HotPDF