Home > java > Sicurezza in Java: il SecurityManager

Sicurezza in Java: il SecurityManager

6 novembre 2010

Translate in English with Google Translate
L’esecuzione di una classe Java richiede, ad opera della Java Virtual Machine, il compimento di 3 operazioni preliminari:

  1. caricamento della classe ad opera del ClassLoader
  2. controllo del codice ad opera del Verifier
  3. controllo dei permessi ad opera del SecurityManager

Di default l’esecuzione di una applicazione Java non prevede il controllo ad opera del SecurityManager mentre invece questo controllo è previsto in caso di esecuzione di una Applet.
L’obiettivo del SecurityManager è di verificare che le classi in esecuzione abbiano i permessi per eseguire direttamente o indirettamente i metodi richiamati. Di default i permessi sono definiti nel file java.policy che può essere personalizzato in base alle necessità. In base ai permessi viene concessa l’esecuzione di operazioni potenzialmente pericolose, ossia tutte quelle operazioni che possono svolgersi sul File System, connessioni di rete, sulla JVM ed altro.

Security Model

Security Model

La classe SecurityManager

Il controllo dei permessi è implementato di default nella classe SecurityManager che si trova nel package java.lang. Questa classe prevede l’implementazione di una serie di controlli di default che corrispondono a dei metodi di tipo void che in base al tipo di controllo da effettuare presentano un metodo con il prefisso check, per esempio checkAccess(), checkListen() ecc.

Di seguito i metodi della classe SecurityManager deputati ad effettuare questi controlli:

I controlli che possono essere effettuati dal SecurityManager sono di consentire al Thread corrente di:

  1. creare un nuovo ClassLoader
  2. richiedere halt della JVM
  3. leggere/scrivere/cancellare un file dal Files System
  4. aprire/accettare una connessione da un determinato host:port
  5. eseguire la stampa
  6. accedere alla clipboard
  7. effettuare un link verso una determinata libreria nativa
  8. accedere/caricare ad un determinato package/classe
  9. leggere/modificare determinate proprietà di sistema

Come si può notare i metodi di SecurityManager sono tutti di tipo void, non sono di tipo boolean come si poteva ipotizzare.

  • Nel caso in cui il controllo ha esito negativo verrà rilanciata una Exception di tipo unchecked, pertanto una RuntimeException del tipo SecurityException oppure NullPointerException.
  • Invece in caso di esito positivo non verrà eseguita alcuna azione e verrà eseguita l’operazione richiesta.

In una applicazione Java l’utilizzo del SecurityManager è disabilitato di default ma può essere abilitato in due momenti diversi:

  1. A Run-Time: utilizzando un parametro a riga di comando in fase di esecuzione della JVM
  2. java -Djava.security.manager NomeClasse
    
  3. A Compile-Time: instanziando la classe SecurityManager e configurando l’ambiente della JVM tramite la classe System come di seguito:
      SecurityManager sm = new SecurityManager();
      System.setSecurityManager(sm);

Passiamo agli esempi
Facciamo subito un esempio pratico in modo da arrivare subito al punto.
Abbiamo visto precedentemente che il SecurityManager effettua dei controlli sui permessi in caso di accesso al FileSystem ma sappiamo anche che di default questi controlli sono disabilitati in una applicazione Java.
Scriviamo una classe che non faccia altro che provare ad accedere ad un file e vediamo cosa succede.

import java.io.FileNotFoundException;
import java.io.FileReader;

public class Start {
    public static void main(String[] args) throws FileNotFoundException {
        FileReader fileReader = new FileReader("prova.txt");
    }
}

Se eseguiamo la classe Start abbiamo questo output:

Exception in thread "main" java.io.FileNotFoundException: prova.txt (No such file or directory)
	at java.io.FileInputStream.open(Native Method)
	at java.io.FileInputStream.(FileInputStream.java:106)
	at java.io.FileInputStream.(FileInputStream.java:66)
	at java.io.FileReader.(FileReader.java:41)
	at Start.main(Start.java:8)

Ovviamente il file prova.txt non è stato trovato e viene riportata l’eccezione FileNotFoundException seguita dallo stack di classi che sono state invocate. Questo ci è utile perchè ci sonsente di notare che la classe FileReader invoca il costruttore della classe FileInputStream.
Adesso vediamo cosa succede se utilizziamo il SecurityManager in modo programmatico nella classe Start che modifichiamo in questo modo:

import java.io.FileNotFoundException;
import java.io.FileReader;

public class Start {
    public static void main(String[] args) throws FileNotFoundException, InterruptedException {
        SecurityManager sm = new SecurityManager();
        System.setSecurityManager(sm);
        FileReader fileReader = new FileReader("prova.txt");
    }
}

Se eseguiamo la classe Start adesso abbiamo questo output:

Exception in thread "main" java.security.AccessControlException: access denied (java.io.FilePermission prova.txt read)
        at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
        at java.security.AccessController.checkPermission(AccessController.java:427)
        at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
        at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
        at java.io.FileInputStream.(FileInputStream.java:100)
        at java.io.FileInputStream.(FileInputStream.java:66)
        at java.io.FileReader.(FileReader.java:41)
        at Start.main(Start.java:12)

Adesso abbiamo una eccezione diversa: AccessControlException. Per capirla andiamo a vedere il costruttore della classe FileInputStream e scopriamo quanto segue:

public FileInputStream(File file) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }
    if (name == null) {
        throw new NullPointerException();
    }
    fd = new FileDescriptor();
    open(name);
}

Scopriamo che nel costruttore della classe FileInputStream viene effettuato un controllo,.che può essere superato solo se si hanno i permessi di lettura del file. Infatti il metodo checkRead() della classe SecurityManager riporta questo codice:

    public void checkRead(String file) {
        checkPermission(new FilePermission(file, SecurityConstants.FILE_READ_ACTION));
    }

Quindi, senza andare oltre, cerchiamo di capire cosa dobbiamo fare per poter ottenere i permessi dalla JVM per leggere i files dal File System. Che si fa adesso?
Soluzione: dobbiamo inserire il permesso di lettura nel file di policy java.policy che si trova:

  • di default nella cartella $JAVA_HOME/jre/lib/security
  • altrimenti verrà cercato nella cartella $HOME dell’utente
  • sempre che non venga stabilito diversamente dall’utente tramite la variabile -Djava.security.policy=/percorso/file/java.policy

Consideriamo il primo caso e modifichiamo il file di default java.policy presente nella cartella $JAVA_HOME/jre/lib/security ed aggiungiamo, al termine del file, il permesso di lettura ai files in questo modo:

grant {
    permission java.io.FilePermission "<<ALL FILES>><code>", "read";
};


Se eseguiamo la classe Start visualizziamo l’output di errore iniziale:

Exception in thread "main" java.io.FileNotFoundException: prova.txt (No such file or directory)
	at java.io.FileInputStream.open(Native Method)
	at java.io.FileInputStream.(FileInputStream.java:106)
	at java.io.FileInputStream.(FileInputStream.java:66)
	at java.io.FileReader.(FileReader.java:41)
	at Start.main(Start.java:11)

Questo ci fa capire che la gestione dei permessi permette di risolvere il problema degli accessi!
A questo punto vediamo il ClassDiagram delle classi che gestiscono i permessi e ritroviamo la classe FilePermission che viene invocata nel metodo checkRead() del SecurityManager:

A sua volta la classe astratta BasicPermission è ereditata da una serie di sotto classi, che vi mostro di seguito:

Ovviamente l’esempio fatto sul file è solo uno dei molti esempi che si potrebbero fare.
Sarebbe possibile creare una propria classe di policy referenziata da una nostra libreria interfcacciata da un nostro SecurityManager che si basa su un ClassLoader personalizzato e così via…

Rendere le cose gratuitamente complicate non aiuta ma sicuramente la personalizzazione dei permissi è possibile qualora fosse necessario. Basta saperlo!🙂

Categorie:java
%d blogger cliccano Mi Piace per questo: