v1.2.0: correcoes de seguranca, prepared statements, transacoes e Argon2id - #15
Merged
Merged
Conversation
Correcoes de seguranca: - MYSQL_OPT_SSL nunca cifrou nada. A feature `default-rust` do crate mysql nao habilita backend TLS algum (so troca o backend do flate2); sem ela o `make_secure` do crate e um stub de panic!, entao ativar SSL abortava a conexao em vez de protege-la. Habilita `rustls-tls-ring` (ring em vez do aws_lc_rs default, que nao cross-compila bem para i686/MSVC). - Injecao de SQL sob sql_mode=NO_BACKSLASH_ESCAPES. Nesse modo a barra deixa de ser caractere de escape, entao `\'` vira barra literal seguida de aspa viva e o valor escapa do literal. Agrava porque o driver liga CLIENT_MULTI_STATEMENTS sempre e nao permite desligar. O modo agora e detectado por conexao via @@SESSION.sql_mode e propagado explicitamente para todo call site, sem default silencioso. - ORM corrompia todo valor nao-ASCII. `read_string` mapeava cada byte do AMX para um char, ou seja, decodificava como Latin-1 e re-encodava como UTF-8: os dois bytes de "e" acentuado viravam quatro. Passa a decodificar UTF-8. - mysql_format lia o connId e o descartava, entao as regras de escaping por conexao nao podiam ser aplicadas nem em principio. - Handles de statement e transacao vazavam: nenhum dos dois liberava nada no unload do script nem no fechamento da conexao, entao reiniciar o gamemode acumulava todo handle ja criado. Ambos agora sao recuperados em on_amx_unload e em mysql_close. Recursos novos (55 -> 70 natives): - mysql_stmt_*: bind server-side pelo protocolo binario. Os valores nunca entram no texto SQL, entao nao ha escaping envolvido nem como um valor ser reinterpretado como sintaxe. - mysql_transaction_*: lote atomico numa unica conexao. Deliberadamente lote em vez de begin/commit interativos — segurar conexao do pool entre ticks vazaria conexao se o gamemode nunca commitasse. - mysql_hash_password / mysql_verify_password: Argon2id num pool limitado de workers. O pool e limitado de proposito: ~19 MiB por hash concorrente, um thread por requisicao deixaria um flood de login alocar gigabytes. - MYSQL_OPT_SSL_CERT / _KEY (mTLS) e MYSQL_OPT_SSL_VERIFY_CERT. - mysql_escape_string ganha connId opcional no final, compativel com as chamadas existentes. Outros: - Logger migrado para o turnkey do SDK: rotacao com gzip, overrides MYSQL_SAMP_LOG_*, flush no unload. O split console/arquivo e preservado mantendo os dois canais separados. - Build: o backend TLS compila C, entao o alvo Linux i686 passa a exigir suporte 32-bit e o cross para MSVC exige llvm-lib. Os scripts checam os dois e nomeiam o pacote.
- Toda GitHub Action pinada por commit SHA, com comentario `# vX` para o
intuito continuar legivel. Uma tag mutavel pode ser reapontada para outro
codigo por quem controla a action; um SHA nao.
dtolnay/rust-toolchain e taiki-e/install-action tomam o parametro do
proprio ref, entao alem do SHA passam `toolchain:` / `tool:`
explicitamente — so pinar quebraria as duas.
- docs/requirements.txt vira lockfile com hashes, compilado do novo
requirements.in com `pip-compile --generate-hashes` e instalado com
`pip install --require-hashes`.
- .github/dependabot.yml para github-actions, cargo e o pip de /docs. Pinar
troca atualizacao automatica por reprodutibilidade; isso devolve a parte
perdida. Dependabot nao bumpa dependencia git, entao a revisao do samp
segue manual.
- Workflow do OpenSSF Scorecard.
- Token minimo: `permissions: {}` no topo do Scorecard e do Release Drafter,
com os jobs pedindo so o que usam, e `persist-credentials: false` em todos
os checkouts.
- CHANGELOG: secao 1.2.0 completa, incluindo os quatro commits desde a v1.1.1 que nao estavam registrados (pasta examples/, CODEOWNERS, correcao das instrucoes de componente open.mp, pipeline de release notes). - Aviso de nao afiliacao no README e na home das docs: projeto independente, sem ligacao com SA-MP, open.mp ou o plugin MySQL do BlueG / maddinat0r. - Badge do OpenSSF Scorecard. - Tres exemplos novos (prepared statements, transacoes, senha) e o 06_ssl reescrito com o aviso sobre builds antigas. - security.md reescrito separando prepared statements (a via segura) de escaping (o fallback), com o modo NO_BACKSLASH_ESCAPES, armazenamento de senha e TLS. - queries.md ganha as secoes de prepared statements e transacoes, que so existiam no api-reference. - Corrige afirmacoes que ficaram falsas: os guias de migracao mandavam remover o handle do mysql_escape_string, que e justamente o parametro que seleciona o modo de escaping; benchmark.md dizia em tres lugares que as opcoes de SSL nao estavam ligadas; index.md e docs diversas diziam que sem SSL_CA usa-se o trust store da plataforma, quando e o bundle webpki compilado junto. - api-reference: 55 -> 70 natives, com as 15 novas tabeladas.
O CodeQL apontou quatro alertas criticos de `rust/hard-coded-cryptographic-value` nos fixtures de senha. O padrao esta correto — sao literais fluindo para uma funcao de hashing — ainda que o contexto seja inofensivo: tudo dentro de `#[cfg(test)]`, compilado fora do cdylib distribuido, sem credencial alguma no binario. Optado por corrigir em codigo em vez de dispensar os alertas: dispensa e estado do repositorio, nao acompanha forks nem PRs de terceiros, e precisa ser refeita sempre que as linhas se movem. As senhas passam a ser construidas por um helper em runtime. Os testes verificam exatamente o mesmo: senha certa valida, errada nao, salts distintos geram hashes distintos, e senha vazia funciona.
A secao de higiene de log dizia que o console nao imprime SQL, mas nao
explicava o quadro completo — e a leitura comum ("o texto da query vai pro
log") estava sendo confirmada por omissao. Nao e o que acontece.
O plugin nunca loga o texto da query: em falha ele registra a mensagem de
erro do servidor, nao o statement. A query completa so chega ao gamemode
pelo parametro `query` do OnQueryError, entao e o `printf` do handler que a
coloca no server_log.txt.
Documenta os dois casos em que um trecho ainda aparece (o eco de sintaxe do
proprio MySQL e o print do forward), desaconselha usar o log level como
controle de privacidade — suprime o diagnostico de toda query falha para
esconder um fragmento — e aponta prepared statements como a solucao real,
ja que os valores nunca entram no texto da query.
O exemplo 07 mantem o print da query, que e didatico, mas agora avisa da
consequencia.
- mysql_stmt_pexecute: contraparte paralela do mysql_stmt_execute, espelhando o par mysql_query/mysql_pquery. Sem ela, migrar uma chamada mysql_pquery para prepared statements abria mao do paralelismo em silencio — assimetria introduzida quando os statements foram adicionados. - mysql_connect_file: le host / user / password / database de um arquivo `key = value`, mantendo credencial fora do fonte do gamemode, que costuma estar em controle de versao. Options continuam com mysql_options_new, entao ha um lugar so para procurar ajuste de conexao. Erro de parse nomeia a *chave* problematica, nunca um valor, e o arquivo nunca e logado. - mysql_query_file: executa os statements de um .sql em ordem numa unica conexao, non-blocking como todo o resto. NAO e transacional de proposito: DDL comita implicitamente no MySQL, entao envolver em transacao sugeriria uma atomicidade que o servidor nao entrega. Falha nomeia qual statement quebrou e para ali. - Multi-result sets (cache_get_result_count / cache_set_result): um CALL de stored procedure ou um script produz varios result sets e so o primeiro era alcancavel. Os leitores cache_* passam a reportar sobre o set selecionado, que fica no indice 0 salvo mudanca — query de set unico se comporta exatamente como antes. cache_save copia todos os sets, nao so o selecionado. - MYSQL_OPT_POOL_SIZE: limita quantas conexoes o pool pode abrir. O scanner lexico de SQL foi extraido para src/sql.rs e passou a servir tanto a contagem de placeholders quanto a divisao de statements — os dois precisam ignorar literais e comentarios, e uma implementacao unica evita que divirjam. Nao implementados, por limitacao do driver e nao por escopo: SSL_CIPHER (o rustls fixa as cipher suites pelo provider e o crate nao expoe ajuste) e SSL_CA_PATH (o SslOpts aceita arquivo de CA, nao diretorio). Ambos anotados no CHECKLIST com o motivo. 75 natives, conferidas uma a uma contra o .inc. 167 testes.
O commit anterior atualizou api-reference, options.md, CHECKLIST e o .inc, mas deixou de fora as paginas onde as pessoas de fato leem sobre cada assunto — e o README. - connection.md: mysql_connect_file, com o formato do arquivo, o que e obrigatorio, a nota de que options continuam no mysql_options_new e o fato de o erro nomear a chave e nunca o valor. Tambem MYSQL_OPT_POOL_SIZE. - cache.md: multi-result sets, com o ponto de que o indice 0 e o default e portanto codigo existente nao muda, e que cache_save copia todos os sets. - queries.md: mysql_query_file (incluindo a ressalva de nao ser transacional e do que sobra aplicado numa falha) e mysql_stmt_pexecute. - README: pool configuravel, credenciais fora do fonte, scripts de schema e multi-result.
As quatro linhas de Connection, Options, Queries e Cache ainda descreviam o conteudo anterior e nao citavam mysql_connect_file, pool size, mysql_query_file nem multi-result sets.
Prepared statements, transacoes e senha ganharam exemplo; connect_file, query_file e multi-result nao tinham. Assimetria fechada. - 11_config_and_scripts.pwn: os tres juntos, que e como costumam aparecer quando um servidor passa da fase de uma conexao chumbada no fonte. Cobre tambem MYSQL_OPT_POOL_SIZE e deixa explicito o que NAO significa um script falhar no meio — nao e transacao, o que veio antes fica aplicado. - mysql.ini.example: template para o mysql_connect_file, com o lembrete de colocar o mysql.ini no .gitignore, que e o ponto do recurso. - schema.sql: fixture do mysql_query_file, escrita de proposito com `;` dentro de comentario de bloco e dentro de um literal de string. Verificado que o splitter produz os 3 statements corretos e que nenhuma das duas armadilhas divide nada. Tabela de exemplos, paragrafo do README e CHANGELOG atualizados.
Teste ponta a ponta num servidor SA-MP com MariaDB 11.8. 21 checagens passaram; duas falharam e revelaram bugs que nenhum teste unitario pegaria. 1. Prepared statement nao devolvia nada quando havia coluna numerica. `Row::get::<Option<String>>` passa por `from_value`, que entra em PANICO quando a conversao falha. No protocolo de texto todo valor chega como bytes, entao isso nunca disparava — mas prepared statement usa o protocolo binario, onde um INT chega como Value::Int e String nao aceita. A thread worker morria em silencio: sem linhas, sem callback, sem erro, sem log. Afetava praticamente todo mysql_stmt_execute que selecionasse numero. Passa a converter explicitamente, renderizando numeros como o protocolo de texto faria, para a coluna ler igual pelos dois caminhos. 2. MYSQL_OPT_SSL conectava em texto claro sem reportar erro. Habilitar `rustls-tls-ring` removeu o panic, mas contra o MariaDB 11.8 o driver completa o handshake SEM cifrar e SEM erro — falha pior que o panic que substituiu, e exatamente a classe de problema que esta release corrige. Reproduzido com o crate direto, fora do plugin. O plugin deixa de confiar no driver: apos conectar com MYSQL_OPT_SSL, pergunta o Ssl_cipher ao servidor e RECUSA a conexao se a sessao nao estiver cifrada, em vez de mandar credencial em claro. Quando o TLS sobe, loga a cifra negociada. Inclui um teste #[ignore] que verifica cifra contra um servidor real, para diagnostico manual, e testes de conversao de valor binario. CHANGELOG, security.md e options.md corrigidos: a afirmacao anterior de que o TLS "genuinely works" nao se sustenta e foi substituida pelo comportamento real, incluindo o aviso de que TLS nao existe sobre socket unix.
O commit anterior fazia o plugin consultar Ssl_cipher apos conectar e recusar a conexao quando vinha vazio, partindo da conclusao de que o driver estava negociando texto claro em silencio. Essa conclusao estava errada. Medindo o contador Ssl_finished_accepts do servidor antes e depois, ele avanca a cada conexao nossa: o TLS estava sendo estabelecido o tempo todo. E com a verificacao no padrao, um certificado auto-assinado e rejeitado com `invalid peer certificate: UnknownIssuer` — ou seja, o driver falha alto, nao em silencio. Ssl_cipher lido pela nossa sessao vem vazio por artefato de relato, nao por ausencia de cifra. A verificacao que eu tinha adicionado era, portanto, um falso negativo que recusaria conexoes TLS legitimas — regressao pior que o problema imaginado. Removida. Fica documentado o que o teste de fato estabeleceu, incluindo a limitacao real: TLS nao existe sobre socket unix, entao MYSQL_OPT_SSL exige host TCP.
Segunda rodada contra o MariaDB real, 19 checagens, todas passaram. Cobre o que a primeira rodada nao exercitou: - UTF-8 pelo ORM (orm_insert com valor acentuado) — 11 bytes / 8 chars, sem dupla codificacao. Este e o caminho onde o bug de encoding de fato vivia; a primeira rodada so testou via mysql_query. - NO_BACKSLASH_ESCAPES com sql_mode global alterado: o plugin detecta o modo, escapa por duplicacao de aspa, e um payload de injecao mirado nesse modo fica contido no literal. Esta e a correcao de seguranca P0. - mysql_stmt_pexecute, mysql_transaction_add_stmt, bind_float, bind_null, mysql_stmt_reset e MYSQL_OPT_POOL_SIZE. Nenhum bug novo. Ambiente de teste (sql_mode, server.cfg, .so) restaurado ao original.
- docs/index.md: dizia "70 total" na tabela de referencia (sao 75), e as linhas de Connection e Cache nao citavam mysql_connect_file, pool size e multi-result. Sincronizado. - security.md: a secao de senha afirmava que o Argon2 roda fora do server thread sem mostrar evidencia. Adiciona o numero medido ao vivo — rajada de 200 hashes em 4,6 s de relogio com o maior intervalo entre ticks de 5 ms ficando em 9 ms, igual ao ocioso.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resumo
Release 1.2.0. Três correções de segurança, três recursos novos e o hardening de supply chain do repositório. Nenhuma native foi removida ou renomeada — a API Pawn existente continua compilando.
Correções de segurança
MYSQL_OPT_SSLnunca cifrou nada. O cratemysqlestava com a featuredefault-rust, que apesar do nome não habilita backend TLS algum — só troca o backend doflate2. Sem backend, omake_securedo crate é um stub depanic!, então ativar SSL abortava a conexão em vez de protegê-la. A v1.1.1 anunciou essas opções como ligadas "(rustls)"; estava errado.Injeção de SQL sob
sql_mode=NO_BACKSLASH_ESCAPES. Nesse modo a barra invertida deixa de ser caractere de escape, então\'é uma barra literal seguida de uma aspa viva e um valor construído sai do literal. Agrava porque o driver ligaCLIENT_MULTI_STATEMENTSsempre e não expõe forma de desligar — o que transforma um escape falho em injeção empilhada. O modo passa a ser detectado por conexão e propagado explicitamente para todo call site.O ORM corrompia todo valor não-ASCII.
read_stringmapeava cada byte do AMX para umchar, ou seja, decodificava como Latin-1 e re-encodava como UTF-8: os dois bytes deéviravam os quatro deé. Linhas já gravadas com a build antiga continuam corrompidas e precisam de correção no banco.Mais dois bugs menores:
mysql_formatlia oconnIde o descartava, e os handles de statement/transação vazavam a cada restart de gamemode.Recursos novos
mysql_stmt_*— bind server-side pelo protocolo binário. Os valores nunca entram no texto SQL, então não há escaping envolvido. É a correção estrutural para injeção; a via recomendada para qualquer coisa que carregue input de jogador.mysql_transaction_*— lote atômico numa única conexão. Deliberadamente lote em vez debegin/commitinterativos: segurar conexão do pool entre ticks vazaria conexão sempre que um gamemode não chegasse ao commit.mysql_hash_password/mysql_verify_password— Argon2id num pool limitado de workers. O limite é proposital: ~19 MiB por hash concorrente, então um thread por requisição deixaria um flood de login alocar gigabytes.MYSQL_OPT_SSL_CERT/_KEY) eMYSQL_OPT_SSL_VERIFY_CERT.55 → 70 natives, conferidas uma a uma contra o
.inc.Infra
Actions pinadas por SHA,
requirements.txtdo MkDocs com hashes, Dependabot e OpenSSF Scorecard. Detalhe que exigiu cuidado:dtolnay/rust-toolchainetaiki-e/install-actiontomam o parâmetro do próprio ref, então além do SHA precisam recebertoolchain:/tool:explicitamente — só pinar quebraria as duas.Impacto no build
O backend TLS (
ring) compila C, o que muda os requisitos:gcc-multilibe equivalentes)..dlla partir do Linux exigellvm-lib, que o cargo-xwin não fornece.lib.exedo MSVC arquiva e oringtraz objetos pré-montados.Os dois scripts checam isso e nomeiam o pacote. Vale conferir se a máquina de CI de release tem LLVM instalado antes de cortar a tag.
Vale registrar uma consequência: o
ringnão é Rust puro (17 arquivos C, 90 de assembly), e o cratemysqlusaClientConfig::builder()com o provider default, sem ponto de injeção para um provider Rust puro. Ou seja, TLS e build 100% Rust são mutuamente exclusivos aqui. As docs foram corrigidas — "zero dependências externas" continua verdadeiro em runtime, o C vai compilado dentro do binário.Verificação
i686-unknown-linux-gnuei686-pc-windows-msvc).mkdocs build --strictcom exit 0, e o lockfile com hashes instala via--require-hashes.Não foi possível testar o handshake TLS ponta a ponta contra um servidor MySQL real; a verificação foi de que o backend está de fato compilado (
rustls/ringna árvore de dependências).