Τεχνικό Άρθρο

Memory-Safe Ανάλυση (Parsing) PDF: Άμυνα ενάντια σε Κακόβουλα Έγγραφα

Μια γραμμή υποδοχής (intake pipeline) εγγράφων δέχεται αρχεία που έχουν γραφτεί από αγνώστους. Τιμολόγια (Invoices), σαρώσεις (scans), συνημμένα από μια φόρμα στον ιστό: το καθένα ισχυρίζεται (claims) ότι είναι ένα PDF και φέρει εκατοντάδες αριθμούς με βάση τους οποίους ο αναλυτής (parser) σας αναμένεται να ενεργήσει (act on). Μήκη ροών (Stream lengths), διαστάσεις εικόνων, μετατοπίσεις (offsets) byte, αναφορές αντικειμένων — το καθένα επιλέχθηκε (chosen) από όποιον παρήγαγε το αρχείο, και μια αποκομμένη μεταφόρτωση (truncated upload) ή ένα εσκεμμένα (deliberately) κακοσχηματισμένο έγγραφο αργά ή γρήγορα θα βάλει έναν από αυτούς τους αριθμούς εκεί όπου προκαλεί ζημιά. Η διαφορά μεταξύ ενός αναλυτή (parser) που επιβιώνει από αυτό το αρχείο και ενός που καταρρέει (crashes), ή συνεχίζει να τρέχει με κατεστραμμένη μνήμη (corrupted memory), είναι ένα μικρό σύνολο από συνήθειες (habits) που δεν εξαρτώνται από καμία συγκεκριμένη βιβλιοθήκη PDF

Οι συνήθειες (habits) μοιράζονται μια κοινή προϋπόθεση (premise): μια τιμή που διαβάζεται από το αρχείο είναι ένας ισχυρισμός (claim), όχι μια μέτρηση (measurement). Γίνεται χρησιμοποιήσιμη μόνο αφού ελεγχθεί με κάτι που ο ίδιος ο αναλυτής (parser) μέτρησε — το πραγματικό μέγεθος του αρχείου, τον πραγματικό αριθμό bytes που παρήγαγε ένας αποκωδικοποιητής (decoder), το πραγματικό βάθος μιας αναδρομής (recursion). Αυτό που ακολουθεί είναι αυτή η προϋπόθεση εφαρμοσμένη στα σημεία όπου οι αναλυτές εγγράφων πραγματικά "σπάνε" (break)

Ένα δηλωμένο μήκος (declared length) είναι ένας ισχυρισμός, όχι μια μέτρηση

Η πιο απλή αναντιστοιχία (mismatch) είναι το μήκος της ροής (stream length). Ένα αντικείμενο ροής (stream object) PDF δηλώνει το πλήθος των byte του (byte count) στο κλειδί /Length, και τα πραγματικά δεδομένα (actual data) κάθονται ανάμεσα στις λέξεις-κλειδιά stream και endstream. Τίποτα δεν αναγκάζει αυτά τα δύο να συμφωνούν. Ένα αποκομμένο αρχείο (truncated file) κρατά λιγότερα πραγματικά (real) bytes από τον δηλωμένο αριθμό· ένα αρχείο από μια χαλασμένη (broken) γεννήτρια (generator) μπορεί να δηλώσει ένα μήκος (length) που φτάνει πέρα από το τέλος του αρχείου ή μέσα σε ένα γειτονικό αντικείμενο. Αν εκχωρήσετε (Allocate) μνήμη βάσει της δηλωμένης τιμής και αντιγράψετε (copy) μέχρι το endstream τότε θα κάνετε υπέρβαση ορίου (overrun) στο buffer· αν διαβάσετε ακριβώς τον δηλωμένο αριθμό χωρίς να ελέγξετε τη διαθεσιμότητα (availability), τότε θα βγείτε εκτός (walk off) από το τέλος του αρχείου. Αφήστε τη δηλωμένη τιμή να οδηγήσει (drive) την εκχώρηση (allocation) μόνο αφού την περιορίσετε (clamping) με βάση τη μετρημένη (measured) απόσταση μέχρι το τέλος των δεδομένων, και αντιμετωπίστε μια διαφωνία (disagreement) ως σημείο απόφασης (decision point) — επισκευάστε σαρώνοντας (scanning) για το endstream, ή απορρίψτε τη ροή — ποτέ ως κάτι που πρέπει να πιστέψετε σιωπηλά

Παράμετροι εικόνας που περιγράφουν ένα raster μεγαλύτερο από αυτό που εκχωρήσατε (allocated)

Οι ροές εικόνων (Image streams) ανεβάζουν το διακύβευμα επειδή δύο ανεξάρτητα σετ αριθμών περιγράφουν τα ίδια pixel. Το λεξικό (dictionary) της εικόνας φέρει τα /Width και /Height, και τα buffers raster συνήθως παίρνουν το μέγεθός τους (sized) από αυτά. Το φίλτρο αποκωδικοποίησης (decode filter) φέρει τη δική του γεωμετρία: το CCITTFaxDecode λαμβάνει τα /Columns, /Rows, και /K από το DecodeParms του, όπου το /K επιλέγει το σχήμα (scheme) Group 3 ή Group 4 και ο αποκωδικοποιητής (decoder) εκπέμπει (emits) (Columns + 7) div 8 bytes ανά γραμμή σάρωσης (scanline). Ένα αρχείο που δηλώνει /Width 100 αλλά παραδίδει στο φίλτρο /Columns 1728 — που είναι η προεπιλογή (default) — κάνει τον αποκωδικοποιητή να παράγει πάνω από δεκαέξι φορές τα bytes ανά γραμμή (row) που περιμένει το buffer, και η υπερχείλιση (overflow) προσγειώνεται (lands) μία γραμμή σάρωσης (scanline) τη φορά σε οτιδήποτε κάθεται (sits) μετά την εκχώρηση (allocation). Όταν απουσιάζει το /Rows, ο αποκωδικοποιητής τρέχει (runs) μέχρι τα δεδομένα να πουν στοπ, γι' αυτό βάλτε όριο (bound) και στον αριθμό των γραμμών (row count). Το DCTDecode έχει την ίδια ραφή (seam): τα δεδομένα JPEG φέρουν το δικό τους πλάτος (width) και ύψος (height) στον δείκτη (marker) SOF τους, και τίποτα δεν τα υποχρεώνει να ταιριάζουν με το λεξικό

Ο αμυντικός κανόνας είναι μηχανικός: υπολογίστε το αναμενόμενο μέγεθος του raster από τις επικυρωμένες (validated) παραμέτρους αποκωδικοποίησης — τα δικά του /Columns και /Rows του φίλτρου για το CCITT, τις διαστάσεις SOF για το DCT — ελέγξτε (check) το με τα δικά σας όρια, εκχωρήστε (allocate) μνήμη βάσει αυτού, και επαληθεύστε κατά τη διάρκεια της αποκωδικοποίησης (during decode) ότι η έξοδος δεν ξεπερνά ποτέ (never runs past) την εκχώρηση. Όταν το λεξικό και το φίλτρο διαφωνούν σχετικά με τη γεωμετρία (geometry), συμβιβάστε τα (reconcile) ή απορρίψτε (reject) την εικόνα. Αυτό που δεν πρέπει να κάνει ποτέ ένας αναλυτής (parser) είναι να ορίσει το μέγεθος του buffer βάσει του ενός σετ αριθμών και να αφήσει τον αποκωδικοποιητή (decoder) να τρέξει με (run at) το άλλο

Αριθμητική του Delphi και παγίδες εκχώρησης (allocation pitfalls)

Τρεις συμπεριφορές του Delphi υπονομεύουν (undermine) ακόμη και έναν αναλυτή (parser) που έχει σκοπό (intends) να επικυρώνει. Η πρώτη είναι ο πολλαπλασιασμός 32-bit: το Delphi αξιολογεί (evaluates) το γινόμενο (product) δύο τελεστών (operands) Integer στα 32 bits ανεξάρτητα από το πλάτος (width) του προορισμού, επομένως το Width * Height * BytesPerPixel μπορεί να κάνει αναδίπλωση (wrap) ακόμη κι όταν κάθε παράγοντας περνά (passes) τον δικό του έλεγχο λογικής (sanity check). Μια σάρωση (scan) 30000 επί 30000 σε τρία bytes ανά pixel είναι 2.7 δισεκατομμύρια bytes, το οποίο αναδιπλώνει (wraps) αρνητικά στην προσημασμένη (signed) αριθμητική 32-bit· ελαφρώς διαφορετικοί παράγοντες αναδιπλώνουν σε ένα μικρό θετικό μήκος (length) που εκχωρεί και υποδιαστασιολογεί (undersizes) το buffer. Εξαναγκάστε (Force) ολόκληρη την έκφραση σε μεγάλο πλάτος (wide) κάνοντας μετατροπή τύπου (casting) στον πρώτο τελεστή — Size := Int64(Width) * Height * BytesPerPixel — και έπειτα συγκρίνετε με ένα ρητό (explicit) ανώτατο όριο (cap) πριν οτιδήποτε φτάσει στο SetLength

Η δεύτερη είναι ο έλεγχος εύρους (range checking). Η προεπιλεγμένη ρύθμιση release του Delphi διατίθεται (ships) με αυτόν απενεργοποιημένο, επομένως ένας εκτός-εύρους (out-of-range) δείκτης (index) που υπολογίζεται από τα δεδομένα του αρχείου δεν εγείρει σφάλμα (does not raise) — διαβάζει ή γράφει στη μνήμη (memory) που βρίσκεται δίπλα στον πίνακα (adjacent to the array). Ενεργοποιήστε τον ξανά με το {$R+} (και το {$Q+} για αριθμητική υπερχείλιση/overflow) στην κορυφή (top) κάθε unit (μονάδας) που χρησιμοποιεί τιμές προερχόμενες από αρχεία (file-derived values) ως δείκτες (indexes). Το κόστος είναι μη μετρήσιμο (unmeasurable) δίπλα στο I/O που ούτως ή άλλως κάνει ένας αναλυτής, και μετατρέπει τη σιωπηλή διαφθορά (silent corruption) σε ένα πιάσιμο (catchable) ERangeError

Η τρίτη είναι το TMemoryStream.SetSize με ένα Int64 που παρέχεται από το αρχείο (file-supplied). Σε ένα τρέχον (current) RTL εκχωρεί (allocates) ό,τι ζήτησε (asked for) το αρχείο, επομένως μια μεμονωμένη ροή (single stream) που ισχυρίζεται (claiming) ότι έχει μέγεθος τέσσερα gigabytes μετατρέπεται σε αποτυχία εξάντλησης μνήμης (out-of-memory failure) στα μέσα της υποδοχής (mid-intake). Σε παλαιότερα RTLs, όπου το SetSize δέχεται ένα Longint, η τιμή πρώτα στενεύει (narrowed) σιωπηλά: ένα δηλωμένο $100000010 γίνεται 16, η εκχώρηση (allocation) επιτυγχάνει (succeeds), και η εγγραφή (write) των πραγματικών δεδομένων τρέχει πολύ πέρα (runs far past) από αυτό. Επικυρώστε (Validate) κάθε μέγεθος με το μετρημένο (measured) μέγεθος πηγής και ένα σκληρό όριο (hard cap) πριν το δει οποιαδήποτε κλήση (call) εκχώρησης

Μετατοπίσεις (Offsets) που δείχνουν έξω από το αρχείο

Ο πίνακας διασταυρούμενων αναφορών (cross-reference table) χαρτογραφεί (maps) τους αριθμούς αντικειμένων (object numbers) σε απόλυτες μετατοπίσεις (absolute byte offsets), και ο αναλυτής (parser) αναζητά (seeks) όπου δείχνει (points) αυτός. Σε ένα κατεστραμμένο (damaged) ή εχθρικό (hostile) αρχείο αυτές οι μετατοπίσεις προσγειώνονται (land) πέρα από το τέλος του αρχείου ή μέσα σε άσχετες δομές. Η TStream κάνει την αποτυχία (failure) αθόρυβη: ο ορισμός του Position πέρα από το Size δεν αποτελεί σφάλμα, και ένα απλό Read πέρα από το τέλος απλώς επιστρέφει λιγότερα bytes από όσα ζητήθηκαν, επομένως ο κώδικας που παραλείπει (skips) τον έλεγχο πλήθους (count check) συνεχίζει να αναλύει (parsing) παλιά (stale) bytes από το προηγούμενο (previous) αντικείμενο. Η άμυνα (defense) είναι ένα σημείο ασφυξίας (chokepoint) — ένας βοηθός (helper) μέσα από τον οποίο περνάει (passes) κάθε αναζήτηση (seek) και ανάγνωση (read) που καθοδηγείται από το αρχείο (file-driven), επικυρώνοντας (validating) το offset και το πλήθος (count) βάσει του μετρημένου μεγέθους (measured file size) πριν το stream κινηθεί (moves)

uses
  System.SysUtils, System.Classes;

const
  MAX_OBJECT_BYTES = 64 * 1024 * 1024; // no single object may exceed 64 MB

type
  EPdfBoundsError = class(Exception);

// Every file-driven seek and read goes through here. Offset and Count are
// file-supplied claims; Source.Size is the measurement they must fit.
procedure ReadBounded(Source: TStream; Offset, Count: Int64;
  var Buffer: TBytes);
begin
  if (Offset < 0) or (Count < 0) or (Count > MAX_OBJECT_BYTES) or
     (Offset > Source.Size) or (Count > Source.Size - Offset) then
    raise EPdfBoundsError.CreateFmt(
      'object extent %d+%d exceeds file size %d',
      [Offset, Count, Source.Size]);
  SetLength(Buffer, Count);
  if Count = 0 then
    Exit;
  Source.Position := Offset;
  Source.ReadBuffer(Buffer[0], Count);
end;

Δρομολογήστε (Route) μέσα από εκεί τα offsets των cross-reference, τα μεγέθη (extents) των stream, και τις αναγνώσεις (reads) ενσωματωμένων αρχείων, και ένα κακό (bad) offset μετατρέπεται σε μια καθαρή απόρριψη (clean rejection) που κατονομάζει (names) τους αριθμούς αντί για παραβίαση πρόσβασης (access violation) τρεις κλήσεις αργότερα

Κύκλοι και βάθος (depth) στο γράφημα αντικειμένων (object graph)

Ένα PDF είναι γράφημα (graph), όχι δέντρο. Οποιαδήποτε τιμή (value) μπορεί να είναι μια έμμεση αναφορά (indirect reference), μια αναφορά μπορεί να επιλυθεί (resolve) σε μια άλλη αναφορά — /Length 12 0 R, όπου το αντικείμενο 12 κρατάει (holds) 13 0 R — και τίποτα δεν εμποδίζει (prevents) μια αλυσίδα από το να κλείσει ξανά (closing back) στον εαυτό της. Ένας επιλυτής (resolver) που ακολουθεί τις αναφορές (references) απλοϊκά (naively) θα κάνει αναδρομή (recurses) μέχρι να εξαντληθεί η εγγενής (native) στοίβα (stack), και η εξάντληση της στοίβας (stack exhaustion) δεν είναι κάτι που μπορείτε να πιάσετε (catch)· τερματίζει τη διεργασία (ends the process). Arrays και λεξικά (dictionaries) με βαθύ (Deeply) φώλιασμα (nested) φτάνουν στο ίδιο τέλος (end) χωρίς κανέναν κύκλο (cycle)

Χρησιμοποιήστε δύο φρουρούς (guards) μαζί (together): ένας ρητός (explicit) μετρητής βάθους (depth counter) οριοθετεί (bounds) την έντιμη-αλλά-βαθιά (honest-but-deep) περίπτωση σε ένα όριο που κανένα νόμιμο (legitimate) αρχείο δεν πλησιάζει (approaches), και ένα σετ (visited set) με τα ήδη επισκεπτόμενα πιάνει (catches) έναν γνήσιο κύκλο (genuine cycle) στη δεύτερη επίσκεψή του, μετατρέποντάς τον (turning it) σε ένα ακριβές (precise), αναφερόμενο (reportable) σφάλμα (error) αντί για χτύπημα ορίου (limit trip)

uses
  System.SysUtils, System.Generics.Collections;

const
  MAX_RESOLVE_DEPTH = 32; // far deeper than any legitimate reference chain

type
  EPdfStructureError = class(Exception);

  TPdfValueKind = (pvNull, pvNumber, pvName, pvString, pvArray,
    pvDictionary, pvStream, pvReference);

  TPdfValue = record
    Kind: TPdfValueKind;
    RefNumber: Integer; // meaningful when Kind = pvReference
    // ... payload fields for the remaining kinds
  end;

// LoadObject is your own routine: it looks up the xref offset for
// ObjNumber, reads the object with ReadBounded, and parses it.
function ResolveObject(ObjNumber, Depth: Integer;
  Visited: TDictionary<Integer, Boolean>): TPdfValue;
begin
  if Depth > MAX_RESOLVE_DEPTH then
    raise EPdfStructureError.Create('reference chain exceeds depth limit');
  if Visited.ContainsKey(ObjNumber) then
    raise EPdfStructureError.CreateFmt(
      'circular reference through object %d', [ObjNumber]);
  Visited.Add(ObjNumber, True);
  try
    Result := LoadObject(ObjNumber);
    if Result.Kind = pvReference then // e.g. /Length 12 0 R
      Result := ResolveObject(Result.RefNumber, Depth + 1, Visited);
  finally
    Visited.Remove(ObjNumber); // siblings may legally share this object
  end;
end;

Η αποσυμπίεση (Decompression) είναι ενισχυτής (amplifier)

Λίγα kilobytes εισόδου FlateDecode μπορούν να φουσκώσουν (inflate) σε gigabytes· η συμπίεση (compression) γενικού σκοπού ανταμείβει (rewards) το επαναλαμβανόμενο απλό κείμενο (repetitive plaintext), και ένας επιτιθέμενος (attacker) μπορεί να το κάνει (make it) μεγίστως επαναλαμβανόμενο (maximally repetitive). Βάλτε όριο (Cap) στο φουσκωμένο (inflated) μέγεθος κάθε ροής (stream) σε αυτό που εύλογα μπορεί (plausibly need) να χρειαστεί ο καταναλωτής της, και κρατήστε (keep) έναν δεύτερο προϋπολογισμό ανά έγγραφο (per-document budget): πεντακόσιες ροές (streams) με την καθεμία (each) λίγο κάτω από (just under) το όριο ανά ροή (per-stream cap) εξαντλούν (exhaust) τη μνήμη (memory) εξίσου σίγουρα με μια γιγαντιαία (giant) ροή. Ο έλεγχος (check) ανήκει μέσα (inside) στον βρόχο "φουσκώματος" (inflation loop), μετρώντας (counting) τα bytes της εξόδου καθώς (as) παράγονται (produced) και ματαιώνοντας (aborting) κατά την παραβίαση (on breach), όχι (not) μετά τον (after the) βρόχο (loop) όταν (when) η μνήμη (memory) έχει ήδη (already) ξοδευτεί (spent). Ένας προϋπολογισμός εγγράφου (document budget) εκφρασμένος (expressed) ως πολλαπλάσιο (multiple) του συμπιεσμένου (compressed) μεγέθους του αρχείου λειτουργεί καλά (works well), καθώς (since) τα νόμιμα (legitimate) έγγραφα ομαδοποιούνται (cluster) πολύ κάτω από (far below) τις αναλογίες (ratios) που φτάνει (reaches) μια τεχνητή (crafted) ροή

Άμυνα (Defense in depth) σε βάθος πέρα από τα δικά σας units

Οι (The) ίδιες (same) κατηγορίες (classes) ελαττωμάτων (defect) ζουν (live) μέσα (inside) στις βιβλιοθήκες (libraries). Δύο περιπτωσιολογικές μελέτες (case studies) σε (on) αυτό το (this) ιστολόγιο (blog) κάνουν μια διαδρομή (walk through) σε (σε) πραγματικά παραδείγματα (real instances): οι (the) αναδιπλώσεις ακέραιων (integer wraps), η (η) χωρίς όρια (unbounded) αναδρομή (recursion), και (and) τα uninitialized buffers (buffers) που κλείστηκαν (closed) σε μια (in a) εγγενή (native) μηχανή (engine) Pascal (Pascal) στο (στο) Σκλήρυνση ενός αναλυτή Pascal PDF απέναντι σε κακόβουλα αρχεία (Hardening a Pascal PDF Parser Against Malicious Files), και (και) οι κίνδυνοι (hazards) (με) της (της) σύμβασης κλήσης (calling-convention), του πλάτους ακέραιων (integer-width), και της ιδιοκτησίας (ownership) κατά (της) τη (της) σύνδεση (binding) μιας (μιας) μηχανής C (C) στο (στο) Σκλήρυνση μιας σύνδεσης (binding) PDFium Component. Για (Για) πραγματικά (genuinely) μη έμπιστη (untrusted) εισαγωγή (intake) — (—) μια δημόσια φόρμα (public upload form) (μια) (—) (ένα) (—) ένα μη ταυτοποιημένο γραμματοκιβώτιο (unauthenticated mailbox) (ένα) — τρέξτε (run) επίσης την (the) εργασία (work) ανάλυσης (parse) και αποκωδικοποίησης (decode) σε μια (in a) ξεχωριστή (separate) διεργασία χαμηλών προνομίων (low-privilege process), (ώστε) (ώστε) (ώστε) το (το) αρχείο που (that) νικά (defeats) (τους) (τους) (τους) όλους τους φρουρούς (every guard) (κάθε) εντός της διεργασίας (in-process) (να) να (να) κοστίσει (costs) (στο) (στο) (στο) μια (μια) (ένα) (ένα) αποτυχημένη εργασία (failed job) (αντί) αντί (αντί) (για) (για) για μια (μια) (μια) (μια) πεσμένη (downed) υπηρεσία (service)

Μια (A) λίστα ελέγχου (checklist) προελέγχου (preflight)

Πριν κυκλοφορήσει (ships) η (the) επόμενη (next) έκδοση (build), περάστε (walk) τον (the) αναλυτή (parser) (σας) (από) απέναντι σε (against) αυτή τη (this) λίστα (list): το μέγεθος (sized) κάθε (every) (σε) (το) buffer (σε) (το) ροής (stream) (να) να (να) έχει (έχει) ρυθμιστεί (έχει) (από) από (από) (ένα) ένα (ένα) περιορισμένο (clamped) (στο) μήκος (length) (και) αντί για (rather than) (στο) (το) (το) δηλωμένο (declared one)· κάθε (every) (το) raster (να) να (να) παίρνει (έχει) (παίρνει) μέγεθος (sized) (το) από (from) επικυρωμένες (validated) παραμέτρους (parameters) (του) (του) του (του) αποκωδικοποιητή (decoder) και να ελέγχεται (checked) απέναντι στην (against) έξοδο (output) του αποκωδικοποιητή (decoder)· κάθε (every) γινόμενο (product) (των) διαστάσεων (dimension) (να) να (να) αξιολογείται (evaluated) σε (in) Int64 (και) και (και) να συγκρίνεται (compared) (με) με (με) ένα (ένα) ρητό (explicit) ανώτατο (cap) όριο (όριο)· (το) {$R+} (να είναι) ενεργό (active) (σε) σε (σε) κάθε (every) (κάθε) (τη) (κάθε) unit (που) που (που) δημιουργεί δείκτες (indexes) (με) με (με) τιμές (values) (που) που (που) προέρχονται από (file-derived) το αρχείο (το αρχείο) (τις)· (κάθε) κάθε (κάθε) (κάθε) αναζήτηση (seek) (να) να (να) ελέγχεται στα όριά της (bounds-checked) απέναντι στο (against the) μετρημένο (measured) (το) μέγεθος του αρχείου (file size)· (κάθε) κάθε (κάθε) ανάλυση (resolution) αναφοράς (reference) να έχει όριο στο (depth-limited) βάθος (βάθος) και να ελέγχεται (cycle-checked) (για) για κύκλους (κύκλους) (και)· κάθε (every) βρόχος "φουσκώματος" (inflation loop) (να) να (να) μετρά (counting) (την) την έξοδο (output) απέναντι στους (against) προϋπολογισμούς ανά ροή (per-stream) και (and) ανά έγγραφο (per-document). Κανένας από (None of) αυτούς (these) τους (the) ελέγχους (checks) (δεν) (δεν) (δεν) (δεν) κοστίζει (costs) μετρήσιμο (measurable) χρόνο (time) (σε) σε (σε) ένα (ένα) νόμιμο (legitimate) έγγραφο (document), (και) και (και) (καθένας) καθένας (καθένας) (τους) μετατρέπει (converts) (τη) τη (τη) (τη) διαφθορά μνήμης (memory corruption) (σε) σε (σε) μια (μια) καθαρή (clean), καταγράψιμη (loggable) απόρριψη (rejection)

Σημείωση: το (the) losLab HotPDF Component, (και) η (the) Βιβλιοθήκη PDF PDFlibPas Delphi (PDFlibPas Delphi PDF Library), (και) και (and) (το) το PDFium Component εφαρμόζουν (apply) (τους) (εσωτερικά) (εσωτερικά) αυτούς τους (these) (εσωτερικά) ελέγχους (checks) (ορίων) ορίων (bounds), τα όρια (limits) (βάθους) βάθους (depth), και τα ανώτατα όρια (caps) (επέκτασης) (της) (της) (επέκτασης) (της) (της) επέκτασης (expansion) εσωτερικά (internally), οπότε (so) μια (μια) (μια) (μια) (ένα) γραμμή (pipeline) υποδοχής (intake) (που) (που) έχει (έχει) (έχει) χτιστεί (built) πάνω τους (on them) (ξεκινάει) (εκκινεί) ξεκινά (starts) (από) από (από) (μια) (ένα) (ένα) (ένα) μια (μια) σκληρυμένη (hardened) βάση (baseline)