Diciamo che ho il seguente pseudocodice nella parte fidata di una sandbox che impedisce al codice non sicuro di chiamare mprotect()
e mmap()
e ptrace()
direttamente (mutext non è accessibile dalla memoria sandbox) ...
//src and dest are user controlled but must be valid.
TrustedPart_for_safe_jit(void * mutext, uint8_t *src,uint8_t *dest, uint32_t size) // in the current case, *dest targets a PROT_NONE memory region
{
MutexLock(mutext);
ValidateOpcodesOrCrash(src,size); // uses calls to mmap on size internally. Contains many different loops and use several 10k thousands lines of codes in the trusted part of the sandbox : this is the longest part. Please also note that src is write protected while being in this function.
unwriteprotect(dest,size); // calls many sandbox’s internal functions
SafeMemcpy(src,dest,size); // THIS IS the function which contains the race condition
asm("mfence");
unEXECprotect(dest,size); // involve write protecting as well as allowing reading
MutexUnlock(mutext);
}
SafeMemcpy(uint8_t *src,uint8_t *dest, uint32_t size) // the data to be copied cannot exceed 128Mb
{
if(!CheckUserTargetPointToValidMemroyRange(dest,size) {
uint8_t *src_ptr=src;
uint8_t *dest_ptr=dest;
uint8_t *end_ptr=des+size;
while (dest_ptr < end_ptr) { // that loop should execute very fast
*(uint32_t *) dest_ptr = *(uint32_t *) src_ptr;
dest_ptr += 4;
src_ptr += 4;
}
}
}
Questa parte è responsabile di consentire al codice non attendibile di utilizzare la compilazione ᴊɪᴛ.
Il punto è thread non attendibile non sono sospesi.
Come sai, quando 2 thread utilizzano memcpy()
con la stessa destinazione, generano dati casuali. In tal caso, tali dati potrebbero potenzialmente contenere istruzioni come int 0x80
, consentendo così di uscire dalla sandbox.
Cose che ho pensato fino ad ora:
- Scrive i dati in un file e leggerli con la chiamata di sistema di lettura attraverso la sandbox. Se la memoria è ancora protetta da scrittura, il programma non si blocca. Ciò implicherebbe il looping e anche se i dati da copiare possono essere 128Mb di grandi dimensioni non sono sicuro che funzionerebbero a causa dell'overhead di syscall.
Un'alternativa sarebbe creare codice diverse volte e provare a leggere con diversi tempi , ma non ho idea di come selezionare la finestra temporale iniziale. - Usa futex ... Ma non sono riuscito a trovare se il futex può essere usato per controllare il valore della memoria non allocata. Non sono sicuro se il thread potrebbe svegliarsi prima che la memoria diventi protetta da scrittura.
Quindi, è possibile pianificare la finestra temporale per le condizioni di gara di memcpy?