Sabe aquele frio na barriga quando você puxa um pen drive no momento errado e algo no sistema “dá ruim”? No kernel, o equivalente é bem mais sério: um use-after-free (UAF)—quando um pedaço de código ainda tenta usar um recurso que já foi liberado porque o dispositivo sumiu. É a receita clássica para crashes difíceis de reproduzir e CVEs nada divertidos.
Nas últimas semanas, surgiu uma proposta que mira exatamente esse problema, com um nome tão direto quanto a ideia: “revocable”. Ela introduz um pequeno (e engenhoso) alicerce de gestão de recursos revogáveis, construído sobre SRCU (Sleepable RCU), para que drivers façam acessos “fracos” a objetos que podem desaparecer a qualquer momento—e consigam verificar com segurança se ainda estão lá antes de usá-los.
O problema, sem rodeios
Em sistemas com hotplug (USB, mas não só), recursos podem “evaporar” enquanto alguém ainda tem um ponteiro para eles. Pense no cros_ec_chardev (o char device do ChromeOS EC): um processo mantém o arquivo aberto; o EC é removido; o ponteiro para a struct cros_ec_device
vira lixo; na próxima ioctl()
… boom, UAF.

A revocable entra aqui como um protocolo de gentileza entre quem fornece e quem consome um recurso. Em vez de o consumidor segurar um ponteiro “forte” (e potencialmente morto), ele passa a segurar uma alça revogável—uma espécie de weak reference que só vira um ponteiro utilizável dentro de um trecho protegido por SRCU. Se o provedor revogou o recurso, a tentativa de acesso retorna NULL, e o consumidor desiste de maneira limpa.
Como funciona, na prática
O design é minimalista:
- Provedor: aloca um
struct revocable_provider
com o ponteiro real do recurso.
Quando o dispositivo vai embora, o provedor chama algo como “revogar” (a série inicial usarevocable_provider_free()
, já falo do nome), que zera o ponteiro interno e faz umsynchronize_srcu()
—esperando todos os leitores em andamento saírem da área segura antes de, de fato, desmontar tudo. - Consumidor: aloca um
struct revocable
ligado ao provedor. Para usar o recurso, chamarevocable_try_access()
: se o recurso ainda existe, entra numa seção de leitura SRCU e devolve o ponteiro; se já foi revogado, devolve NULL. Ao sair, o consumidor chamarevocable_release()
e pronto.
Para tornar o padrão difícil de errar, há um macro REVOCABLE(rev, res)
que cria um bloco de uso único: você recebe res
já validado (ou NULL), usa dentro do bloco, e a release acontece automaticamente ao sair do escopo—até em caminhos de erro. É literalmente um “abra-fecha com garantia”.
Por baixo dos panos, o revocable_provider
mantém a SRCU (que permite dormir dentro da seção crítica—perfeito para caminhos de driver que não querem o custo/rigidez de um spinlock) e um kref simples para gerenciar o ciclo de vida da estrutura auxiliar. A semântica é a mesma que aprendemos a gostar em RCU: leituras baratas e uma sincronização explícita quando o lado escritor precisa mudar o mundo.
O caso-piloto: ChromeOS EC
A série de patches usa o cros_ec como “campo de provas”:
- O device do EC passa a expor um
revocable_provider
que aponta para a própriastruct cros_ec_device
. - O cros_ec_chardev vira consumidor: toda vez que precisa falar com o EC, entra num bloco
REVOCABLE()
, pega acros_ec_device*
se existir, e segue; se não existir, retorna-ENODEV
em vez de arriscar um UAF. - Há testes KUnit e kselftest cobrindo o básico (acesso válido), o caminho de revogação e o uso via macro. Ponto extra: explicar e testar bem esse tipo de primitivo vale ouro.
Entre a base e o topo: onde isso deve viver?
Aqui começou a parte animada do debate—como sempre, a melhor parte do kernel:
- Greg Kroah-Hartman (Greg KH) gostou da abordagem (“é o que queríamos há anos”), elogiou a presença de testes e documentação, e basicamente disse: vamos em frente. Também brincou que podemos discutir o nome do macro, mas “quem escreve, batiza”—sinal verde para experimentar.
- Danilo Krummrich (que implementou peça semelhante em Rust no kernel) trouxe duas provocações úteis:
- Nome da função de revogação:
revocable_provider_free()
soa como “liberar memória”, mas o que ela faz primeiro é revogar o recurso e só depois, quando o último consumidor se for, liberar. A sugestão é renomear para algo como*_revoke()
—mais fiel à semântica. - API simétrica com a versão em Rust: lá, existe um método estilo closure (tente-acessar-e-execute) que lembra muito o
REVOCABLE()
. O paralelismo ajuda a criar “memória muscular” de uso entre linguagens.
- Nome da função de revogação:
- Bartosz Golaszewski puxou a discussão para cima: ótimo arrumar cros_ec, mas o problema é sistêmico. Será que essa base serve para I²C, GPIO (onde já existe uma solução custom baseada em SRCU), e outras camadas grandes? Em outras palavras: não queremos só um helper simpático—queremos um padrão reutilizável.
- Laurent Pinchart foi além: por que não subir esse padrão para camadas de mais alto nível—por exemplo, o char device (cdev)? A corrida entre chamadas de syscalls e
.remove()
existe em diversos subsistemas (V4L2 que o diga). Se a lógica de “entrar/ sair da região segura” ficasse embutida nas rotas de cdev, drivers não precisariam “pintar” cada caminho manualmente; bastaria sinalizar a remoção e deixar o core bloquear novos acessos e drenar os em andamento. É um norte ambicioso: baixo nível disponível para casos especiais; alto nível fazendo o grosso.
O consenso que emerge—e eu concordo—é que a “Linux kernel revocable” deve existir como primitivo de fundação (drivers podem usá-la diretamente), mas a vitória grande virá quando subsystems (e, idealmente, cdev) a incorporarem, reduzindo a chance de cada driver reinventar seus próprios guard-rails.
Por que isso é importante?
Porque UAF por ciclo de vida desalinhado é uma dor antiga—e traiçoeira. Você pode despejar camadas de get/put
e locks e ainda perder uma corrida. O “revocable” troca esse jogo por um contrato claro:
- Consumidores só usam o recurso dentro de uma seção onde o provedor garante observabilidade estável ou nega o acesso com NULL.
- Provedores revogam e sincronizam sem medo de “puxar o tapete” de leitores invisíveis.
E como é SRCU, os consumidores podem dormir. Isso importa para caminhos de IO reais, não só para micro-benchmarks.
O que esperar a seguir
Minha aposta:
- Ajustes de UX da API—especialmente o
*_free()
→*_revoke()
—para evitar confusões mentais sobre “liberar vs. revogar”. - Mais estudos de caso: I²C, GPIO, talvez TTY e blocos multimídia (V4L2/DRM) onde o padrão de “arquivo aberto + dispositivo se foi” é cotidiano.
- Experimentos no cdev: mesmo que não dê para “mover tudo” de cara, encapsular o enter/exit no core diminuiria muito o risco de usos incorretos em drivers.
Se você escreve driver, a moral da história é simples: esse é um tijolo pequeno que resolve um problema grande. Comece onde dói—o seu caminho de char device, a sua ponte I²C, aquele buffer que às vezes “some”. Use a alça revogável em volta do acesso, trate NULL como “o device já era”, e siga. O kernel agradece; os backtraces, também.
Em suma: o “Linux kernel revocable” não é só mais um nome em include/linux/
. É uma mudança de postura sobre ownership e lifetime em hotplug. Se a comunidade empurrar isso para os lugares certos (dos drivers até o cdev), a gente troca uma classe inteira de UAFs por retornos de erro previsíveis—e noites de sono um pouco mais tranquilas.