HotPDF สามารถถอดรหัสตัวกรองภาพ PDF ที่เสี่ยงที่สุดสามตัว คือ DCTDecode, JPXDecode และ JBIG2Decode ภายในโพรเซสลูกอายุสั้นแยกต่างหาก แทนที่จะทำภายในแอปพลิเคชันของคุณเอง คุณสมบัติที่เปิดใช้งานเรื่องนี้คือ CodecIsolationMode และผลในทางปฏิบัติคือ codestream JPEG 2000 ที่ผิดรูปแบบซึ่งเดิมจะทำให้แอป VCL ของคุณล่ม ตอนนี้จะฆ่าโพรเซสลูกแบบใช้แล้วทิ้งแทน ในขณะที่โฮสต์รายงานรหัสสถานะแล้วทำงานต่อไป
ความแตกต่างนี้สำคัญที่สุดในจุดที่ไฟล์ PDF มาถึงจริง ๆ นั่นคือฟอร์มอัปโหลด เกตเวย์อีเมล เครื่องสแกน หรือจุดวาง FTP ของพาร์ทเนอร์ คุณไม่สามารถควบคุมไบต์เหล่านั้นได้ และโคเดกภาพก็คือจุดที่ความเสียหายในอดีตมักเกิดขึ้น
เหตุใดภาพเสียเพียงภาพเดียวจึงทำให้แอปพลิเคชันทั้งหมดล่ม
เพราะโคเดกภาพเป็นส่วนเดียวของตัวอ่าน PDF ที่รันสเตตแมชชีนซับซ้อนบนข้อมูลที่ผู้โจมตีควบคุมได้ โดยแทบไม่มีการตรวจสอบเชิงโครงสร้างเหลือให้พึ่งพาแล้ว เมื่อไบต์มาถึงตัวถอดรหัส JPEG 2000 หรือ JBIG2 ตารางอ้างอิงไขว้ถูกแยกวิเคราะห์ไปแล้ว อ็อบเจกต์ถูกแก้ไขแล้ว สายตัวกรองถูกคลี่ออกแล้ว สิ่งที่เหลืออยู่คือ codestream ดิบที่บอกว่ามีกี่ไทล์ กี่คอมโพเนนต์ กี่บิตต่อตัวอย่าง ตัวเลขที่ผิดตรงนั้นไม่ใช่ข้อผิดพลาดในการแยกวิเคราะห์ แต่เป็นขนาดการจัดสรรหน่วยความจำที่ผิดพลาดหรือดัชนีที่อยู่นอกช่วงภายในลูปถอดรหัสที่แน่นหนา
การจำกัดงบประมาณช่วยได้ และคุณควรมีมันอยู่แล้ว HotPDF จำกัดการขยายขนาดด้วย DecodeBudgetBytes และ DocumentDecodeBudgetBytes และจำกัดสายตัวกรองด้วย DecodeFilterLimit และ DecodePipelineDepthLimit เหตุผลเบื้องหลังเพดานเหล่านี้อธิบายไว้ใน การจำกัดการถอดรหัสสำหรับตัวกรองซ้อนและ PDF bomb แต่งบประมาณเป็นไบต์ตอบได้เพียงคำถามเดียว คือปริมาณผลลัพธ์ที่อนุญาต มันตอบไม่ได้ว่าจะเกิดอะไรขึ้นเมื่อตัวถอดรหัสล่มก่อนที่จะสร้างผลลัพธ์ใด ๆ เลย การละเมิดหน่วยความจำภายในลูปถอดรหัสไม่ใช่การละเมิดนโยบายที่คุณปฏิเสธได้ แต่เป็นเหตุการณ์ระดับโพรเซส และการควบคุมที่เชื่อถือได้เพียงอย่างเดียวสำหรับเหตุการณ์ระดับโพรเซสคือโพรเซสอื่นเท่านั้น
สิ่งที่ HotPDF แยกโดดออกมา และสิ่งที่ไม่แยก
HotPDF แยกโดดโคเดกออกมาเพียงสามประเภทเท่านั้น ซึ่งระบุเป็น hckDCT, hckJPX และ hckJBIG2 ในยูนิต HPDFCodecIsolation ส่วนที่เหลือทั้งหมด ได้แก่ Flate, LZW, RunLength, ASCII85, CCITT ยังคงทำงานในโพรเซสเดิม เพราะตัวถอดรหัสเหล่านั้นเรียบง่ายพอที่จะจำกัดด้วยงบประมาณได้ และไม่ใช่จุดที่ความล้มเหลวที่น่าสนใจเกิดขึ้น
ช่องทางการส่งข้อมูลถูกออกแบบให้แคบโดยตั้งใจ โฮสต์จะจัดสรรการแมปหน่วยความจำร่วมที่มีขอบเขตจำกัดหนึ่งชุด เขียน THPDFCodecSharedHeader แบบตายตัวพร้อมข้อมูลนำเข้าที่บีบอัดและเซกเมนต์ส่วนกลางของ JBIG2 ถ้ามี จากนั้นเรียกใช้งานตัวประมวลผลลูกและรอ ตัวประมวลผลลูกจะเขียนพิกเซลที่ถอดรหัสแล้วกลับเข้าไปในการแมปเดียวกันและตั้งค่าคำสถานะ ไม่มีโพรโทคอลไปป์ที่จะหลุดซิงก์ ไม่มีรูปแบบซีเรียลไลซ์ที่จะถูก fuzz และเฮดเดอร์มีค่าเวทมนตร์และเวอร์ชันกำกับไว้ ทำให้ไบนารีของตัวประมวลผลลูกที่ไม่ตรงกันถูกปฏิเสธแทนที่จะถูกอ่านผิด
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 ปฏิเสธการถอยกลับนั้น: ตัวประมวลผลลูกที่ใช้งานไม่ได้จะทำให้การถอดรหัสถูกทำเครื่องหมายว่าจัดการแล้วและล้มเหลว ดังนั้น codestream ที่ไม่น่าเชื่อถือจะไม่มีทางเข้าถึงพื้นที่หน่วยความจำของคุณเลย
เลือกตามโมเดลภัยคุกคาม ไม่ใช่ตามความสะดวก โปรแกรมดูเอกสารบนเดสก์ท็อปที่เปิดไฟล์ซึ่งผู้ใช้มีอยู่ในดิสก์แล้วใช้ 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 มีค่าเริ่มต้น 10,000 และถูกตรวจสอบให้อยู่ในช่วง 1 ถึง 600,000 ค่าที่อยู่นอกช่วงนี้จะทำให้เกิดข้อยกเว้นแทนที่จะถูกปรับให้อยู่ในช่วงอย่างเงียบ ๆ CodecWorkerMemoryLimitBytes มีค่าเริ่มต้น 536,870,912 ไบต์ และต้องเป็นศูนย์เท่านั้น หมายถึงไม่มีขีดจำกัด หรืออย่างน้อย 67,108,864 ไบต์ เพราะเพดานที่เล็กกว่านี้ไม่สามารถรองรับชุดข้อมูลทำงานของตัวถอดรหัสที่สมจริงได้ และจะทำให้ทุกเอกสารล้มเหลว เพดานหน่วยความจำถูกบังคับใช้ด้วย Windows Job Object แบบ kill-on-close ทำให้ตัวประมวลผลลูกตายไปพร้อมกับ job แม้ว่าโฮสต์จะถูกยุติแบบกะทันหันก็ตาม
เพดานที่สามคือขีดจำกัดผลลัพธ์ ซึ่งเป็นค่าที่คำนวณได้ ไม่ใช่ค่าที่ตั้งเอง HotPDF คำนวณจำนวนไบต์ที่ต้องการจากพื้นที่ที่ร้องขอ หรือจากรูปทรงภาพที่คาดไว้ เป็นความกว้างคูณความสูงคูณสามสำหรับผลลัพธ์ 24 บิต จากนั้นจำกัดค่านั้นลงให้ไม่เกิน DecodeBudgetBytes เมื่อมีการตั้งงบประมาณไว้ ตัวถอดรหัสที่รายงานเฮดเดอร์ที่ดูสมเหตุสมผลแล้วพยายามส่งพิกเซลออกมามากกว่าที่รูปทรงอนุญาตไว้มาก จะถูกหยุดด้วยการแมปหน่วยความจำเอง และโฮสต์จะเห็น cwsOutputLimit นี่คือเหตุผลที่ชั้นการแยกโดดและงบประมาณการถอดรหัสเสริมกันและกัน งบประมาณกำหนดว่าภาพจะใหญ่ได้แค่ไหน และขอบเขตการแยกโดดทำให้มั่นใจได้ว่าการโกหกเรื่องขนาดนั้นจะไม่กลายเป็นการเขียนนอกขอบเขตในโพรเซสของคุณ
จุดยืนของฟีเจอร์นี้ในเส้นทางรับไฟล์ที่แข็งแกร่ง
การแยกโดดโพรเซสคือชั้นนอกสุดของสายการป้องกันที่เริ่มต้นเร็วกว่านั้นมาก ขีดจำกัดเชิงโครงสร้างจะปฏิเสธเอกสารที่ไม่สมเหตุสมผลตั้งแต่เวลาแยกวิเคราะห์ งบประมาณตัวกรองจำกัดการขยายขนาด การแยกโดดควบคุมสิ่งที่รอดพ้นจากทั้งสองชั้นนั้น สำหรับเอกสารที่มาถึงชั้นภาพ ควรรู้ไว้ว่าคุณกำลังใช้โคเดกตัวไหนอยู่จริง เพราะ การจัดการ JPXDecode และ พจนานุกรมสัญลักษณ์ของ JBIG2 มีรูปแบบความล้มเหลวที่ต่างกันมาก และ JBIG2 โดยเฉพาะมีเซกเมนต์ส่วนกลางข้ามหน้าที่แซนด์บ็อกซ์แบบต่อภาพอย่างไม่รอบคอบจะทำให้พัง
ต้นทุนนี้ตรงไปตรงมาและควรพูดให้ชัด: การเรียกใช้งานหนึ่งโพรเซสต่อภาพที่แยกโดดหนึ่งภาพเพิ่มเวลาหลักมิลลิวินาที และเอกสารที่มีหน้าสแกนหลายร้อยหน้าจะรู้สึกได้ ให้ชั่งน้ำหนักกับสิ่งที่ได้กลับมา บนตัวแปลงไฟล์แบบชุดที่รันโดยไม่มีคนเฝ้าในตอนกลางคืน การสูญเสียปริมาณงานแทบมองไม่เห็น และการควบคุมการล่มคือประเด็นทั้งหมด บนโปรแกรมดูเอกสารแบบโต้ตอบที่เปิดเอกสารซึ่งผู้ใช้เชื่อถืออยู่แล้ว cimDisabled หรือ cimAutomatic เป็นค่าเริ่มต้นที่สมเหตุสมผล โหมดนี้เป็นเพียงพร็อพเพอร์ตี้ธรรมดา จึงไม่มีอะไรขวางคุณจากการเลือกตามคลาสเอกสารในขณะรันไทม์
HotPDF ส่งมอบชั้นการแยกโดด งบประมาณการถอดรหัส และขีดจำกัดตัวแยกวิเคราะห์เชิงโครงสร้างมาในคอมโพเนนต์ VCL แบบเนทีฟตัวเดียวสำหรับ Delphi และ C++Builder โดยไม่ต้องดีพลอยรันไทม์ภายนอกใด ๆ นอกจากไฟล์ปฏิบัติการของตัวประมวลผลลูกเอง เอกสาร API ฉบับเต็มและรุ่นทดลองใช้งานอยู่ที่ หน้าคอมโพเนนต์ PDF สำหรับ Delphi ของ HotPDF