مقاله فنی

ایزوله‌سازی کدک‌های تصویر PDF در فرآیند کارگر با HotPDF

HotPDF می‌تواند سه فیلتر تصویری پرریسک PDF، یعنی DCTDecode، JPXDecode و JBIG2Decode، را درون یک فرآیند کارگر مجزا و کوتاه‌عمر رمزگشایی کند، به‌جای اینکه این کار را درون برنامه شما انجام دهد. ویژگی‌ای که این قابلیت را فعال می‌کند CodecIsolationMode است، و اثر عملی آن این است که یک کداستریم JPEG 2000 معیوب که پیش از این برنامه VCL شما را از کار می‌انداخت، اکنون فقط یک فرآیند فرزند یک‌بارمصرف را می‌کشد در حالی که میزبان یک کد وضعیت گزارش می‌دهد و به کار خود ادامه می‌دهد

این تفاوت دقیقاً همان‌جاهایی اهمیت دارد که فایل‌های PDF واقعاً از آنجا می‌رسند: یک فرم آپلود، یک دروازه ایمیل، یک دستگاه اسکن، یک محل بارگذاری FTP یک شریک تجاری. شما بر آن بایت‌ها کنترلی ندارید، و کدک‌های تصویر همان‌جایی هستند که آسیب‌های تاریخی زندگی می‌کنند

چرا یک تصویر خراب کل برنامه را از پا در می‌آورد؟

چون کدک تصویر تنها بخشی از یک خواننده PDF است که یک ماشین حالت پیچیده را روی داده‌ای که مهاجم کنترل می‌کند اجرا می‌کند، تقریباً بدون هیچ بررسی ساختاری باقی‌مانده‌ای برای تکیه‌کردن. تا زمانی که بایت‌ها به رمزگشای JPEG 2000 یا JBIG2 می‌رسند، جدول ارجاع متقابل تجزیه شده، شیء حل شده، زنجیره فیلتر باز شده، و آنچه باقی می‌ماند یک کداستریم خام است که می‌گوید چند کاشی، چند مؤلفه، چند بیت به ازای هر نمونه. یک عدد اشتباه در آنجا یک خطای تجزیه نیست. آن یک اندازه تخصیص نادرست یا یک اندیس خارج از محدوده درون یک حلقه رمزگشایی فشرده است

سقف‌های بودجه کمک می‌کنند، و باید از پیش داشته باشیدشان. HotPDF گسترش را با DecodeBudgetBytes و DocumentDecodeBudgetBytes محدود می‌کند، و زنجیره‌های فیلتر را با DecodeFilterLimit و DecodePipelineDepthLimit محدود می‌کند؛ استدلال پشت این سقف‌ها در رمزگشایی محدود برای فیلترهای تودرتو و بمب‌های PDF پوشش داده شده است. اما یک بودجه بایتی فقط به یک سؤال پاسخ می‌دهد، اینکه چه مقدار خروجی مجاز است. این نمی‌تواند پاسخ دهد که وقتی رمزگشا پیش از تولید هرگونه خروجی خطا می‌دهد چه اتفاقی می‌افتد. یک نقض دسترسی درون یک حلقه رمزگشایی، یک نقض سیاست نیست که بتوانید رد کنید؛ آن یک رویداد در سطح فرآیند است، و تنها مهار قابل‌اتکا برای یک رویداد در سطح فرآیند، یک فرآیند دیگر است

HotPDF چه چیزی را ایزوله می‌کند، و چه چیزی را نمی‌کند

HotPDF دقیقاً سه نوع کدک را ایزوله می‌کند، که در یونیت HPDFCodecIsolation به‌صورت hckDCT، hckJPX و hckJBIG2 فهرست شده‌اند. هرچیز دیگر، یعنی Flate، LZW، RunLength، ASCII85، CCITT، درون فرآیند باقی می‌ماند، چون آن رمزگشاها به‌اندازه کافی ساده هستند که با بودجه محدود شوند و جایی نیستند که خرابی‌های جالب‌توجه از آنجا سرچشمه می‌گیرند

مسیر انتقال عمداً باریک است. میزبان یک نگاشت حافظه اشتراکی محدود تخصیص می‌دهد، یک THPDFCodecSharedHeader ثابت به‌همراه ورودی فشرده و هر بخش سراسری JBIG2 می‌نویسد، کارگر را راه‌اندازی می‌کند، و منتظر می‌ماند. کارگر پیکسل‌های رمزگشایی‌شده را به همان نگاشت بازمی‌نویسد و یک کلمه وضعیت تنظیم می‌کند. هیچ پروتکل pipeای وجود ندارد که از هماهنگی خارج شود، هیچ قالب سریال‌سازی‌ای برای فازینگ وجود ندارد، و سرآیند یک مقدار جادویی و یک نسخه حمل می‌کند، بنابراین یک باینری کارگر نامتناظر رد می‌شود نه اینکه به‌اشتباه خوانده شود

uses
  HPDFDoc, HPDFCodecIsolation;

var
  Pdf: THotPDF;
  Info: THPDFCodecWorkerInfo;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    // شکست بسته: هرگز این کدک‌ها را درون فرآیند رمزگشایی نکن
    Pdf.CodecIsolationMode := cimRequired;
    Pdf.CodecWorkerExecutable := 'HotPDFCodecWorker.exe';
    Pdf.CodecWorkerTimeoutMilliseconds := 5000;       // 1..600000
    Pdf.CodecWorkerMemoryLimitBytes := 268435456;     // 0 یا حداقل 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;

CodecWorkerExecutable را خالی بگذارید و HotPDF کارگر را در کنار فایل اجرایی خودتان، به‌صورت HotPDFCodecWorker.exe در پوشه ParamStr(0)، پیدا می‌کند. وقتی استقرار شما کارگر را جای دیگری قرار می‌دهد، آن را به‌طور صریح تنظیم کنید؛ مقدار از طریق ExpandFileName بسط داده می‌شود، بنابراین یک مسیر نسبی نسبت به پوشه جاری حل می‌شود نه پوشه برنامه، که به‌ندرت چیزی است که روی یک سرویس می‌خواهید

خودکار یا اجباری: کدام نوع خرابی را ترجیح می‌دهید؟

سه مقدار THPDFCodecIsolationMode سه پاسخ متفاوت به یک سؤال را رمزگذاری می‌کنند: وقتی کارگر اصلاً نمی‌تواند اجرا شود چه باید اتفاق بیفتد. cimDisabled کاملاً ایزوله‌سازی را نادیده می‌گیرد و درون فرآیند رمزگشایی می‌کند، همان رفتار پیش از نسخه 3.x. cimAutomatic، پیش‌فرض، کارگر را امتحان می‌کند و وقتی فایل اجرایی کارگر گم است یا اجرا نمی‌شود، به‌طور بی‌صدا به رمزگشایی درون فرآیند بازمی‌گردد، که به‌صورت وضعیت cwsUnavailable گزارش می‌شود. cimRequired این بازگشت را رد می‌کند: یک کارگر ناموجود، رمزگشایی را رسیدگی‌شده و شکست‌خورده علامت می‌زند، بنابراین هیچ کداستریم نامعتبری هرگز به فضای آدرس شما نمی‌رسد

بر اساس مدل تهدید انتخاب کنید، نه بر اساس راحتی. یک نمایشگر دسکتاپ که اسنادی را باز می‌کند که کاربر از پیش روی دیسک دارد، روی cimAutomatic مشکلی ندارد، جایی که یک کارگر گمشده به رفتار کلاسیک تنزل می‌کند به‌جای شکستن محصول. یک سرویس دریافتی که فایل‌ها را از اینترنت تجزیه می‌کند باید cimRequired را اجرا کند، چون یک اشتباه استقراری که به‌آرامی لایه ایزوله‌سازی را حذف می‌کند دقیقاً از آن نوع رگرسیون‌هایی است که کسی تا زمانی که اهمیت پیدا کند متوجه آن نمی‌شود. به این عدم‌تقارن توجه کنید: فقط cwsUnavailable بازگشت را فعال می‌کند. کارگری که راه‌اندازی شده و سپس سقوط کرده، به مهلت زمانی رسیده، یا به یک سقف برخورد کرده، در هر دو حالت یک شکست رمزگشایی است، هرگز یک تلاش مجدد بی‌صدا درون فرآیند نیست

خواندن نتیجه از THPDFCodecWorkerStatus

GetLastCodecWorkerInfo نتیجه آخرین رمزگشایی ایزوله‌شده را بازمی‌گرداند، و شمارش وضعیت آنقدر مشخص است که بتواند تصمیمات عملیاتی واقعی را هدایت کند، نه یک خط لاگ عمومی «تصویر شکست خورد». مقادیر عبارت‌اند از cwsNotRun، cwsSucceeded، cwsUnavailable، cwsLaunchFailed، cwsTimedOut، cwsCrashed، cwsDecodeFailed، cwsProtocolError و cwsOutputLimit

با آن‌ها به‌عنوان سه گروه رفتار کنید. مشکلات استقرار cwsUnavailable و cwsLaunchFailed هستند: کسی بدون کارگر عرضه کرده، یا یک محصول آنتی‌ویروس مانع ایجاد فرآیند می‌شود. مشکلات سند cwsDecodeFailed و cwsOutputLimit هستند: فایل معیوب است یا از سیاست شما بزرگ‌تر، و رد آن پاسخ درست است. گروه جالب‌توجه cwsTimedOut و cwsCrashed است، چون این‌ها رویدادهایی هستند که پیش از این فرآیند میزبان را معلق یا از کار می‌انداختند. وقتی این اتفاق می‌افتد، فیلدهای همراه ProcessId، ExitCode و ElapsedMilliseconds به‌اندازه کافی به شما می‌دهند تا با یک مدخل Windows Error Reporting همبستگی برقرار کنید و تصمیم بگیرید که آیا یک فایل مشتری بیمارگونه است یا کسی در حال کاوش شماست

procedure LogCodecOutcome(const Info: THPDFCodecWorkerInfo);
begin
  case Info.Status of
    cwsSucceeded:
      ; // چیزی برای گزارش نیست
    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;

سقف‌هایی که واقعاً اعمال می‌شوند

سه سقف جداگانه روی هر رمزگشایی ایزوله‌شده اعمال می‌شود، و دانستن اینکه کدام‌یک فعال شده یک بعدازظهر حدس‌زدن را صرفه‌جویی می‌کند. CodecWorkerTimeoutMilliseconds پیش‌فرض 10000 دارد و در بازه 1 تا 600000 اعتبارسنجی می‌شود؛ مقداری خارج از آن به‌جای محدودسازی بی‌صدا، خطا صادر می‌کند. CodecWorkerMemoryLimitBytes پیش‌فرض 536870912 بایت دارد و باید یا صفر باشد، به‌معنای بدون سقف، یا حداقل 67108864 بایت، چون یک سقف کوچک‌تر نمی‌تواند یک مجموعه کاری واقع‌بینانه رمزگشا را در خود جای دهد و هر سندی را شکست می‌دهد. سقف حافظه توسط یک Windows Job Object با معنایی kill-on-close اعمال می‌شود، بنابراین کارگر حتی اگر میزبان به‌طور ناگهانی خاتمه یابد، همراه با job می‌میرد

سومین سقف، سقف خروجی است، و آن به‌جای پیکربندی‌شدن، مشتق‌شده است. HotPDF بایت‌های لازم را از ناحیه درخواستی، یا از هندسه تصویر مورد انتظار، به‌صورت عرض ضربدر ارتفاع ضربدر سه برای خروجی 24 بیتی محاسبه می‌کند، سپس وقتی یک بودجه تنظیم شده آن مقدار را تا DecodeBudgetBytes محدود می‌کند. رمزگشایی که یک سرآیند باورپذیر گزارش می‌کند و سپس تلاش می‌کند پیکسل‌های به‌مراتب بیشتری از آنچه هندسه اجازه می‌دهد صادر کند، توسط خود نگاشت متوقف می‌شود، و میزبان cwsOutputLimit را می‌بیند. به همین دلیل است که لایه ایزوله‌سازی و بودجه رمزگشایی مکمل یکدیگرند: بودجه تعیین می‌کند یک تصویر چقدر بزرگ می‌تواند باشد، و مرز ایزوله‌سازی مطمئن می‌شود که یک دروغ درباره آن اندازه نمی‌تواند به یک نوشتن خارج از محدوده درون فرآیند شما تبدیل شود

جایگاه این قابلیت در یک مسیر دریافت سخت‌شده

ایزوله‌سازی فرآیند بیرونی‌ترین لایه یک زنجیره دفاعی است که خیلی زودتر آغاز می‌شود. سقف‌های ساختاری اسناد غیرباورپذیر را در زمان تجزیه رد می‌کنند. بودجه‌های فیلتر گسترش را محدود می‌کنند. ایزوله‌سازی آنچه از هر دو جان سالم به‌در می‌برد را مهار می‌کند. برای اسنادی که به لایه تصویر می‌رسند، ارزش دارد بدانید واقعاً کدام کدک را به‌کار می‌گیرید، چون مدیریت JPXDecode و دیکشنری‌های نماد JBIG2 نمایه‌های شکست بسیار متفاوتی دارند، و JBIG2 به‌طور خاص بخش‌های سراسری بین‌صفحه‌ای را حمل می‌کند که یک sandbox ساده‌لوحانه بر مبنای هر تصویر آن را می‌شکند

هزینه این کار صادقانه است و ارزش گفتن دارد: راه‌اندازی یک فرآیند به ازای هر تصویر ایزوله‌شده میلی‌ثانیه اضافه می‌کند، و یک سند با صدها صفحه اسکن‌شده آن را احساس خواهد کرد. آن را در برابر آنچه به‌دست می‌آورید بسنجید. روی یک مبدل دسته‌ای که بدون نظارت شبانه اجرا می‌شود، افت توان عملیاتی نامرئی است و مهار سقوط تمام هدف است. روی یک نمایشگر تعاملی که اسنادی را باز می‌کند که کاربر از پیش به آن‌ها اعتماد دارد، cimDisabled یا cimAutomatic پیش‌فرض معقولی است. این حالت یک ویژگی ساده است، پس چیزی مانع نمی‌شود که آن را برای هر رده سند به‌صورت جداگانه در زمان اجرا انتخاب کنید

HotPDF لایه ایزوله‌سازی، بودجه‌های رمزگشایی و سقف‌های تجزیه‌گر ساختاری را به‌صورت یک کامپوننت بومی VCL برای Delphi و C++Builder عرضه می‌کند، بدون هیچ زمان‌اجرای خارجی برای استقرار به‌جز خود فایل اجرایی کارگر. مستندات کامل API و یک نسخه آزمایشی در صفحه کامپوننت PDF Delphi HotPDF موجود است