Articolo tecnico

Parsing PDF Sicuro per la Memoria: Difendersi dai Documenti Dannosi

Una pipeline di acquisizione dei documenti accetta file scritti da estranei. Fatture, scansioni, allegati provenienti da un modulo web: ciascuno sostiene di essere un PDF e trasporta centinaia di numeri su cui ci si aspetta che il tuo parser agisca. Lunghezze dei flussi, dimensioni delle immagini, offset dei byte, riferimenti agli oggetti — ciascuno di essi è stato scelto da chiunque abbia prodotto il file, e un upload troncato o un documento malformato in modo deliberato finirà per posizionare uno di questi numeri proprio dove provoca danni. La differenza tra un parser che sopravvive a un file simile e uno che si blocca in modo anomalo (crash), oppure che continua a girare con la memoria corrotta, si riduce a un piccolo set di abitudini che non dipendono da alcuna particolare libreria PDF

Queste abitudini condividono un'unica premessa: un valore letto dal file è un'affermazione (claim), e non una misurazione. Esso diventa utilizzabile soltanto dopo essere stato verificato (checked) confrontandolo con qualcosa che il parser ha misurato in modo autonomo — la dimensione reale del file, il reale numero di byte prodotto da un decodificatore (decoder), la profondità reale di una ricorsione. Ciò che segue non è altro che tale premessa applicata ai punti esatti in cui i parser dei documenti finiscono di fatto per rompersi

Una lunghezza dichiarata è un'affermazione, non una misurazione

La mancata corrispondenza (mismatch) più semplice è la lunghezza del flusso. Un oggetto stream del PDF dichiara il suo conteggio in byte nella chiave /Length, e i dati effettivi risiedono tra le parole chiave stream ed endstream. Niente impone che i due concordino. Un file troncato possiede meno byte reali rispetto al conteggio dichiarato; un file partorito da un generatore corrotto può dichiarare una lunghezza che oltrepassa la fine del file, o che scongiura persino in un oggetto vicino. Esegui l'allocazione dal valore dichiarato e copia fino a endstream ed ecco che oltrepasserai i limiti del buffer (overrun); leggi l'esatto conteggio dichiarato senza controllare la disponibilità, ed ecco che sbufferai fuori dalla fine del file. Lascia che il valore dichiarato indirizzi l'allocazione esclusivamente dopo averlo limitato (clamping) in base alla distanza misurata fino alla fine dei dati, e tratta un disaccordo alla stregua di un punto decisionale — ripara tramite la scansione per trovare endstream, oppure rigetta del tutto il flusso — mai come una cosa a cui credere ciecamente e silenziosamente

Parametri dell'immagine che descrivono un raster più grande di quello che hai allocato

I flussi d'immagini alzano la posta in gioco per via di due insiemi indipendenti di numeri in grado di descrivere gli stessi pixel. Il dizionario dell'immagine trasporta /Width e /Height, a partire dai quali i buffer del raster sono solitamente dimensionati. Il filtro di decodifica (decode filter) possiede la sua propria geometria: CCITTFaxDecode preleva /Columns, /Rows e /K dal suo DecodeParms, dove /K seleziona lo schema Gruppo 3 o Gruppo 4 e il decodificatore emette (Columns + 7) div 8 byte per ogni linea di scansione (scanline). Un file che dichiara /Width 100 ma passa al filtro un /Columns 1728 — il default — porta il decoder a produrre oltre sedici volte la quantità di byte per singola riga che il buffer si aspetterebbe, con il risultato che l'overflow andrà a parare, una scanline alla volta, in qualunque cosa risieda oltre l'allocazione. Quando manca /Rows il decoder andrà avanti finché i dati non diranno basta, indi per cui conviene delimitare pure il conteggio delle righe (row count). DCTDecode presenta la stessa cucitura: i dati JPEG possiedono le proprie misure relative a larghezza e altezza nel loro marcatore SOF (SOF marker), e niente obbliga questi ultimi a corrispondere al dizionario

La regola difensiva si rivela automatica (mechanical): calcola la dimensione attesa del raster (expected raster size) a partire dai parametri di decodifica convalidati — il /Columns e il /Rows nativi del filtro per ciò che concerne il CCITT, le dimensioni SOF in merito al DCT — verificala nei confronti dei tuoi limiti, esegui un'allocazione da questi ultimi e, in fase di decodifica, assicurati che l'output non sconfini mai nell'allocazione. In caso di disaccordo circa la geometria tra il dizionario e il filtro, cerca di conciliarli oppure rigetta la foto. Quello che un parser non deve commettere è dimensionare il buffer da un set di numeri per poi lasciar correre il decoder sull'altro

Aritmetica Delphi e insidie di allocazione

Tre comportamenti tipici di Delphi sono soliti sminuire perfino un parser destinato alla validazione. Il primo è la moltiplicazione a 32 bit: Delphi stima il prodotto di due operandi Integer a 32 bit a prescindere dalla larghezza (width) della destinazione, ragion per cui Width * Height * BytesPerPixel può mandare il risultato in wrap anche nel momento in cui tutti i fattori superano agevolmente i loro controlli d'integrità (sanity check). Una scansione 30000 x 30000 a tre byte per pixel totalizza 2,7 miliardi di byte, il che determina un wrap negativo se impieghiamo l'aritmetica con segno a 32 bit; fattori leggermente diversi vanno in wrap verso una piccola lunghezza positiva che finisce con l'allocare e il sottodimensionare il buffer. Bada a forzare (Force) l'intera espressione in modo ampio tramite il cast del primo operando — Size := Int64(Width) * Height * BytesPerPixel — indi paragona il risultato a un limite esplicito (explicit cap) ben prima che qualunque cosa arrivi alla SetLength

Il secondo è il controllo dell'intervallo (range checking). La configurazione di rilascio predefinita di Delphi ne è sprovvista in partenza (ships with it off), così che un indice fuori intervallo calcolato partendo dai dati presenti all'interno dei file non darà adito al sollevamento (raise) di alcuna eccezione, bensì procederà alla lettura e/o scrittura della porzione di memoria adiacente al vettore (array). Riaccendilo ricorrendo a {$R+} (e a {$Q+} per l'overflow di natura aritmetica) nella parte superiore di ciascuna unità (unit) intenta a effettuare indicizzazioni sfruttando valori riconducibili a file. Si tratta di un costo affatto misurabile in confronto all'I/O compiuto del resto da qualsivoglia parser, con il duplice vantaggio di convertire ogni tacita e silenziosa corruzione (silent corruption) in un acchiappabile ERangeError

Il terzo è TMemoryStream.SetSize tramite l'impiego di un Int64 estrapolato da file. Su una RTL moderna, questa combinazione provvede ad allocare tutto quello che il file abbia domandato in origine; con simili prerogative, ne consegue che basterebbe un solo flusso intento a reclamare quattro gigabyte per incappare in un fallimento causato dall'esaurimento della memoria (out-of-memory failure) nel bel mezzo della procedura d'importazione (mid-intake). Sulle RTL un po' più datate, in cui SetSize si affida in prima persona a un semplice Longint, il valore finisce dapprima coll'essere assottigliato in sordina: ecco allora che un dichiarato $100000010 diventa un misero 16, l'allocazione fila liscio, e la fase dedicata alla scrittura dei dati veri e propri si spinge decisamente ben oltre siffatto limite irrisorio. Accertati in primo luogo di validare (Validate) la totalità delle grandezze ponendole a confronto con le dimensioni d'origine misurate e con una limitazione insormontabile (hard cap), giacché queste ultime non dovranno apparire d'innanzi a una qualsiasi chiamata di allocazione prima che tale iter sia giunto a degna conclusione

Gli offset che puntano al di fuori del file

La tabella dei riferimenti incrociati mappa i numeri degli oggetti in corrispondenza degli offset assoluti in byte (absolute byte offsets), a fronte di un parser dedito invece a fiondarsi su qualunque entità (seeks) siffatti offset si propongano di richiamare all'attenzione del suddetto tramite uno o più puntamenti diretti (points). In seno a un file di tipo corrotto/lesionato oppure inesorabilmente votato a nuocere apertamente, ecco che questi stessi offset scelgono immancabilmente d'incunearsi e di atterrare (land) al di là della coda o finale del file (past the end of the file) per non dire perfino tra i meandri di una qualsivoglia struttura non relazionata e aliena rispetto all'intero ecosistema d'origine. Ed è in quest'ottica che TStream trasforma qualsivoglia fallimento (failure) in una sorta d'evento quieto e del tutto sordo: sì, giacché il semplice impostare a piacere la Position al di fuori e finanche bel oltre l'estensione (Size) non va annoverato in alcun modo come errore preminente, laddove una spudorata e normalissima manovra di Read, pur trasbordando dalla linea di meta finale (past the end), avrà come unico esito apparente quello di recapitare al mittente un quantitativo di byte persino inferiore rispetto alla mole del pacchetto originale appositamente richiesta un istante dianzi; un comportamento assai controverso e ingannevole, con sommo rincrescimento del codice (code) che per tutta risposta decide in via sommaria di by-passare ed eludere con abile maestria (skips) i vari responsi emersi a fronte del check o verifica di rito, preferendo per giunta inoltrarsi lungo i meandri del parsing avvalendosi a suo gusto e piacere della compagnia sgradita, oltremodo insalubre, di byte oramai logori (stale bytes) originati dalle propaggini terminali dell'oggetto (object) che li precedeva. Come non citare, dunque, che l'unica e formidabile via per ergere il proprio scudo a protezione di questo scellerato malcostume risieda pur sempre nell'impiego diligente di uno smistamento stretto e alquanto angusto (chokepoint) — in altre parole, l'inappuntabile scesa in campo di un provvidenziale helper chiamato al preciso scopo d'imbrigliare e intercettare sia l'una che l'altra movenza legata alla ricerca (seek) e alla speculare interpretazione dei vari flussi d'origine del file (file-driven), validando pertanto qualsivoglia offset così come qualunque medesimo conteggio parziale (count) direttamente al cospetto degli inequivocabili dettami desunti per converso dalle inconfutabili taglie o pezzature ponderate in principio dal file a mano a mano sotto indagine, onde per cui lo stream stesso venga autorizzato a procedere spedito prima ancora d'infilarsi per suo volere nell'arduo e infausto tragitto ch'egli stesso paventa e anela in qualsivoglia attimo immemore del tempo

uses
  System.SysUtils, System.Classes;

const
  MAX_OBJECT_BYTES = 64 * 1024 * 1024; // nessun singolo oggetto può superare 64 MB

type
  EPdfBoundsError = class(Exception);

// Ogni ricerca (seek) e lettura guidata da file passa di qui. Offset e Count sono
// affermazioni (claims) fornite dal file; Source.Size è la misura entro cui devono rientrare.
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;

Instrada attraverso di esso gli offset dei riferimenti incrociati, le estensioni (extents) del flusso e le letture di file integrati (embedded-file), e un offset errato diventerà un rifiuto pulito che cita i numeri, anziché trasformarsi in una violazione di accesso (access violation) a tre chiamate di distanza

Cicli e profondità nel grafo degli oggetti

Un PDF è un grafo, non un albero. Qualsiasi valore può essere un riferimento indiretto, un riferimento a sua volta si può risolvere in un altro riferimento — /Length 12 0 R, dove l'oggetto 12 contiene 13 0 R — e nulla vieta che una catena si chiuda su se stessa. Un risolutore (resolver) che si cimenta ingenuamente nell'ardua impresa di rincorrere ciascun riferimento finirà fatalmente col procedere in totale ricorsione sino all'inesorabile esaurimento (exhaustion) dello stack primigenio e nativo: a onor del vero, l'esaurimento dello stack medesimo non figurerà mai nei panni di una qualsivoglia insidia passibile di parata, d'arresto oppure di cattura; difatti, questo non farà altro che porre fine allo stesso processo (process). Vettori e dizionari nidificati fin troppo nel profondo (Deeply nested) sono condannati a raggiungere il medesimo fato ancor privi d'aver esperito ciclo (cycle) alcuno

Utilizza un paio di guardie all'unisono: in prima istanza un palese contatore deputato a vagliare le profondità (depth counter), destinato per l'appunto ad agguantare e confinare la totalità di siffatte casistiche — oneste nel midollo ma finanche viscerali nei meandri più scoscesi, ostiche al pari di una sponda insidiosa e arcigna ove un qualsiasi file con pretese di legittimità disdegni puranco di stazionare, non pago peraltro d'aver lambito la temibile frontiera che il limite gli porge — e indi un congiunto prelevato dalle schiere visitate (visited set) il cui fido compito risiederà nell'intercettare l'avanzata spuria di un qualsivoglia circolo mendace ancor prima che questo riaffiori all'alba d'un eventuale secondo periglioso arrembaggio, traviando ed emendando di siffatta medesima condotta ciascun palese sopruso in vista d'una metamorfosi in palese e refertato diniego anziché di una qualsivoglia infrazione nei riguardi del summenzionato limit trip

uses
  System.SysUtils, System.Generics.Collections;

const
  MAX_RESOLVE_DEPTH = 32; // decisamente più profondo di qualsiasi catena di riferimento legittima

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 è la tua routine personale: cerca l'offset xref per
// ObjNumber, legge l'oggetto con ReadBounded, e lo analizza (parse).
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 // es. /Length 12 0 R
      Result := ResolveObject(Result.RefNumber, Depth + 1, Visited);
  finally
    Visited.Remove(ObjNumber); // i fratelli (siblings) possono legalmente condividere questo oggetto
  end;
end;

La decompressione è un amplificatore

Pochi chilobyte d'input di FlateDecode sono capaci di gonfiarsi (inflate) fino a toccare il tetto dei gigabyte; non a caso, la compressione d'impronta general-purpose ricompensa i testi in chiaro ripetitivi e un aggressore ha campo libero nel tentativo di rendere il testo ripetitivo alla massima potenza. Argina (Cap) le dimensioni lievitate inerenti a qualsivoglia flusso a quanto effettivamente stimato dal plausibile fabbisogno del consumatore preposto alla sua lavorazione, non disdegnando la provvidenziale entrata in campo di un secondo e propedeutico budget per-document (da riferire all'intero documento e non in parte singola): l'azione combinata di cinquecento flussi assembrati fin poco al di sotto della soglia limite di ognuno prenderà il sopravvento e ridurrà in esaurimento (exhaust) ciascun baluardo di memoria appieno e ancor con più veemenza di un colosso partorito dal seno primigenio dell'immenso, un titanico corso di una mostruosa, impari fiumana. L'accertamento (check), di contro, appartiene di diritto all'interregno (inside) assiso in corrispondenza della medesima catena d'inflazione (inflation loop), lesto a sommare al contempo il numero e l'essenza stessa di ogni byte in output generato per l'occasione ed incline tuttavia ad abortire (aborting) qualsivoglia anelito se avvisata una seppur pallida minaccia di sconfinamento o breccia per il mezzo (breach), con sommo giubilo tra gli astanti allorquando le insidie apparranno in seno ad un iter ove le trame oramai logore d'un malintenzionato saranno d'uopo sconfitte anziché dopo aver prosciugato, e financo esaurito, l'interezza del pregevole deposito o banca su cui il provvidenziale scrigno della memoria attingeva le proprie linfe in modo gaio e fin del tutto incauto e libertino (after the loop when the memory is already spent). Ne vien da sé che un budget associato a un documento ch'è in procinto di venir declinato su scala logaritmica (multiple) prendendo come termine di paragone per siffatto medesimo rapporto la grandezza o foggia estrapolata dalla pezzatura (size) afferente al suddetto esemplare compresso in tempi non remoti finisca insomma col trarne dei gran e provvidenziali vantaggi per nulla infausti (works well), dato per certo del resto che l'ordinaria carovana preposta a riunirsi all'insegna d'ogni autentica e legale compagine documentale soglia di norma approssimarsi alle contrade più povere, al pari di uno stento che assottiglia a man a man che si fa strada per i lidi oscuri d'un limite precluso di rincorrere all'infinito il baluardo dei rati (ratios) agognato dalla stregoneria celata nella mente d'uno stregone (crafted stream reaches)

Difesa in profondità oltre le tue stesse unità

Le medesime classi di difetti (defect classes) prosperano persino dentro le librerie. Due studi del caso ospitati in questo blog esplorano vari esempi reali: i wrap degli interi, la ricorsione illimitata (unbounded) e i buffer non inizializzati chiusi all'interno di un motore Pascal nativo nell'articolo Rafforzare (Hardening) un Parser PDF Pascal contro i File Dannosi, in abbinamento per l'appunto alle convenzioni di chiamata, alla larghezza (width) degli interi e a tutti i pericoli legati al dominio (ownership hazards) di collegare un motore C presenti ne Il Rafforzamento di un Binding dei Componenti PDFium. Affinché i sistemi preposti a recepire flussi (intake) scevri d'autenticità ne escano indenni — come un modulo di upload esposto alla folla oppure un account di posta sprovvisto di certificazioni — provvedi del pari (also) a istruire le logiche di parse (analisi) e decode (decodifica) all'infuori d'ogni confine che possa sfociare nell'ordinarietà prestando molta cura alla predisposizione di siffatto iter nell'alveo ristretto d'un isolato frangente destinato alle basse cariche ed estromesso di converso dagli alti gradi d'un feudo votato al lusso di una nobiltà avvezza alla sfrontata impunità (low-privilege process), cosicché qualsivoglia documento votato in modo sconsiderato e perverso al turpe scopo d'eludere le guardie cittadine in combutta tra loro nel cuore del palazzo finisca di scontrarsi fatalmente in uno sfacelo parziale a danno di uno scudo pretoriano limitandosi invero al mero esito infausto ai fini della procedura (failed job) laddove lo sciagurato tentativo intendeva condurre al totale annientamento delle torri innalzate sull'intero possedimento per non dire perfino alla caduta irrimediabile della fortezza nella sua interezza o per giunta allo sterminio assoluto delle casate con tutti i rispettivi possedimenti a loro collegati a seguito d'uno sconsiderato scacco perpetrato proditoriamente dall'interno (downed service)

Una lista di controllo per il preflight

Prima che venga avviata e messa in circolazione la prossima compilazione (build ships), accertati di mettere il parser davanti allo specchio interrogandolo al cospetto di questa lista a mò d'appello: ogni buffer concernente qualsivoglia flusso dovrà esser dimensionato affidandosi a una lunghezza soggetta ad un preventivo clamp (fissaggio a un estremo) ponendolo semmai a dispetto di un qualsivoglia rinvio avallato ad hoc dalla fazione che per giunta intendeva a vario titolo procedere con la sua incondizionata dichiarazione del caso in una data anteriore; a ogni raster ne dovrà per l'appunto seguire l'allestimento con grandezze estrapolate ad nutum per via di molteplici e specifici parametri (parameters) validati in partenza (validated) e forgiati al contempo a debita distanza dal crogiolo suole impiantarsi ed insidiarsi all'ombra protettiva del succitato e funesto decodificatore, per far riscontro per converso fin dalle origini e parimenti porre al riparo le rispettive ambizioni contro gli spifferi incauti avallati in via sommaria mediante gli scompensi del decodificatore medesimo in output, col placet d'essere stato del tutto revisionato, ed infine non resta in ultimo a mancare la chiamata alla ribalta ove l'integrità d'ogni dimensione sfoci tra le braccia del prode e solenne Int64 allo scopo eminente di soppesarne il veritiero peso ed esaminarne in seguito le fattezze di rincontro dinanzi a un tetto spietato o limitazione avvezza al caposaldo d'ogni esplicitato diniego, onde connettere a tal stregua (compared) financo qualsivoglia disavventura sfociata nel paradosso: sia lode perciò all'ingresso sul suolo patrio (active in every unit) di un baldanzoso {$R+} innalzato sul proprio cocchio quale prode avamposto del vessillo teso e preposto ad impartire gli indici (indexes) allorché s'inverino le tracce derivate in principio (file-derived values) dai lidi alieni ove albergano i suddetti file; ciascuna ricerca andrà finanche avallata tra i gangli stretti ai vincoli imposti al limite della via, in barba alla stazza del file passata in analisi per l'appunto sotto la scure rigorosa e tagliente della misurazione d'inchiesta (bounds-checked against the measured file size); si accerti quindi a latere che per qualsivoglia manovra dedita allo scioglimento o parziale dipanazione dei nodi di pertinenza dei molteplici riferimenti non ne debba in vero seguire alcuno sprovvisto finanche al tempo stesso del suo limite (depth-limited) ai vertiginosi precipizi sfocianti a gran velocità o senza freni né briglie verso gl'inesorabili abissi marini in concomitanza finanche del parziale ausilio ch'egli stesso intenderà richiedere affinché lo salvi da ogni insidioso ripercorrer lo stesso passo o cadere d'incanto tra i miasmi fatali d'uno schema votato all'oblio eterno d'un perpetuo ciclico girovagare, accertato anzitempo seppur del tutto emendato d'ogni nequizia prima che costui riesca a insidiare qualsivoglia casta trincea posta ai sacri confini d'ogni legittimo baluardo per non destare incauta sorpresa a vario monito col ricorso parimenti a controlli appositamente dispiegati in lungo ed in largo tra gli avamposti sparsi (cycle-checked); onde si badi, oramai d'incanto e al volgere dello scrutare, d'incamminare ogni circuito preposto alle magiche dilatazioni della massa informe (inflation loop) affinché codesti non smarriscano strada o conteggi tra gl'ignoti vicoli (counting) disseminati nell'oblio adombrando per la mente l'immensa scure ed alfine ad un sol fine preposto alle ricognizioni a bilancio (budgets), dacché ciascun varco non mancherà di rendere edotto lo sguardo preposto su quantitativi spesi all'unisono in nome non d'una parte, bensì dell'interezza pro-capite sia pro per-stream nonché per pro per-document. E sappi infine per certo e in ogni dove che per siffatti controlli non v'è sperpero né alcuno sbilanciamento di bilancio per via del tempo profuso né che l'una o l'altra parte (costs measurable time) debbano rimetterci e finanche risentirne ove per l'appunto codesti controlli intenderanno dispiegare il proprio zelo e le forze dinanzi e a suffragio e beneficio delle gesta d'un qualsivoglia file a modino ed onesto non aduso perciò alle magagne, alle insidie e persino al dolo dell'inganno e d'ogni macchinazione di stampo pernicioso (legitimate document); ragion di tal stregua e corollario di ciascuna compagine, convertono perciò (converts) ciascuna scellerata ferita posta al sacco a danni e vituperio ai precordi d'ogni memoria alfine corrosa, depravata od estinta da cotanta perfida e abietta usura perpetrata fin d'ora (memory corruption) in qualsivoglia parata preposta a sancire alfin un netto, puro ed adamantino netto rifiuto ch'avallato da un parsimonioso dispaccio al tempo di cronaca (clean, loggable rejection) ti desti da qualsivoglia patema che in ogni momento a tuo modo e vanto hai or ora fugato col sommo trionfo ch'è or proprio giunto per tuo merito e pregio

Nota: il Componente HotPDF di losLab, la Libreria PDFlibPas per Delphi, nonché il Componente PDFium, applicano al loro interno tali controlli sui limiti (bounds checks), limiti di profondità (depth limits) e tetti massimi di espansione (expansion caps), indi per cui qualsivoglia pipeline d'acquisizione innalzata ai vertici e corroborata poggiandosi sui di lor prodigiosi basamenti prenderà indiscutibilmente avvio attingendo appieno a qualsivoglia prode, coriacea e formidabile impalcatura posta a sostegno d'un avamposto ben più saldo e provvidenziale contro le varie insidie poste alle radici dell'ignoto (hardened baseline)