
CVE-2017-13868: Vazamento de informações de dados não inicializados do heap do kernel no XNU.
A função ctl_ctloutput no macOS High Sierra 10.13 ignora o valor de retorno de uma chamada a sooptcopyin, o que abre uma janela de corrida para vazar dados não inicializados do heap do kernel para o espaço do usuário. ctl_ctloutput-leak é uma prova de conceito que tenta desencadear esse vazamento de informações. A exploração requer privilégios de root.
Essa exploração foi confirmada para funcionar no macOS High Sierra 10.13.1 Beta 17B25c e no iOS 10.1.1 14B100 (sob mach_portal).
Aqui está a parte relevante de ctl_ctloutput no macOS High Sierra 10.13:
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP, // (a) data is allocated
M_WAITOK); // without M_ZERO.
if (data == NULL)
return (ENOMEM);
/*
* 4108337 - copy user data in case the
* kernel control needs it
*/
error = sooptcopyin(sopt, data, // (b) sooptcopyin() is
sopt->sopt_valsize, sopt->sopt_valsize); // called to fill the
} // buffer; the return
len = sopt->sopt_valsize; // value is ignored.
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit, // (c) The getsockopt()
kcb->userdata, sopt->sopt_name, // implementation is
data, &len); // called to process
if (data != NULL && len > sopt->sopt_valsize) // the buffer.
panic_plain("ctl_ctloutput: ctl %s returned "
"len (%lu) > sopt_valsize (%lu)\n",
kcb->kctl->name, len,
sopt->sopt_valsize);
socket_lock(so, 0);
if (error == 0) {
if (data != NULL)
error = sooptcopyout(sopt, data, len); // (d) If (c) succeeded,
else // then the data buffer
sopt->sopt_valsize = len; // is copied out to
} // userspace.
Este código faz o seguinte:
data da função getsockopt, sem especificar o flag M_ZERO para zerar os bytes alocados.getsockopt do espaço do usuário usando sooptcopyin, preenchendo o buffer de dados recém-alocado. Esta cópia deve sobrescrever completamente os dados alocados, razão pela qual o flag M_ZERO não era necessário. No entanto, o valor de retorno de sooptcopyin não é verificado, o que significa que é possível que a cópia tenha falhado, deixando dados não inicializados no buffer. A cópia poderia falhar se, por exemplo, o programa passasse um endereço não mapeado para getsockopt.getsockopt para este socket de controle do kernel. Essa implementação deve processar o buffer de entrada, possivelmente modificando-o e encurtando-o, e retornar um código de resultado. No entanto, a implementação é livre para assumir que o buffer fornecido já foi inicializado (já que teoricamente vem do espaço do usuário) e, portanto, várias implementações não modificam o buffer. A função NECP necp_ctl_getopt, por exemplo, simplesmente retorna 0 sem processar o buffer de dados.getsockopt não retornar um erro, ctl_ctloutput chama sooptcopyout para copiar o buffer de dados de volta ao espaço do usuário.Assim, ao especificar um endereço de dados não mapeado para getsockopt, podemos fazer com que um buffer do heap de tamanho controlado seja alocado, impedir que o conteúdo desse buffer seja inicializado e então alcançar uma chamada a sooptcopyout que tenta escrever esse buffer de volta ao endereço não mapeado. Tudo o que precisamos fazer para que a cópia de saída seja bem-sucedida é remapear esse endereço entre as chamadas a sooptcopyin e sooptcopyout. Se conseguirmos fazer isso, vazaremos dados não inicializados do heap do kernel para o espaço do usuário.
Acontece que essa é uma corrida bastante fácil de vencer. Durante os testes no meu Macbook Pro de 2015, o número médio de tentativas para vencer a corrida nunca foi superior a 600, e a mediana nunca foi superior a 5. No iOS 10.1.1 em um iPhone 7, a corrida foi ainda mais fácil de vencer, normalmente exigindo no máximo 2 tentativas. (Este teste foi realizado com DEBUG desativado, pois os printfs diminuem drasticamente a velocidade da exploração.)
Para compilar, execute make. Veja o início do Makefile para várias opções de compilação.
Execute a exploração especificando o tamanho alvo do vazamento na linha de comando:
$ sudo ./ctl_ctloutput-leak 128
000000: ef be ad de ef be ad de 00 00 00 00 00 00 00 00
000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000020: 00 00 00 00 00 00 00 00 01 00 00 00 40 80 00 00
000030: de 28 45 00 04 00 00 00 a0 ff 4a 26 80 ff ff ff
000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000050: 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000070: 00 00 00 00 00 00 00 00 ef be ad de ef be ad de
Relatei este problema à Apple em 7 de outubro de 2017. Foi atribuído o CVE-2017-13868. A Apple corrigiu os problemas no macOS 10.13.2 e no iOS 11.2.
O código ctl_ctloutput-leak é lançado em domínio público. Como cortesia, peço que, se você referenciar ou usar qualquer parte deste código, atribua-o a mim.