
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.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.
getsockopt não retornar um erro, ctl_ctloutput chama sooptcopyout para copiar o buffer de dados de volta ao espaço do usuário.