Sicurezza e architettura del programma di caricamento classi

Precedente 1 2 Pagina 2 Pagina 2 di 2

Caricatori di classi e spazi dei nomi

Per ogni classe che carica, la JVM tiene traccia di quale class loader, primordiale o oggetto, ha caricato la classe. Quando una classe caricata fa prima riferimento a un'altra classe, la macchina virtuale richiede la classe di riferimento dallo stesso programma di caricamento classi che ha caricato originariamente la classe di riferimento. Ad esempio, se la macchina virtuale carica la classe Volcanotramite un particolare programma di caricamento classi, tenterà di caricare qualsiasi classe a cui si Volcanofa riferimento tramite lo stesso programma di caricamento classi. Se si Volcanoriferisce a una classe denominata Lava, magari invocando un metodo in classe Lava, la macchina virtuale richiederà Lavaal caricatore di classi che ha caricato Volcano. La Lavaclasse restituita dal programma di caricamento classi è collegata dinamicamente alla classe Volcano.

Poiché la JVM adotta questo approccio al caricamento delle classi, per impostazione predefinita le classi possono vedere solo altre classi che sono state caricate dallo stesso programma di caricamento classi. In questo modo, l'architettura di Java consente di creare più spazi dei nomi all'interno di una singola applicazione Java. Uno spazio dei nomi è un insieme di nomi univoci delle classi caricate da un particolare programma di caricamento classi. Per ogni programma di caricamento classi, la JVM mantiene uno spazio dei nomi, che viene popolato dai nomi di tutte le classi che sono state caricate tramite quel programma di caricamento classi.

Una volta che una JVM ha caricato una classe denominata Volcanoin un particolare spazio dei nomi, ad esempio, è impossibile caricare una classe diversa denominata Volcanonello stesso spazio dei nomi. VolcanoTuttavia, è possibile caricare più classi in una JVM, poiché è possibile creare più spazi dei nomi all'interno di un'applicazione Java. Puoi farlo semplicemente creando più caricatori di classi. Se crei tre spazi dei nomi separati (uno per ciascuno dei tre Volcanoprogrammi di caricamento Volcanoclassi ) in un'applicazione Java in esecuzione, caricando una classe in ciascuno spazio dei nomi, il tuo programma potrebbe caricare tre classi differenti nell'applicazione.

Un'applicazione Java può istanziare più oggetti del programma di caricamento classi dalla stessa classe o da più classi. Può, quindi, creare tanti (e tanti diversi tipi di) oggetti di caricamento classi di cui ha bisogno. Le classi caricate da diversi programmi di caricamento classi si trovano in spazi dei nomi diversi e non possono accedere l'una all'altra a meno che l'applicazione non lo consenta esplicitamente. Quando si scrive un'applicazione Java, è possibile separare le classi caricate da origini diverse in spazi dei nomi diversi. In questo modo, è possibile utilizzare l'architettura del programma di caricamento classi di Java per controllare qualsiasi interazione tra codice caricato da origini diverse. È possibile impedire al codice ostile di accedere e sovvertire il codice amichevole.

Caricatori di classi per applet

Un esempio di estensione dinamica con i programmi di caricamento delle classi è il browser Web, che utilizza gli oggetti del caricatore di classi per scaricare i file di classe per un'applet attraverso una rete. Un browser Web attiva un'applicazione Java che installa un oggetto caricatore di classi, solitamente chiamato caricatore di classi di applet , che sa come richiedere file di classe da un server HTTP. Le applet sono un esempio di estensione dinamica, perché quando l'applicazione Java si avvia, non sa quali file di classe il browser chiederà di scaricare attraverso la rete. I file di classe da scaricare vengono determinati in fase di esecuzione, poiché il browser incontra pagine che contengono applet Java.

L'applicazione Java avviata dal browser Web di solito crea un oggetto caricatore di classe applet diverso per ogni posizione sulla rete da cui recupera i file di classe. Di conseguenza, i file di classe da diverse origini vengono caricati da diversi oggetti di caricamento classi. Questo li inserisce in diversi spazi dei nomi all'interno dell'applicazione Java host. Poiché i file di classe per applet da origini diverse sono collocati in spazi dei nomi separati, il codice di un'applet dannosa non può interferire direttamente con i file di classe scaricati da qualsiasi altra fonte.

Cooperazione tra i caricatori di classe

Spesso, un oggetto caricatore di classi si basa su altri caricatori di classi, per lo meno sul caricatore di classi primordiale, per aiutarlo a soddisfare alcune delle richieste di caricamento di classi che gli vengono incontro. Ad esempio, immagina di scrivere un'applicazione Java che installa un caricatore di classi il cui modo particolare di caricare i file di classe si ottiene scaricandoli attraverso una rete. Si supponga che durante l'esecuzione dell'applicazione Java, venga effettuata una richiesta del programma di caricamento classi per caricare una classe denominata Volcano.

Un modo per scrivere il programma di caricamento classi consiste nel fargli prima chiedere al programma di caricamento classi primordiale di trovare e caricare la classe dal suo archivio affidabile. In questo caso, poiché Volcanonon fa parte dell'API Java, si presume che il programma di caricamento classi primordiale non riesca a trovare una classe denominata Volcano. Quando il caricatore di classi primordiale risponde che non può caricare la classe, il caricatore di classi potrebbe quindi tentare di caricare la Volcanoclasse nel suo modo personalizzato, scaricandola attraverso la rete. Supponendo che il tuo programma di caricamento classi sia stato in grado di scaricare la classe Volcano, quella Volcanoclasse potrebbe quindi svolgere un ruolo nel futuro corso di esecuzione dell'applicazione.

Per continuare con lo stesso esempio, supponiamo che qualche tempo dopo un metodo di classe Volcanovenga richiamato per la prima volta e che il metodo faccia riferimento alla classe Stringdall'API Java. Poiché è la prima volta che il riferimento viene utilizzato dal programma in esecuzione, la macchina virtuale chiede al tuo class loader (quello che ha caricato Volcano) di caricare String. Come prima, il programma di caricamento classi prima passa la richiesta al programma di caricamento classi primordiale, ma in questo caso il programma di caricamento classi primordiale è in grado di restituire una Stringclasse al programma di caricamento classi.

Il class loader primordiale molto probabilmente non doveva essere effettivamente caricato Stringa questo punto perché, dato che Stringè una classe così fondamentale nei programmi Java, era quasi certamente utilizzato prima e quindi già caricato. Molto probabilmente, il programma di caricamento classi primordiale ha appena restituito la Stringclasse che aveva precedentemente caricato dal repository attendibile.

Poiché il caricatore di classi primordiale è stato in grado di trovare la classe, il caricatore di classi non tenta di scaricarlo attraverso la rete; si limita a passare alla macchina virtuale la Stringclasse restituita dal programma di caricamento classi primordiale. Da quel momento in poi, la macchina virtuale utilizza quella Stringclasse ogni volta che la classe fa Volcanoriferimento a una classe denominata String.

Caricatori di classi nella sandbox

Nella sandbox di Java, l'architettura del class loader è la prima linea di difesa contro il codice dannoso. Dopotutto, è il class loader che porta il codice nella JVM, codice che potrebbe essere ostile.

L'architettura del programma di caricamento classi contribuisce alla sandbox di Java in due modi:

  1. Impedisce al codice dannoso di interferire con il codice benevolo.
  2. Protegge i confini delle librerie di classi affidabili.

L'architettura del programma di caricamento classi protegge i confini delle librerie di classi attendibili assicurandosi che le classi non attendibili non possano fingere di esserlo. Se una classe dannosa potesse indurre con successo la JVM a credere che fosse una classe affidabile dall'API Java, quella classe dannosa potrebbe potenzialmente superare la barriera sandbox. Impedendo alle classi non attendibili di impersonare classi attendibili, l'architettura del programma di caricamento classi blocca un potenziale approccio per compromettere la sicurezza del runtime Java.

Spazi dei nomi e scudi

L'architettura del programma di caricamento classi impedisce al codice dannoso di interferire con il codice benevolo fornendo spazi dei nomi protetti per le classi caricate da diversi programmi di caricamento classi. Come accennato in precedenza, lo spazio dei nomi è un insieme di nomi univoci per le classi caricate che viene mantenuto dalla JVM.

Gli spazi dei nomi contribuiscono alla sicurezza perché puoi, in effetti, posizionare uno scudo tra le classi caricate in diversi spazi dei nomi. All'interno della JVM, le classi nello stesso spazio dei nomi possono interagire direttamente tra loro. Le classi in spazi dei nomi diversi, tuttavia, non possono nemmeno rilevare la presenza l'una dell'altra a meno che non si fornisca esplicitamente un meccanismo che consente alle classi di interagire. Se una classe dannosa, una volta caricata, avesse garantito l'accesso a tutte le altre classi attualmente caricate dalla macchina virtuale, quella classe potrebbe potenzialmente apprendere cose che non dovrebbe sapere o potrebbe interferire con la corretta esecuzione del programma.

Creare un ambiente sicuro

Quando si scrive un'applicazione che utilizza i programmi di caricamento classi, si crea un ambiente in cui viene eseguito il codice caricato dinamicamente. Se si desidera che l'ambiente sia privo di falle di sicurezza, è necessario seguire alcune regole quando si scrive l'applicazione e i programmi di caricamento classi. In generale, ti consigliamo di scrivere la tua applicazione in modo che il codice dannoso sia protetto da codice benevolo. Inoltre, vorrai scrivere class loader in modo tale da proteggere i confini delle librerie di classi affidabili, come quelle dell'API Java.

Spazi dei nomi e sorgenti di codice

Per ottenere i vantaggi in termini di sicurezza offerti dagli spazi dei nomi, è necessario assicurarsi di caricare le classi da origini diverse tramite caricatori di classi diversi. Questo è lo schema, descritto sopra, utilizzato dai browser Web abilitati per Java. L'applicazione Java avviata da un browser Web di solito crea un oggetto caricatore di classe applet diverso per ciascuna sorgente di classi che scarica sulla rete. Ad esempio, un browser userebbe un oggetto caricatore di classi per scaricare classi da //www.niceapplets.com e un altro oggetto caricatore di classi per scaricare classi da //www.meanapplets.com.

Protezione dei pacchetti limitati

Java consente alle classi nello stesso pacchetto di concedersi reciproci privilegi di accesso speciali che non sono concessi alle classi esterne al pacchetto. Quindi, se il tuo class loader riceve una richiesta per caricare una classe che con il suo nome si dichiara sfacciatamente di far parte dell'API Java (ad esempio, una classe denominata java.lang.Virus), il tuo class loader dovrebbe procedere con cautela. Se caricata, una tale classe potrebbe ottenere un accesso speciale alle classi fidate di java.lange potrebbe eventualmente utilizzare tale accesso speciale per scopi subdoli.

Di conseguenza, si scriverà normalmente un programma di caricamento classi in modo che si rifiuti semplicemente di caricare qualsiasi classe che dichiara di far parte dell'API Java (o qualsiasi altra libreria runtime attendibile) ma che non esiste nel repository attendibile locale. In altre parole, dopo che il caricatore di classi ha passato una richiesta al caricatore di classi primordiale e il caricatore di classi primordiale indica che non può caricare la classe, il caricatore di classi dovrebbe controllare per assicurarsi che la classe non si dichiari membro di un pacchetto affidabile. In caso affermativo, il programma di caricamento della classe, invece di provare a scaricare la classe attraverso la rete, dovrebbe generare un'eccezione di sicurezza.

Protezione di pacchi proibiti

Inoltre, potresti aver installato alcuni pacchetti nel repository attendibile che contengono classi che desideri che la tua applicazione possa caricare tramite il caricatore di classi primordiale, ma che non desideri siano accessibili alle classi caricate tramite il tuo caricatore di classi. Ad esempio, supponiamo di aver creato un pacchetto denominato absolutepowere di averlo installato sul repository locale accessibile dal programma di caricamento classi primordiale. Supponi anche di non volere che le classi caricate dal tuo programma di caricamento classi siano in grado di caricare qualsiasi classe dal absolutepowerpacchetto. In questo caso, dovresti scrivere il tuo class loader in modo che la prima cosa che fa è assicurarsi che la classe richiesta non si dichiari come membro delabsolutepowerpacchetto. Se viene richiesta una classe di questo tipo, il programma di caricamento classi, invece di passare il nome della classe al programma di caricamento classi primordiale, dovrebbe generare un'eccezione di sicurezza.

L'unico modo in cui un programma di caricamento classi può sapere se una classe proviene o meno da un pacchetto limitato, come java.lang, o da un pacchetto proibito, come absolutepower, è il nome della classe. Pertanto, a un programma di caricamento classi deve essere fornito un elenco dei nomi dei pacchetti limitati e vietati. Poiché il nome della classe java.lang.Virusindica che proviene dal java.langpacchetto ed java.langè nell'elenco dei pacchetti limitati, il programma di caricamento classi dovrebbe generare un'eccezione di sicurezza se il programma di caricamento classi primordiale non è in grado di caricarlo. Allo stesso modo, poiché il nome della classe absolutepower.FancyClassLoaderindica che fa parte del absolutepowerpacchetto e il absolutepowerpacchetto è nell'elenco dei pacchetti proibiti, il programma di caricamento classi dovrebbe generare un'eccezione di sicurezza.

Un programma di caricamento di classi attento alla sicurezza

Un modo comune per scrivere un programma di caricamento classi orientato alla sicurezza consiste nell'utilizzare i seguenti quattro passaggi:

  1. Se esistono pacchetti da cui questo programma di caricamento classi non è autorizzato a caricare, il programma di caricamento classi controlla se la classe richiesta si trova in uno di quei pacchetti proibiti menzionati sopra. In tal caso, genera un'eccezione di sicurezza. In caso contrario, continua con il passaggio due.

  2. Il programma di caricamento classi passa la richiesta al programma di caricamento classi primordiale. Se il programma di caricamento classi primordiale restituisce correttamente la classe, il programma di caricamento classi restituisce la stessa classe. In caso contrario, continua con il passaggio tre.

  3. Se esistono pacchetti affidabili a cui questo programma di caricamento classi non è autorizzato ad aggiungere classi, il programma di caricamento classi controlla se la classe richiesta si trova in uno di quei pacchetti limitati. In tal caso, genera un'eccezione di sicurezza. In caso contrario, continua con il passaggio quattro.

  4. Infine, il programma di caricamento classi tenta di caricare la classe in modo personalizzato, ad esempio scaricandola su una rete. In caso di successo, restituisce la classe. Se non ha successo, genera un errore "nessuna definizione di classe trovata".

Eseguendo i passaggi uno e tre come descritto sopra, il programma di caricamento classi protegge i bordi dei pacchetti attendibili. Con il primo passaggio, impedisce il caricamento di una classe da un pacchetto proibito. Con il passaggio tre, non consente a una classe non attendibile di inserirsi in un pacchetto attendibile.

Conclusione

L'architettura del class loader contribuisce al modello di sicurezza della JVM in due modi:

  1. separando il codice in più spazi dei nomi e posizionando uno "scudo" tra il codice in diversi spazi dei nomi
  2. proteggendo i confini delle librerie di classi affidabili, come l'API Java

Entrambe queste capacità dell'architettura del programma di caricamento classi di Java devono essere utilizzate correttamente dai programmatori in modo da trarre vantaggio dalla sicurezza che offrono. Per sfruttare lo scudo dello spazio dei nomi, è necessario caricare il codice da diverse origini tramite diversi oggetti del programma di caricamento classi. Per trarre vantaggio dalla protezione dei confini dei pacchetti attendibili, i programmi di caricamento delle classi devono essere scritti in modo che controllino i nomi delle classi richieste con un elenco di pacchetti limitati e vietati.

Per una panoramica del processo di scrittura di un programma di caricamento classi, incluso il codice di esempio, vedere l' articolo JavaWorld di Chuck McManis , "Le basi dei programmi di caricamento classi Java".

Il prossimo mese

Nell'articolo del mese prossimo, continuerò la discussione sul modello di sicurezza della JVM descrivendo il verificatore di classe.

Bill Venners scrive software professionalmente da 12 anni. Con sede nella Silicon Valley, fornisce consulenza sul software e servizi di formazione sotto il nome di Artima Software Company. Negli anni ha sviluppato software per i settori dell'elettronica di consumo, dell'istruzione, dei semiconduttori e delle assicurazioni sulla vita. Ha programmato in molti linguaggi su molte piattaforme: linguaggio assembly su vari microprocessori, C su Unix, C ++ su Windows, Java sul Web. È autore del libro: Inside the Java Virtual Machine, pubblicato da McGraw-Hill.

Ulteriori informazioni su questo argomento

  • Il libro The Java virtual machine Specification (//www.aw.com/cp/lindholm-yellin.html), di Tim Lindholm e Frank Yellin (ISBN 0-201-63452-X), parte di The Java Series (// www.aw.com/cp/javaseries.html), da Addison-Wesley, è il riferimento definitivo per Java virtual machine.
  • Calcolo sicuro con JavaNow e il futuro (un white paper) // www.javasoft.com/marketing/collateral/security.html
  • Domande frequenti sulla sicurezza delle applet

    //www.javasoft.com/sfaq/

  • Sicurezza di basso livello in Java, di Frank Yellin //www.javasoft.com/sfaq/verifier.html
  • La home page di Java Security

    //www.javasoft.com/security/

  • Vedi la home page delle applet ostili

    //www.math.gatech.edu/~mladue/HostileApplets.html

  • Il libro Java SecurityHostile Applets, Holes, and Antidotes, del Dr. Gary McGraw e Ed Felton, fornisce un'analisi approfondita dei problemi di sicurezza che circondano Java. //www.rstcorp.com/java-security.html
  • Articoli precedenti di "Roba da smanettoni":
  • The Lean, Mean Virtual Machine - Fornisce un'introduzione alla Java virtual machine.
  • The Java Class File Lifestyle - Offre una panoramica del file di classe Java, il formato di file in cui sono compilati tutti i programmi Java.
  • Garbage Collected Heap di Java: fornisce una panoramica della garbage collection in generale e dell'heap di Garbage Collection della Java virtual machine in particolare.
  • Bytecode Basics - Presenta i bytecode della Java virtual machine e discute i tipi primitivi, le operazioni di conversione e le operazioni di stack in particolare.
  • Aritmetica in virgola mobile: descrive il supporto a virgola mobile della macchina virtuale Java ei bytecode che eseguono operazioni in virgola mobile.
  • Logica e aritmetica: descrive il supporto della macchina virtuale Java per l'aritmetica logica e intera e i relativi bytecode.
  • Oggetti e array: descrive il modo in cui la Java virtual machine gestisce oggetti e array e discute i bytecode pertinenti.
  • Eccezioni: descrive come la macchina virtuale Java gestisce le eccezioni e discute i bytecode pertinenti.
  • Try-Infine: descrive come la Java virtual machine implementa le clausole try-finalmente e discute i bytecode pertinenti.
  • Flusso di controllo: descrive come la Java virtual machine implementa il flusso di controllo e discute i bytecode pertinenti.
  • The Architecture of Aglet - Descrive il funzionamento interno degli aglet, la tecnologia agent software autonoma basata su Java di IBM.
  • The Point of Aglets - Analizza l'utilità del mondo reale di agenti mobili come gli aglet, la tecnologia di agent software autonoma basata su Java di IBM.
  • Invocazione e restituzione del metodo: descrive i quattro modi in cui la Java virtual machine richiama i metodi, inclusi i bytecode pertinenti.
  • Sincronizzazione dei thread: mostra come funziona la sincronizzazione dei thread nella macchina virtuale Java. Discute i bytecode per entrare e uscire dai monitor.
  • Architettura di sicurezza di Java: offre una panoramica del modello di sicurezza integrato nella JVM e esamina le funzioni di sicurezza integrate della JVM.

Questa storia, "Security and the class loader architecture" è stata originariamente pubblicata da JavaWorld.