Cosa può fare ESATTAMENTE il JIT Java?

2

C'è qualche documentazione che elenca esaustivamente le ottimizzazioni che il JIT Java può fare? Posso trovare facilmente articoli con esempi di ciò che il JIT può fare, ma voglio assicurarmi che sia non andando a ottimizzare un confronto di hash delle password e esponermi a un attacco di cronometraggio.

Il mio codice è vagamente:

Record record = SELECT user.hash, true as is_real FROM user 
                WHERE user.email = :email:
                UNION SELECT :dummy_hash:, false
                ORDER BY is_real desc
                LIMIT 1;
byte[] hash = record.getValue1();
boolean real = record.getValue2();
boolean hashCorrect = verify(plaintextPassword, hash);
return hashCorrect && real;

Voglio assicurarmi che Java passi il tempo a confrontare la password con il mio dummy-hash, quindi un utente malintenzionato non può enumerare un elenco degli indirizzi email dei miei utenti tentando di accedere con una password errata e vedere quale quelli restituiscono un 403 FORBIDDEN più rapidamente.

    
posta Andrew Rueckert 19.05.2018 - 01:35
fonte

3 risposte

3

No, non esiste un limite superiore alle ottimizzazioni che una JVM può eseguire. Pertanto, non è fondamentalmente possibile evitare ottimizzazioni. Preferisci invece strongmente l'utilizzo di funzioni dalla libreria standard Java, poiché potrebbero essere in grado di offrire garanzie di sicurezza aggiuntive sulla JVM per cui sono state progettate.

Nel tuo particolare esempio, verify() non può essere ottimizzato via interamente se esegue effetti collaterali, o se tutti i dati sono in qualche modo inclusi nel valore di ritorno. Per esempio. restituire un numero intero in cui alcuni bit indicano che il risultato della verifica forza il calcolo da eseguire. Tuttavia, questo non può impedire ottimizzazioni in quanto la tua funzione potrebbe essere inline.

Se le operazioni a tempo costante non sono possibili, considera che dormire a caso potrebbe anche essere una difesa sufficiente. Aumentando la varianza del tempo di risposta per entrambi i casi in modo che le distribuzioni temporali si sovrappongano, gli aggressori avrebbero bisogno di un numero eccessivo di tentativi per email per etichettarli come noti / sconosciuti con sufficiente sicurezza. L'obiettivo qui non è quello di rendere gli attacchi temporali impossibili, ma di renderli così costosi che il logging o il rilevamento delle intrusioni noteranno l'attacco e possono rispondere ad es. per limiti di velocità, divieti IP, notifiche utente o altri meccanismi.

    
risposta data 19.05.2018 - 10:25
fonte
2

Nessuna JVM conforme può eseguire un'ottimizzazione che causerebbe la modifica dell'ordine di effetti collaterali come specificato nella definizione della lingua. È disponibile qualsiasi altra ottimizzazione.

Nel tuo caso particolare, non sarebbe consentito ottimizzare la verifica dell'hash se c'è un effetto collaterale osservabile di testare il risultato 'reale'. Ad esempio, potresti ottenere questo risultato:

in qualche stato globale da qualche parte, aggiungi questo:

volatile AtomicBoolean lastTest;

quindi cambia il tuo codice in:

byte[] hash = record.getValue1();
AtomicBoolean real = new AtomicBoolean(record.getValue2());
lastTest = real;   // by making this assignment, the optimizer is no longer allowed 
                   // to assume that other threads cannot access or modify 'real',
                   // so all reads and writes must actually be performed

boolean hashCorrect = verify(plaintextPassword, hash);
return hashCorrect && real.getAndSet(false);

la VM non può quindi impedire che la verifica dell'hash abbia luogo poiché ciò potrebbe causare un effetto collaterale che potrebbe essere potenzialmente osservabile in un altro thread e che gli stati di definizione della lingua non dovrebbero accadere.

(Naturalmente, presumo che non ci siano informazioni disponibili per il compilatore che gli permettano di conoscere il risultato della chiamata di verifica senza doverlo effettivamente chiamare ... quindi la password in chiaro e l'hash provengono entrambi da fonti esterne e l'algoritmo hash è abbastanza complesso da non poter essere analizzato staticamente in anticipo per scoprire quali combinazioni di parametri danno risultati noti ... ma in qualsiasi sistema realistico di questo tipo questo è assolutamente vero)

    
risposta data 19.05.2018 - 10:59
fonte
1

Lo stai facendo con complessità inutile.

In primo luogo, hai una query SQL complessa, che dovrebbe essere evitata. La tua logica di business non dovrebbe essere scritta in SQL. Vorrei usare SQL solo per le query e implementare la logica in Java. Essenzialmente, il tuo codice sembra che tu non voglia implementare la logica in Java, e quindi esegui le modifiche SQL per mantenere il codice Java non modificato il più possibile. Perché è così?

In secondo luogo, stai sprecando preziosi cicli della CPU.

Aggiungi una sospensione casuale con un intervallo di tempo approssimativo adeguatamente definito, all'incirca lo stesso tempo necessario per verificare l'hash. Fallo se l'utente non viene trovato. Ciò rende impossibile per il cliente ottenere informazioni sui tempi, e salva anche preziosi cicli della CPU dormendo sinceramente invece che in loop. Potresti aggiungere un po 'di casualità comune ai percorsi di codice "non trovato" e "trovato" per renderlo ancora più sicuro, per impedire alle persone di accedere troppo velocemente.

Come dovrebbe essere la casualità? Per il percorso del codice "non trovato", ottenere la deviazione standard e la media dei tempi di calcolo dell'hash della password per molti campioni (ad es. 1000), quindi approssimare questo con un Distribuzione gaussiana . Per il percorso di codice comune, è necessario aggiungere più deviazione standard rispetto al percorso di codice "non trovato".

Inoltre, potresti assumere il ruolo di attaccante e provare a fare analisi statistiche per quanto sia difficile tramite le informazioni di cronometraggio possibile verificare se c'è un solo indirizzo email. Se ci vuole più di un minuto, sono sicuro che nessuno elencherà i tuoi indirizzi email, ma attaccherà solo quelli isolati. Se impiega più di un'ora, probabilmente non verranno interrogati anche quelli isolati.

Inoltre, potresti voler limitare in qualche modo le richieste provenienti da un singolo indirizzo IP. Vorrei utilizzare l'algoritmo token bucket per consentire le burst di accesso, ma limitare la velocità degli accessi a lungo termine. Ciò renderebbe anche più difficile enumerare gli indirizzi e-mail o persino interrogare quelli isolati.

    
risposta data 19.05.2018 - 12:11
fonte

Leggi altre domande sui tag