v1.4.0: MYSQL_SYNC, diagnóstico de TLS, limites ajustáveis, ${ENV} e duas correções de segurança - #59
Merged
Merged
Conversation
A 3.5.0 esta publicada no registro, entao a dependencia sai da tag do git e passa a vir de la, com checksum no lockfile. Corrige de quebra uma divergencia antiga: a tag `v3.5.0` declarava `version = "3.4.0"` no proprio manifesto, e o `Cargo.lock` registrava 3.4.0 apontando para a tag v3.5.0. O modulo `mainthread` da 3.5.0 nao e adotado ainda: o `post` recebe closure sem parametro e nao ha rota para o estado do plugin a partir de uma worker thread, entao o canal `mpsc` do `QueryManager` continua.
Uma native registrada no `initialize_plugin!` e nao declarada no include compila sem erro e fica inalcancavel a partir do Pawn; o inverso falha so quando alguem chama. Nenhum dos dois aparecia em build ou teste. O teste novo compara as duas listas nos dois sentidos, a partir de arquivos que qualquer build ja produz. Um segundo teste confere que o include no estilo open.mp tem alias para toda native do base, que e o que pega um deslize na conversao de nomes do build.rs. O `SAMP_PAWN_INCLUDE` do SDK, que escreve as declaracoes de todas as natives registradas quando o servidor sobe, fica documentado no CONTRIBUTING como conferencia manual: precisa de servidor vivo, entao nao serve para o CI.
`mysql_tls_active(connId)` e `mysql_tls_cipher(connId, dest[], max_len)` informam o que o handshake negociou, e nao o que `MYSQL_OPT_SSL` pediu - a opcao e o pedido, estas sao a resposta. O valor e lido uma vez no `connect()`, na conexao que ja e aberta para detectar o `sql_mode`, entao nenhuma das duas custa query. Sessao sem cifra escreve string vazia e retorna false; o retorno separa esse caso do id inexistente. A correcao que vem junto e mais grave. Com `MYSQL_OPT_SSL = 1` contra `127.0.0.1`, `Ssl_cipher` voltava vazio: a conexao pedia TLS, nao reportava erro e trafegava em texto claro. A causa e o `prefer_socket` do driver, ligado por padrao. Depois do handshake ele move uma conexao de loopback para o socket unix do servidor, onde nada e cifrado - e as duas condicoes se encontram no arranjo local mais comum que existe. Pedir TLS agora desliga essa otimizacao; sem TLS ela permanece, que e onde ela ajuda. Medido contra MariaDB 11.8: `TLS_AES_256_GCM_SHA384` onde antes havia string vazia. Um teste fixa a armadilha, conferindo que o padrao do driver casa as duas condicoes e que o que o `connect()` monta com TLS nao as casa mais.
Os tres tetos que protegem a memoria do servidor estavam cravados no codigo: 1024 caches salvos, 100000 linhas por resultado e 4096 bytes por string do ORM. Numero fixo atende o caso comum e atrapalha os dois extremos - o relatorio que le mesmo uma tabela grande, e a maquina apertada que quer ser mais rigorosa. `mysql_limit_set(limit, value)` e `mysql_limit_get(limit)`, com as constantes MYSQL_LIMIT_SAVED_CACHES, MYSQL_LIMIT_RESULT_ROWS e MYSQL_LIMIT_ORM_STRING_LEN. Globais, nao por conexao: a memoria protegida e a do servidor, que todas compartilham. Os valores ficam em `src/limits.rs` como atomicos porque o teto de linhas e aplicado no `collect_result`, que roda em worker thread e nao tem rota para o estado do plugin. A leitura acontece por linha, entao `Relaxed` da conta. Valor tem de ser positivo: zero e recusado em vez de virar "ilimitado", ja que ilimitado e o estado que esses tetos existem para evitar. Baixar um teto mantem o que ja esta guardado e recusa o proximo. Bater o teto de linhas continua truncando com aviso, agora citando o teto vigente.
O `mysql_connect_file` ja tirava a senha do codigo do gamemode. Agora
`${NOME}` em qualquer valor e trocado pela variavel de ambiente, entao o
arquivo nomeia o segredo em vez de guarda-lo, e uma copia dele nao vale
nada sozinha.
Nome ausente expande para vazio e registra qual faltou: mandar o literal
`${MYSQL_PASSWORD}` ao servidor daria "access denied" e mandaria o
operador procurar no lugar errado. So `${...}` e referencia, entao senha
com cifrao continua funcionando, e a expansao vale em qualquer valor.
Junto vai uma correcao de mensagem: o caminho de conexao formatava o erro
do driver com o `Display` cru, entao `mysql_error()` devolvia
`Pool creation failed: MySqlError { ERROR 1045 (28000): ... }`. Passa a
usar o mesmo tratamento que o caminho de query ja tinha, entregando so a
mensagem do servidor.
Uma coluna VECTOR lida crua volta vazia para o Pawn, e o motivo nao e o tipo: um float32 comeca com bytes zero, e string Pawn termina no primeiro NUL. BLOB nao volta vazio, volta corrompido pela conversao para texto. Binario nao trafega em string Pawn. O caminho e converter no SQL: HEX() para blob, VEC_ToText() e VEC_DISTANCE_*() para vetor, que chegam como string e float normais. Verificado contra MariaDB 11.8 - nao ha nada a acrescentar no plugin. A tabela de limites da mesma pagina dizia numeros fixos e agora aponta as constantes MYSQL_LIMIT_*.
Tres lacunas, todas verificadas antes de virar texto: 1. `mysql_connect` bloqueia. A documentacao diz que toda query e nao-bloqueante, o que e verdade, mas abrir a conexao espera o handshake: cerca de 10 ms contra um banco local, 70 ms com TLS. A pagina de conexao passa a avisar que isso e aceitavel no OnGameModeInit e nao dentro de callback de jogador. 2. TLS falha fechado. Servidor que nao oferece criptografia faz o connect falhar, em vez de seguir em texto claro numa conexao que pediu cifra. 3. O caminho do `mysql_query_file` e relativo ao diretorio de trabalho do servidor, nao a `scriptfiles/`. A tabela de limites da pagina de seguranca passa a dizer "padrao" onde dizia "limite", com a ressalva de que da para estreitar e alargar, mas nao remover.
O padrao continua nao-bloqueante. `MYSQL_SYNC` no lugar do nome do
callback faz aquela chamada esperar o banco, e o resultado e o cache
corrente na linha seguinte, sem forward e sem public.
Nao e native nova porque uma `mysql_query_sync` termina copiada para
dentro de callback de jogador; um valor escrito na chamada declara a
intencao onde o custo e pago. Tambem nao podia ser argumento novo: o Pawn
recusa parametro depois de `{Float,_}:...`, e coloca-lo antes do callback
quebraria toda chamada existente. O valor e uma string que nenhum public
pode ter, ja que identificador nao aceita `!`.
O resultado vive num campo proprio do CacheManager, e nao na pilha de
ativos: a pilha e balanceada por um push por callback e um pop na volta,
entao um push extra feito de dentro de um callback seria removido por ele
e deixaria orfa a entrada de baixo. A pilha tem prioridade de leitura
sobre esse campo, porque so esta cheia enquanto um callback roda e ali o
cache corrente tem de ser o do callback.
Sincrono dentro de um callback e recusado: o resultado ou sombrearia o
cache do callback ou ficaria ilegivel ate ele voltar, e as duas saidas
surpreendem. Os usos que o recurso atende - schema no arranque, migracao,
comando de console - acontecem fora de callback.
Suportado em `mysql_query`, `mysql_pquery` e `mysql_query_file`. Nas
demais e recusado com aviso, e nao ignorado: sem a checagem o valor
viraria nome de callback inexistente e a chamada seria dispare-e-esqueca
silenciosa. Nas de senha a recusa tem motivo proprio, ja que a assinatura
nao tem buffer de saida e o Argon2id e lento de proposito.
Verificado em servidor: `SELECT SLEEP(1)` sincrono travou 1002 ms, quatro
statements de schema em sequencia deram o resultado correto sem corrida,
erro devolveu false e disparou OnQueryError, e o caminho assincrono segue
intacto.
O resultado de um `MYSQL_SYNC` ficava no lugar ate outra chamada bloqueante substituir ou alguem chamar `cache_unset_active()`. Num script que faz uma unica chamada dessas no arranque, que e o uso previsto, isso significa segurar o resultado pela vida do processo - e com o teto padrao em 100 mil linhas pode ser bastante memoria parada. Agora ele e liberado no inicio do tick seguinte. O servidor e single-threaded, entao um public do Pawn roda inteiro dentro de um frame e quem chamou ja retornou quando o proximo tick comeca. Isso da ao caminho bloqueante a mesma garantia deterministica que o assincrono tem do callback ao retornar. A regra fica enunciavel numa frase: o resultado vale no frame em que foi pedido; para guardar alem disso, `cache_save()`. Sem liberacao manual. Verificado em servidor: no mesmo frame o resultado e legivel; num timer 200 ms depois nao ha cache ativo; e o que foi guardado com `cache_save()` permanece.
Com o MYSQL_SYNC, "toda query e nao-bloqueante" deixou de ser verdade, e anunciar "suporta sincrono" convidaria exatamente o uso que o recurso dificulta de proposito. O texto passa a dizer as duas coisas juntas: nao-bloqueante por padrao, bloqueante quando a chamada pede. Paginas ajustadas: a abertura de queries, que agora lista os dois pontos que bloqueiam - o connect e o MYSQL_SYNC - em vez de tratar o connect como excecao unica; README, index, benchmark, CHECKLIST e a referencia da API, que ganhou a constante. O guia de migracao era o mais defasado: dizia que o `Cache:mysql_query` do R41-4 foi removido por design, sem dar alternativa a quem vem de la justamente atras da chamada bloqueante. Passa a mapear para `mysql_query(connId, query, MYSQL_SYNC)`, com a ressalva de que e recusado dentro de callback.
Bump do `Cargo.toml` para 1.4.0 e secao do changelog datada, cobrindo as 30 alteracoes desde a v1.3.0. Minor porque do lado Pawn tudo e aditivo: nenhuma native foi removida ou renomeada, entao gamemode da 1.3.0 compila sem mudanca. A excecao, destacada no primeiro paragrafo da secao, e o `OnQueryError`, que passou a entregar os argumentos na ordem declarada pelo forward - quem tratava esse forward lia os campos trocados e precisa conferir o handler. O changelog registra tambem as mudancas de dependencia (rust-samp 3.5.0 do crates.io, mysql 28.0.2, rustls 0.23.45 pelo RUSTSEC-2026-0285) e atualiza o estado dos dois avisos informativos: a correcao do `lru` esta mergeada no upstream desde 21/09 mas ainda nao lancada, e o `mysql` continua exigindo a faixa 0.16.
NullSablex
force-pushed
the
feat/backlog-2026-09
branch
from
September 26, 2026 18:10
dc04e49 to
1c51ee4
Compare
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.
Release 1.4.0. Aditiva do lado Pawn — nenhuma native foi removida ou renomeada, então gamemodes da 1.3.0 compilam sem mudança. Uma mudança de comportamento exige atenção, descrita logo abaixo.
Atenção antes de atualizar
OnQueryErrorentregava os cinco argumentos em ordem inversa. O forward é declaradoOnQueryError(errorid, error[], callback[], query[], connId), mas o gamemode recebia o id da conexão emerrorid, o texto da query emerror, a mensagem de erro emquerye o código do MySQL emconnId.A causa está no despacho: o
exec_public!empilha os argumentos ao contrário, que é a convenção da pilha do AMX, então a ordem listada na chamada é a ordem em que o public recebe — e o código listava começando pelo fim, como se precisasse inverter manualmente. O defeito existe desde a 1.0.0.Quem trata esse forward vinha lendo os campos trocados e precisa conferir o handler.
Novidades
MYSQL_SYNC— bloqueio explícito, por chamadaO padrão continua sendo não-bloqueante. Escrever
MYSQL_SYNCno lugar do nome do callback faz aquela chamada esperar o banco e deixa o resultado legível na linha seguinte:É um valor e não uma native porque uma
mysql_query_synctermina copiada para dentro deOnPlayerUpdate; um valor escrito na chamada declara a intenção onde o custo é pago. Também não podia ser um argumento novo: o Pawn recusa qualquer parâmetro depois de{Float,_}:..., e colocá-lo antes do callback quebraria todas as chamadas existentes.Três regras sustentam o recurso:
Aceito em
mysql_query,mysql_pqueryemysql_query_file.Diagnóstico de TLS
mysql_tls_active(connId)emysql_tls_cipher(connId, dest[], max_len)informam o que o handshake negociou, e não o queMYSQL_OPT_SSLpediu. O valor é capturado uma vez na abertura da conexão, então nenhuma das duas custa query. Sessão sem criptografia escreve cifra vazia e retornafalse.Limites de memória ajustáveis
mysql_limit_set(limit, value)emysql_limit_get(limit)tornam configuráveis os três tetos que antes eram fixos:MYSQL_LIMIT_SAVED_CACHES(1024),MYSQL_LIMIT_RESULT_ROWS(100000) eMYSQL_LIMIT_ORM_STRING_LEN(4096). São globais porque a memória protegida é a do servidor. Um valor precisa ser positivo —0é recusado em vez de significar "ilimitado" — e baixar um teto preserva o que já está guardado.${NAME}no arquivo de conexãoO
mysql_connect_filejá mantinha a senha fora do gamemode; agora o arquivo pode nomear o segredo em vez de guardá-lo:password = ${MYSQL_PASSWORD}Nome não definido expande para vazio e registra qual faltou, em vez de enviar o literal ao servidor e produzir um "access denied" enganoso. Apenas
${...}é referência, então senha com cifrão continua válida.Outras correções
TLS em host de loopback rodava sem criptografia.
MYSQL_OPT_SSL = 1contra127.0.0.1reportava sucesso comSsl_ciphervazio. Oprefer_socketdo driver, ligado por padrão, move uma conexão de loopback para o socket unix após o handshake, e ali nada é cifrado. Pedir TLS agora desliga essa otimização; sem TLS ela permanece.rustls0.23.43 → 0.23.45 (RUSTSEC-2026-0285). Mensagens de handshake do TLS 1.3 aceitas através da fronteira de nível de criptografia.Erros do driver chegavam ao Pawn com estrutura interna do Rust. Uma tabela inexistente chegava como
MySqlError { ERROR 1146 (42S02): ... }. Agora passa apenas a mensagem do servidor.mysql_query_filefalhava sem avisar quem chamou. Arquivo ilegível ou sem statements era apenas linha de log e retornofalse, invisível para a forma sem callback. Agora também disparaOnQueryError, com o caminho no lugar da query eerrorid0.Dependências e CI
rust-samppassa da tag do git para o crates.io, em 3.5.0.mysql28.0.0 → 28.0.2, mais uma rodada ampla de transitivas.Cargo.toml, então observar apenas o manifesto deixava correções de segurança passarem despercebidas.tests/inc_natives.rscompara a lista doinitialize_plugin!com o include nos dois sentidos, para que uma native registrada e não declarada falhe o build em vez de ficar inalcançável a partir do Pawn.Documentação
O callback sempre foi opcional, mas isso estava dito em uma subseção no fim de uma página enquanto a abertura e todos os exemplos sugeriam o contrário. Da mesma forma, a ordenação FIFO vale para a entrega de callbacks e não para a execução — statements submetidos em sequência executam concorrentemente —, e a tabela comparativa recomendava justamente o uso que falha.
Ficaram registradas garantias que existiam apenas no código:
mysql_connectbloqueia enquanto as queries não; TLS falha fechado; o caminho domysql_query_fileé relativo ao diretório do servidor; e coluna binária não trafega em string Pawn, o que tornaVEC_ToText()eHEX()o caminho paraVECTOReBLOB.Com o
MYSQL_SYNC, as páginas deixaram de afirmar que tudo é assíncrono, sem passar a anunciar suporte a síncrono de forma genérica.Verificação
Executado em SA-MP 0.3.7 com MariaDB 11.8, não apenas compilado:
i686-unknown-linux-gnucargo clippy --all-targets -- -D warnings,cargo fmt --checke a verificação de codificação dos arquivos Pawn, todos limposmkdocs build --strictsem links quebradoscargo auditsem vulnerabilidades; permanecem os dois avisos informativos já documentados no changelogChangelog da 1.4.0 datado de 04/10/2026, cobrindo as 30 alterações desde a v1.3.0.