HotPDF có thể giải mã ba bộ lọc ảnh PDF rủi ro nhất, DCTDecode, JPXDecode và JBIG2Decode, bên trong một tiến trình worker sống ngắn tách biệt thay vì bên trong ứng dụng của bạn. Thuộc tính bật tính năng này là CodecIsolationMode, và hiệu quả thực tế là một chuỗi mã JPEG 2000 dị dạng vốn trước đây sẽ làm sập ứng dụng VCL của bạn thì nay chỉ giết chết một tiến trình con dùng một lần, trong khi tiến trình chủ báo về một mã trạng thái rồi tiếp tục hoạt động
Sự khác biệt đó quan trọng nhất ở chính những nơi PDF thực sự đến từ: một biểu mẫu tải lên, một cổng thư điện tử, một thiết bị quét, một điểm gửi FTP của đối tác. Bạn không kiểm soát các byte đó, và các codec ảnh là nơi tồn tại thiệt hại lịch sử
Vì sao chỉ một ảnh lỗi lại làm sập cả ứng dụng?
Vì codec ảnh là phần duy nhất của một trình đọc PDF chạy một máy trạng thái phức tạp trên dữ liệu do kẻ tấn công kiểm soát mà gần như không còn kiểm tra cấu trúc nào để dựa vào. Khi các byte đến được bộ giải mã JPEG 2000 hay JBIG2, bảng tham chiếu chéo đã được phân tích, đối tượng đã được giải quyết, chuỗi bộ lọc đã được bung ra, và những gì còn lại là một chuỗi mã thô cho biết bao nhiêu tile, bao nhiêu thành phần, bao nhiêu bit mỗi mẫu. Một con số sai ở đó không phải là lỗi phân tích cú pháp. Đó là một kích thước cấp phát sai hoặc một chỉ số nằm ngoài phạm vi bên trong một vòng lặp giải mã chặt chẽ
Giới hạn ngân sách có ích, và bạn nên đã có sẵn chúng. HotPDF giới hạn mức mở rộng bằng DecodeBudgetBytes và DocumentDecodeBudgetBytes, và giới hạn chuỗi bộ lọc bằng DecodeFilterLimit và DecodePipelineDepthLimit; lý do đằng sau các giới hạn đó được trình bày trong giải mã có giới hạn cho bộ lọc lồng nhau và bom PDF. Nhưng một ngân sách byte chỉ trả lời được một câu hỏi, đầu ra được phép lớn đến đâu. Nó không thể trả lời chuyện gì xảy ra khi bộ giải mã gặp lỗi trước khi tạo ra bất kỳ đầu ra nào. Một lỗi truy cập bộ nhớ bên trong vòng lặp giải mã không phải là một vi phạm chính sách bạn có thể từ chối; đó là một sự kiện cấp tiến trình, và cách cách ly đáng tin cậy duy nhất cho một sự kiện cấp tiến trình là một tiến trình khác
HotPDF cách ly gì, và không cách ly gì
HotPDF cách ly đúng ba loại codec, được liệt kê là hckDCT, hckJPX và hckJBIG2 trong đơn vị HPDFCodecIsolation. Mọi thứ khác, Flate, LZW, RunLength, ASCII85, CCITT, vẫn chạy trong tiến trình, vì các bộ giải mã đó đủ đơn giản để giới hạn bằng ngân sách và không phải nơi phát sinh những sự cố đáng lo ngại
Lớp truyền tải được thiết kế cố ý hẹp. Tiến trình chủ cấp phát một vùng ánh xạ bộ nhớ dùng chung có giới hạn, ghi một tiêu đề THPDFCodecSharedHeader cố định cùng dữ liệu đầu vào đã nén và mọi phân đoạn toàn cục JBIG2, khởi chạy worker, rồi chờ. Worker ghi các pixel đã giải mã trở lại cùng vùng ánh xạ đó và đặt một từ trạng thái. Không có giao thức pipe nào có thể mất đồng bộ, không có định dạng tuần tự hóa nào để fuzz, và tiêu đề mang theo một giá trị magic cùng một số phiên bản để một tệp thực thi worker không khớp bị từ chối thay vì bị đọc sai
uses
HPDFDoc, HPDFCodecIsolation;
var
Pdf: THotPDF;
Info: THPDFCodecWorkerInfo;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
// Fail closed: không bao giờ giải mã các codec này trong tiến trình
Pdf.CodecIsolationMode := cimRequired;
Pdf.CodecWorkerExecutable := 'HotPDFCodecWorker.exe';
Pdf.CodecWorkerTimeoutMilliseconds := 5000; // 1..600000
Pdf.CodecWorkerMemoryLimitBytes := 268435456; // 0 hoặc >= 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;
Để trống CodecWorkerExecutable và HotPDF sẽ tìm worker ngay cạnh tệp thực thi của bạn, dưới tên HotPDFCodecWorker.exe trong thư mục của ParamStr(0). Hãy đặt giá trị này rõ ràng khi bản triển khai của bạn đặt worker ở nơi khác; giá trị được mở rộng qua ExpandFileName, nên một đường dẫn tương đối sẽ được giải quyết dựa trên thư mục hiện hành thay vì thư mục ứng dụng, điều hiếm khi là điều bạn muốn trên một dịch vụ
Tự động hay bắt buộc: bạn muốn kiểu thất bại nào?
Ba giá trị của THPDFCodecIsolationMode mã hóa ba câu trả lời khác nhau cho cùng một câu hỏi, chuyện gì nên xảy ra khi worker hoàn toàn không thể chạy. cimDisabled bỏ qua hoàn toàn việc cách ly và giải mã trong tiến trình, đây là hành vi từ trước phiên bản 3.x. cimAutomatic, giá trị mặc định, thử chạy worker rồi âm thầm chuyển về giải mã trong tiến trình khi tệp thực thi worker bị thiếu hoặc không khởi chạy được, việc này được báo cáo với trạng thái cwsUnavailable. cimRequired từ chối kiểu dự phòng đó: một worker không khả dụng khiến lượt giải mã được đánh dấu là đã xử lý và thất bại, nên không chuỗi mã không đáng tin nào từng chạm đến vùng địa chỉ của bạn
Hãy chọn theo mô hình đe dọa, không theo sự thuận tiện. Một trình xem desktop mở các tài liệu người dùng đã có sẵn trên đĩa thì dùng cimAutomatic là ổn, nơi một worker bị thiếu chỉ giảm cấp về hành vi kinh điển thay vì làm hỏng sản phẩm. Một dịch vụ tiếp nhận phân tích các tệp từ internet nên chạy cimRequired, vì một sai sót triển khai âm thầm loại bỏ lớp cách ly chính là kiểu hồi quy mà không ai nhận ra cho đến khi nó gây hậu quả. Hãy lưu ý sự bất đối xứng: chỉ cwsUnavailable mới kích hoạt dự phòng. Một worker đã khởi chạy rồi sau đó sập, hết thời gian chờ, hoặc chạm giới hạn là một lỗi giải mã ở cả hai chế độ, không bao giờ là một lượt thử lại âm thầm trong tiến trình
Đọc kết quả từ THPDFCodecWorkerStatus
GetLastCodecWorkerInfo trả về kết quả của lượt giải mã cách ly gần nhất, và kiểu liệt kê trạng thái đủ cụ thể để dẫn dắt các quyết định vận hành thực sự thay vì một dòng log "ảnh lỗi" chung chung. Các giá trị là cwsNotRun, cwsSucceeded, cwsUnavailable, cwsLaunchFailed, cwsTimedOut, cwsCrashed, cwsDecodeFailed, cwsProtocolError và cwsOutputLimit
Hãy coi chúng là ba nhóm. Sự cố triển khai là cwsUnavailable và cwsLaunchFailed: ai đó đã xuất xưởng mà thiếu worker, hoặc một phần mềm diệt virus đang chặn việc tạo tiến trình. Sự cố tài liệu là cwsDecodeFailed và cwsOutputLimit: tệp bị dị dạng hoặc lớn hơn chính sách của bạn cho phép, và việc từ chối nó là câu trả lời đúng. Nhóm đáng chú ý là cwsTimedOut và cwsCrashed, vì đó là những sự kiện mà trước đây từng làm treo hoặc giết chết tiến trình chủ. Khi điều đó xảy ra, các trường ProcessId, ExitCode và ElapsedMilliseconds đi kèm cho bạn đủ dữ liệu để đối chiếu với một mục trong Windows Error Reporting và quyết định xem một tệp của khách hàng là bất thường hay có ai đó đang dò xét bạn
procedure LogCodecOutcome(const Info: THPDFCodecWorkerInfo);
begin
case Info.Status of
cwsSucceeded:
; // không có gì cần báo cáo
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;
Các giới hạn thực sự có tác dụng
Ba giới hạn riêng biệt áp dụng cho mọi lượt giải mã cách ly, và biết cái nào bị kích hoạt sẽ tiết kiệm cả một buổi chiều đoán mò. CodecWorkerTimeoutMilliseconds mặc định là 10.000 và được kiểm tra hợp lệ trong khoảng 1 đến 600.000; một giá trị nằm ngoài khoảng đó sẽ gây lỗi thay vì bị cắt bớt âm thầm. CodecWorkerMemoryLimitBytes mặc định là 536.870.912 byte và phải bằng không, nghĩa là không giới hạn, hoặc ít nhất 67.108.864 byte, vì một giới hạn nhỏ hơn không thể chứa một tập làm việc bộ giải mã thực tế và sẽ làm thất bại mọi tài liệu. Giới hạn bộ nhớ được thi hành bằng một Windows Job Object với ngữ nghĩa kill-on-close, nên worker sẽ chết cùng job ngay cả khi tiến trình chủ bị chấm dứt đột ngột
Giới hạn thứ ba là giới hạn đầu ra, và nó được suy ra chứ không phải cấu hình. HotPDF tính số byte cần thiết từ vùng được yêu cầu, hoặc từ hình học ảnh dự kiến, bằng chiều rộng nhân chiều cao nhân ba cho đầu ra 24-bit, sau đó cắt giá trị đó xuống DecodeBudgetBytes khi có đặt ngân sách. Một bộ giải mã báo cáo một tiêu đề hợp lý rồi cố gắng phát ra nhiều pixel hơn hẳn so với hình học cho phép sẽ bị chặn lại bởi chính vùng ánh xạ, và tiến trình chủ nhận được cwsOutputLimit. Đây là lý do lớp cách ly và ngân sách giải mã bổ trợ cho nhau: ngân sách xác định một ảnh được phép lớn đến đâu, còn ranh giới cách ly đảm bảo một lời nói dối về kích thước đó không thể trở thành một lần ghi ngoài phạm vi trong tiến trình của bạn
Vị trí của tính năng này trong một đường tiếp nhận đã được gia cố
Cách ly tiến trình là lớp ngoài cùng của một chuỗi phòng thủ bắt đầu từ sớm hơn nhiều. Giới hạn cấu trúc từ chối các tài liệu không hợp lý ngay tại thời điểm phân tích. Ngân sách bộ lọc giới hạn mức mở rộng. Cách ly ngăn chặn những gì vượt qua cả hai lớp trên. Với các tài liệu đến được tầng ảnh, đáng để biết bạn đang thực sự dùng codec nào, vì xử lý JPXDecode và từ điển ký hiệu JBIG2 có hồ sơ lỗi rất khác nhau, và JBIG2 đặc biệt mang theo các phân đoạn toàn cục xuyên trang mà một sandbox theo từng ảnh riêng lẻ ngây thơ sẽ làm hỏng
Chi phí này cần được nói thẳng và đáng để trả: khởi chạy một tiến trình cho mỗi ảnh cách ly tốn thêm mili giây, và một tài liệu với hàng trăm trang quét sẽ cảm nhận rõ điều đó. Hãy đo lường điều này với những gì nó mang lại. Trên một bộ chuyển đổi hàng loạt chạy không giám sát qua đêm, tổn thất thông lượng là vô hình và việc ngăn chặn sập là toàn bộ mục đích. Trên một trình xem tương tác mở các tài liệu người dùng đã tin tưởng sẵn, cimDisabled hoặc cimAutomatic là mặc định hợp lý. Chế độ này là một thuộc tính đơn giản, nên không gì ngăn bạn chọn theo từng loại tài liệu tại thời điểm chạy
HotPDF cung cấp lớp cách ly, ngân sách giải mã và giới hạn phân tích cấu trúc như một thành phần VCL gốc duy nhất cho Delphi và C++Builder, không cần runtime bên ngoài nào ngoài chính tệp thực thi worker. Tài liệu API đầy đủ và bản dùng thử có tại trang thành phần PDF HotPDF cho Delphi