
Exploit de escalonamento local de privilégios para CVE-2023-0386 visando overlayfs do kernel Linux. Inclui análise detalhada da vulnerabilidade, código PoC e guia de exploração passo a passo usando FUSE e namespaces de usuário.
gcc -Wall exp.c `pkg-config fuse --cflags --libs` -o exp
./exp /tmp

O conhecimento teórico deste artigo (namespaces, sistema de arquivos overlay, sistema de arquivos fuse etc.) vem do chatGPT.
Identificador da vulnerabilidade: CVE-2023-0386
Produto afetado: linux kernel - sistema de arquivos overlay
Versões afetadas: 5.11 ~ 5.19
Pré-requisitos: poder usar unshare ou criar um sistema de arquivos overlay
Impacto: escalonamento local de privilégios
Compilar o kernel manualmente:
Prepare um kernel dentro da faixa de versões vulneráveis, exceto a 5.15 (a 5.15 parece ter problemas), habilitando os dois sistemas de arquivos, overlay e fuse:
CONFIG_SLUB_DEBUGOVERLAY_FS
CONFIG_FUSE_FS
O Ubuntu 21.10 com kernel 5.13.0-16-generic foi testado e funciona:

Antes de analisar a vulnerabilidade, vamos fazer o chatGPT interpretar um especialista em kernel Linux:
(Pergunta ao chatGPT: a partir de agora você fará o papel de um especialista em kernel Linux e me ajudará a responder algumas perguntas)
As informações públicas sobre a vulnerabilidade são escassas; as mais diretas são as do patch. O link para o patch é:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f11ada10d0a

Podemos ver que uma verificação foi adicionada na função ovl_copy_up_one. Primeiro vamos perguntar ao chatGPT o que essa função faz:

Portanto, essa função atua na operação de copiar um arquivo da camada inferior para a camada superior no sistema de arquivos overlay. Agora, analisando o contexto, vejamos a verificação adicionada pelo patch:
static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
int flags)
{
int err;
DEFINE_DELAYED_CALL(done);
struct path parentpath;
struct ovl_copy_up_ctx ctx = {
.parent = parent,
.dentry = dentry,
.workdir = ovl_workdir(dentry),
};
if (WARN_ON(!ctx.workdir))
return -EROFS;
ovl_path_lower(dentry, &ctx.lowerpath);
err = vfs_getattr(&ctx.lowerpath, &ctx.stat,//[1] 获取底层文件系统的stat
STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
if (err)
return err;
//[2]补丁新加判断文件的stat属性中的用户id和用户组id是否在当前命名空间有映射
if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
!kgid_has_mapping(current_user_ns(), ctx.stat.gid))
return -EOVERFLOW;
[1] Primeiro, a função vfs_getattr é usada para obter os atributos do arquivo de destino no sistema de arquivos inferior. A função vfs_getattr recebe uma estrutura struct path de um arquivo e retorna a estrutura struct stat correspondente a esse arquivo.
[1.1] ctx.lowerpath é o caminho de um arquivo no sistema de arquivos inferior dentro do overlayfs; o sistema de arquivos overlay será apresentado mais adiante.
[1.2] A estrutura struct stat armazena informações de metadados do arquivo, incluindo o proprietário e o grupo do arquivo, entre outras. As informações do proprietário obtidas serão verificadas na verificação adicionada pelo patch abaixo.
[2] Em seguida, a função kuid_has_mapping é chamada para verificar as informações de proprietário e grupo do arquivo recém-obtidas, determinando se o proprietário e o grupo do arquivo de destino possuem mapeamento no namespace de usuário atual.
[2.1] A função kuid_has_mapping recebe dois parâmetros: uma estrutura struct user_namespace e uma estrutura struct kuid. Essa função verifica se as informações de usuário fornecidas possuem mapeamento no namespace de usuário fornecido. O mapeamento de usuários em namespaces será detalhado a seguir.
Portanto, sabemos que, ao executar a função vulnerável (ovl_copy_up_one), a operação falhará se o usuário proprietário ou o grupo proprietário do arquivo na camada inferior não tiver mapeamento no namespace atual.
Com isso, o princípio do patch fica claro. Mesmo assim, ainda precisamos resolver as seguintes questões para conseguir reproduzir a vulnerabilidade:
ovl_copy_up_one está envolvida, ou seja, a cópia de um arquivo da camada inferior para a camada superior no sistema de arquivos overlay?lowerpath — cujo proprietário é verificado quanto ao mapeamento — na cadeia lógica acima?Antes de responder a essas duas perguntas, precisamos esclarecer alguns conceitos básicos:
(Pergunta ao chatGPT: apresente os namespaces no kernel Linux, por favor)
No Linux, namespaces são um recurso do kernel usado para isolamento de recursos. Com eles, um grupo de processos pode parecer que está rodando em um ambiente de sistema independente, aumentando a segurança e o gerenciamento do sistema. Namespaces desempenham um papel fundamental em tecnologias de contêineres (como o Docker), permitindo que os contêineres rodem em ambientes isolados sem afetar outros contêineres ou o sistema principal.
O kernel Linux suporta 7 tipos de namespaces (mount, pid, net, ipc, user, time, cgroup), e cada um isola um tipo específico de recurso do sistema. Os namespaces são criados, modificados e gerenciados por uma série de chamadas de sistema (como clone, unshare e setns). Runtimes de contêineres (como o Docker) e outras ferramentas de virtualização utilizam esses recursos de namespace para fornecer ambientes de execução independentes e isolados aos contêineres.
Entre eles, a função de verificação kuid_has_mapping, adicionada pelo patch da vulnerabilidade, envolve o namespace de usuário (user namespace), um dos 7 namespaces mencionados acima.
(Pergunta ao chatGPT: apresente o namespace de usuário entre eles, por favor)
O namespace de usuário (User Namespace) é usado para isolar IDs de usuário (UID) e IDs de grupo (GID). Com ele, é possível usar conjuntos independentes de IDs de usuário e grupo em diferentes namespaces. Isso significa que usuários e grupos em um namespace de usuário podem ter IDs ou permissões diferentes em outro namespace. Ele melhora a segurança e o gerenciamento do sistema, especialmente em ambientes de contêineres.
A principal característica do namespace de usuário é o mapeamento de IDs: ele permite mapear UIDs e GIDs de um namespace para UIDs e GIDs de outro. Isso significa que, em namespaces de usuário diferentes, os mesmos UID e GID podem representar usuários e grupos diferentes. Por exemplo, um usuário root (UID 0) em um contêiner pode ser mapeado como um usuário sem privilégios no sistema principal.
Precisamos apenas lembrar dos seguintes pontos:
Por exemplo, usando o usuário breeze, crio um novo namespace de usuário e, dentro dele, ao verificar um arquivo de propriedade de root, o grupo exibido é nobody: