Resumen
El método calc_pow del servidor RPC de monerod (manejador cryptonote::core_rpc_server::on_calcpow en src/rpc/core_rpc_server.cpp) calcula la prueba de trabajo de un bloque que le pasa el cliente en el parámetro block_blob. El manejador comprobaba que el blob se pudiera interpretar, pero no su longitud mínima. Con major_version 7 el cálculo usa Cryptonight variante 1, que necesita al menos 43 bytes de datos; si recibe menos, el código de src/crypto/slow-hash.c no devuelve un error, sino que termina el proceso con _exit(1).
El resultado es que una sola petición con datos controlados por el cliente basta para cerrar el nodo entero. El fallo lo encontró Ada Logics con un arnés de fuzzing de los endpoints RPC de Monero, trabajo contratado por el MAGIC Monero Fund (comité de la organización benéfica MAGIC Grants), que lo remitió al equipo de Monero. MAGIC Grants renunció expresamente a la recompensa económica.
Cadena de llamadas hasta la terminación, según la traza del fuzzer:
cryptonote::core_rpc_server::on_calcpow src/rpc/core_rpc_server.cpp:2098
cryptonote::get_block_longhash src/cryptonote_core/cryptonote_tx_utils.cpp:702
cn_slow_hash src/crypto/hash.h:74
cn_slow_hash src/crypto/slow-hash.c:911
La salida problemática está en esta línea del código de entonces: https://github.com/monero-project/monero/blob/17f6fb871c09507cd13c23fdecd9cbcca3f01326/src/crypto/slow-hash.c#L139
Pasos de reproducción
- Arranca
moneroden una máquina local (RPC en el puerto por defecto, 18081). - Envía esta llamada JSON-RPC, con un
block_blobde solo 3 bytes (010101) ymajor_version7:
$ curl http://127.0.0.1:18081/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"calc_pow","params":{"major_version": 7, "height": 5, "block_blob": "010101", "seed_hash": "1111111111111111111111111111111111111111111111111111111111111111"}' -H 'Content-Type: application/json'
monerodse cierra mostrando el mensaje:
Cryptonight variant 1 needs at least 43 bytes of data
Impacto
Quien pueda llegar a la interfaz RPC del nodo puede provocar su caída con una única petición: el proceso monerod termina y deja de dar servicio hasta que se vuelva a arrancar. El reporte lo describe como una posible denegación de servicio por cierre de monerod.
Remediación
El reporte figura como resuelto en HackerOne, pero no detalla el parche definitivo. Los autores proponen añadir la comprobación más arriba en la pila de llamadas, antes de llegar al hash. Como referencia, este es el cambio que usan ellos para que el fuzzer no se detenga en este fallo (los números de línea pueden variar): rechazar en on_calcpow cualquier blob de menos de 43 bytes con el mismo error que un blob inválido.
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -2074,6 +2075,12 @@ namespace cryptonote
error_resp.message = "Wrong block blob";
return false;
}
+ // std::cout << "Blob data: " << blockblob.size() << " bytes" << std::endl;
+ if (blockblob.size() < 43) {
+ error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
+ error_resp.message = "Wrong block blob";
+ return false;
+ }
if(!m_core.check_incoming_block_size(blockblob))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE;
Qué aprender de este caso
- Busca
exit(),_exit(),abort()oassertdentro de funciones criptográficas o de bajo nivel y sigue hacia atrás quién las llama: si algún endpoint RPC o API les hace llegar datos del cliente, cada una es un botón de apagado remoto. - Los métodos RPC que reciben un blob en hexadecimal y lo pasan tal cual a una función de hash o de parseo son un buen objetivo de fuzzing: prueba tamaños mínimos (1-3 bytes), vacíos y justo por debajo del límite que exige cada variante del algoritmo.
- Cuando una función interna tiene un requisito de tamaño (aquí, 43 bytes para Cryptonight variante 1), compruébalo en el manejador que recibe la petición y devuelve allí un error al cliente, en vez de confiar en que la capa baja lo gestione sin matar el proceso.
- Cuidado con los parámetros que eligen la rama de código (
major_versiondecidía la variante del hash): cada valor posible puede tener sus propias precondiciones, y hay que validar la entrada para todas ellas.