Integer Overflow Explicado: O Bug Que Destroi
Integer overflow e um dos bugs mais antigos da computacao e ainda causa desastres em producao. Entenda o mecanismo, veja exemplos em cada linguagem e saiba como detectar
TL;DR
Integer Overflow Explicado: O Bug Que Destroi. Integer overflow e um dos bugs mais antigos da computacao e ainda causa desastres em producao. Entenda o mecanismo, veja exemplos em cada linguagem e saiba como detectar antes que chegue ao usuario.
Integer overflow e um daqueles bugs que parece simples demais para ser perigoso. Um numero fica grande demais, da volta pro zero — qual o problema? O problema e que esse mecanismo simples destruiu um foguete europeu de $370 milhoes, esta na origem de vulnerabilidades de seguranca classicas como buffer overflow, e ainda aparece regularmente em sistemas financeiros que lidam com centavos multiplicados por bilhoes de transacoes.
A parte mais traicoeira do overflow: ele frequentemente nao causa um crash imediato. O programa continua rodando, aparentemente normal, mas com valores completamente errados. No Ariane 5, o sistema de navegacao recebeu um valor invalido, entrou em modo de erro, e o foguete foi destruido. Em sistemas financeiros, pode resultar em creditos ou debitos absurdos. Em sistemas de seguranca, pode abrir brechas exploravel.
O Velocimetro Que Volta pro Zero
Pensa em um velocimetro mecanico antigo que vai de 0 a 999.999 km. Quando o carro atinge 999.999 km rodados e voce dirige mais 1 km, o odometro nao explode — ele volta para 000.000. O carro rodou 1.000.000 km, mas o marcador mostra zero. Isso e exatamente o que acontece com integer overflow.
Um tipo inteiro de N bits consegue representar 2^N valores. Um uint8_t (8 bits sem sinal) vai de 0 a 255. Dois elevado a oito e 256, entao tem 256 valores possiveis (de 0 a 255). Se voce adiciona 1 ao valor 255, o resultado seria 256, que em binario seria 100000000 — 9 bits. Mas como so temos 8 bits de espaco, o bit mais significativo e perdido e fica 00000000, ou seja, 0. O numero 'deu volta'.
Para inteiros com sinal (signed), que usam um bit para indicar positivo/negativo, o comportamento e ligeiramente diferente. Um int8_t vai de -128 a 127. Somar 1 ao 127 resulta em -128. Esse e o overflow classico que causa bugs sutis em comparacoes: se voce espera que um contador sempre seja positivo e ele vira negativo, logica baseada em if (counter > 0) pode falhar de formas inesperadas.
JavaScript — Silencioso e Perigoso
JavaScript nao tem inteiros nativos na maior parte do tempo — usa IEEE 754 double precision floating point para quase tudo. Isso significa que nao ha overflow classico de inteiro, mas ha um limite de precisao inteira: Number.MAX_SAFE_INTEGER = 9007199254740991 (2^53 - 1). Acima desse valor, operacoes aritmeticas simplesmente perdem precisao sem avisar.
// JavaScript: perda de precisao silenciosa
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 <- MESMO VALOR!
console.log(Number.MAX_SAFE_INTEGER + 3); // 9007199254740994 <- pulou o 3!
// Verificando se um numero e seguro
console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740992)); // false
// Solucao: BigInt para numeros grandes
const grande = BigInt(Number.MAX_SAFE_INTEGER);
console.log(grande + 1n); // 9007199254740992n (preciso)
console.log(grande + 2n); // 9007199254740993n (preciso)
// Cuidado: nao misture BigInt com Number
// console.log(grande + 1); // TypeError!O problema real com JavaScript e que IDs de banco de dados, timestamps em milissegundos e valores financeiros em centavos podem facilmente exceder MAX_SAFE_INTEGER em sistemas de grande escala. IDs do Twitter, por exemplo, excederam MAX_SAFE_INTEGER em 2012, o que causou bugs em clientes JavaScript que liam os IDs como numeros em vez de strings.
Python — Inteiros Infinitos
Python 3 nao tem integer overflow. Ponto. Os inteiros em Python sao arbitrariamente grandes — a linguagem aloca mais memoria automaticamente conforme o numero cresce. Voce pode calcular fatoriais de numeros absurdos sem preocupacao. Isso tem um custo: operacoes com numeros muito grandes sao mais lentas do que operacoes com tipos de largura fixa.
# Python: sem overflow em inteiros nativos
x = 2 ** 100
print(x) # 1267650600228229401496703205376 — funciona!
print(2 ** 1000) # funciona, resultado enorme mas correto
# Mas arrays numpy PODEM ter overflow (tipos C por baixo)
import numpy as np
arr = np.array([127], dtype=np.int8)
print(arr + 1) # [-128] — overflow! numpy usa tipos C de largura fixa
# Sempre especifique dtype em numpy para numeros grandes
arr_safe = np.array([127], dtype=np.int64) # ou int128, ou Python int
print(arr_safe + 1) # [128] — correto
# struct.pack tambem pode overflow
import struct
struct.pack('B', 256) # struct.error: ubyte format requires 0 <= number <= 255TypeScript — Nao Te Salva
TypeScript compila para JavaScript, entao herda exatamente os mesmos comportamentos numericos. O sistema de tipos do TypeScript nao consegue prevenir overflow em tempo de compilacao porque os valores sao conhecidos so em runtime. O tipo number e sempre um IEEE 754 double, independente de voce escrever const x: number = 1. O TypeScript nao tem tipos de inteiro de largura fixa nativos.
// TypeScript: herda os problemas do JavaScript
const maxSafe: number = Number.MAX_SAFE_INTEGER;
const unsafe: number = maxSafe + 1;
const alsoUnsafe: number = maxSafe + 2;
console.log(unsafe === alsoUnsafe); // true — TypeScript nao vai te avisar!
// O tipo 'number' nao distingue int de float
const a: number = 1;
const b: number = 1.5;
// Ambos sao 'number' — o compilador nao tem como saber
// Para trabalhar com BigInt em TypeScript:
const big: bigint = 9007199254740991n;
console.log(big + 1n); // 9007199254740992n — correto
// Voce pode criar um branded type para 'safe integer'
type SafeInt = number & { readonly __brand: 'SafeInt' };
function toSafeInt(n: number): SafeInt {
if (!Number.isSafeInteger(n)) {
throw new Error(`${n} nao e um inteiro seguro`);
}
return n as SafeInt;
}Rust — Panic em Debug, Wrap em Release
Rust e a linguagem que lida com overflow de forma mais explicita e segura. Em modo debug, qualquer overflow causa um panic (crash controlado com mensagem de erro clara). Em modo release, por performance, o comportamento padrao e wrapping silencioso — o mesmo que C. Mas o Rust te da ferramentas explicitas para dizer exatamente o que voce quer.
fn main() {
let x: u8 = 255;
// Em debug: panic! attempt to add with overflow
// Em release: wrapping silencioso (volta pra 0)
// let overflow = x + 1; // PERIGOSO em release!
// Checked: retorna Option<T>
match x.checked_add(1) {
Some(result) => println!("Resultado: {}", result),
None => println!("Overflow detectado!"), // vai cair aqui
}
// Saturating: limita ao maximo do tipo
let saturated = x.saturating_add(10);
println!("Saturado: {}", saturated); // 255 — nao passa do maximo
// Wrapping: explicito sobre o comportamento
let wrapped = x.wrapping_add(1);
println!("Wrapped: {}", wrapped); // 0 — explicito e intencional
// Overflowing: retorna (resultado, overflow_happened)
let (result, overflowed) = x.overflowing_add(1);
println!("Resultado: {}, Overflow: {}", result, overflowed); // 0, true
// Para criptografia, use wrapping explicito:
let hash_step: u32 = u32::MAX;
let next_step = hash_step.wrapping_add(1); // 0 — intencional em CRC
}Go — Wrap Silencioso
Go tem tipos de inteiro de largura fixa (int8, int16, int32, int64, uint8, etc.) e o comportamento em overflow e wrapping silencioso, semelhante ao C. Nao ha panic, nao ha erro — o numero simplesmente da volta. Isso e consistente e previsivel, mas coloca o onus de validacao no desenvolvedor.
package main
import (
"fmt"
"math"
)
func main() {
// Go: wrap silencioso em overflow
var x uint8 = 255
fmt.Println(x + 1) // 0 — sem erro, sem panic
var y int8 = 127
fmt.Println(y + 1) // -128 — signed overflow
// Para detectar overflow manualmente:
a := math.MaxInt64
b := 1
// Verificacao manual antes da operacao
if a > math.MaxInt64 - b {
fmt.Println("Overflow seria causado!")
} else {
fmt.Println(a + b)
}
// Biblioteca golang.org/x/exp/constraints pode ajudar
// Para operacoes criticas, valide sempre os inputs antes
}O Ariane 5 e o caso mais famoso, mas overflow aparece em producao com mais frequencia do que a maioria dos devs imagina. Em 2004, o servidor Microsoft SQL Server tinha uma vulnerabilidade de integer overflow na funcao de processar pacotes de rede que permitia execucao remota de codigo. Em 2015, um bug de overflow em MP3 players causava crash ao tocar musicas com mais de 13 horas de duracao — um int de 32 bits em milissegundos nao comportava duracao maior que ~24 dias, mas em contagens de frames sim.
Em sistemas financeiros, overflow em calculos de juros compostos pode resultar em valores negativos ou absurdamente grandes. Imagine um sistema legado em C que calcula juros sobre um saldo de bilhoes de centavos — se o campo for int32_t, o maximo e ~2.1 bilhoes. Um cliente com R$21 milhoes em uma conta ja causa overflow. Isso nao e hipotetico: bancos migrando de sistemas COBOL antigos para novos sistemas em linguagens modernas constantemente encontram esses casos.
Jogos tambem sao afetados. O famoso bug do 'Civilization' com Gandhi: o nivel de agressividade era representado como uint8 (0-255) e Gandhi comecava com nivel 1. Uma tecnologia da arvore tecnologica reduzia agressividade em 2. 1 - 2 em uint8 resulta em 255 — nivel maximo de agressividade. Gandhi se tornava o lider mais guerreiro do jogo. A Firaxis brincou com isso nas versoes modernas, tornando o bug um easter egg intencional.
Overflow em Seguranca
Integer overflow e frequentemente o primeiro passo em exploits de seguranca. Um overflow pode fazer com que um buffer alocado seja menor do que o esperado, causando um buffer overflow subsequente. Muitas vulnerabilidades criticas de heap corruption comecam com um integer overflow em um calculo de tamanho de alocacao. Por isso SBOM e code auditing importam tanto.
Checkers e Linters
Para C e C++, o AddressSanitizer (ASan) e UndefinedBehaviorSanitizer (UBSan) do LLVM detectam overflow em runtime durante testes. Compile com -fsanitize=undefined,address e rode seus testes — qualquer overflow vai gerar um report detalhado. Para Java, ferramentas como SpotBugs tem detectores especificos para overflow. Para Go, a biblioteca math/bits tem funcoes que detectam overflow.
UBSan (C/C++)
Undefined Behavior Sanitizer do LLVM. Detecta overflow em runtime durante testes. Compile com -fsanitize=undefined.
Semgrep
Analise estatica que pode detectar padroes de overflow como aritmetica nao verificada em inputs externos.
CodeQL
Ferramenta da GitHub que tem queries especificas para integer overflow e vulnerabilidades relacionadas.
Clippy (Rust)
O linter do Rust avisa sobre cast entre tipos numericos que pode causar truncamento ou overflow inesperado.
cargo-audit
Audita dependencias Rust por vulnerabilidades conhecidas, incluindo aquelas causadas por overflow em libs de terceiros.
Safe Math Libraries
Em vez de fazer aritmetica nua, use bibliotecas que verificam overflow explicitamente. Em C, a (C23) tem funcoes como ckd_add(), ckd_mul() que retornam um booleano indicando se houve overflow. Em JavaScript, encapsule operacoes criticas em funcoes que verificam Number.isSafeInteger(). Em Python com numpy, prefira dtype=object para inteiros arbitrarios quando precisao e critica.
Testes de Boundary
Qualquer campo numerico que aceita input externo deve ter testes de boundary: zero, negativo, maximo do tipo, maximo + 1, e valores proximo ao limite. Se sua funcao recebe uma idade de usuario, teste com 0, -1, 127, 128, 255, 256 e 2147483647. Se recebe um preco em centavos, teste com Long.MAX_VALUE. Esses testes custam pouco para escrever e pegam bugs que testes de caminho feliz jamais encontram.
Use esse checklist ao revisar codigo que faz calculos com numeros de origem externa ou que lida com contadores, tamanhos de buffer, valores financeiros ou timestamps. Nao e necessario aplicar para toda e qualquer operacao matematica — foque em onde o impacto de um valor errado seria significativo.
Auditoria de Integer Overflow
- Todos os inputs numericos externos tem validacao de range antes de uso?
- Calculos com valores financeiros usam tipos de precisao adequada (BigInt, Decimal, ou similar)?
- Multiplicacoes de dois valores de origem externa tem verificacao de overflow antes da operacao?
- Arrays e buffers alocados com tamanho calculado verificam que o calculo nao fez overflow?
- Contadores em loops longos usam tipos suficientemente grandes para o maximo esperado?
- Timestamps em milissegundos usam int64 ou equivalente, nao int32?
- Conversoes entre tipos numericos (cast) verificam que o valor cabe no tipo destino?
- Testes incluem valores no limite superior (MAX - 1, MAX, MAX + 1) do tipo usado?
- Codigo C/C++ critico compila com UBSan em ambiente de teste?
- Rust: codigo de producao usa checked_* ou saturating_* para operacoes criticas?
Se voce quiser ver como esses principios se aplicam em revisao de codigo do dia a dia, o artigo sobre code review como devs seniores fazem vai te mostrar como incorporar essa mentalidade de 'auditoria adversarial' no processo de revisao de PR. E para o contexto historico do maior desastre causado por overflow — o Ariane 5 — o artigo sobre os 10 bugs mais caros da historia entra em detalhes sobre o impacto e o que poderia ter sido feito diferente.
Perguntas frequentes
O que e integer overflow?
Integer overflow acontece quando uma operacao matematica produz um resultado maior do que o tipo de dado consegue armazenar. Por exemplo, um inteiro de 8 bits sem sinal vai de 0 a 255. Se voce somar 1 ao 255, o resultado volta para 0 em vez de ser 256. Isso pode causar comportamentos inesperados e vulnerabilidades de seguranca.
JavaScript tem integer overflow?
Sim e nao. JavaScript usa numeros de ponto flutuante de 64 bits (IEEE 754) para todos os numeros, entao nao tem overflow classico de inteiro. Mas tem um limite de inteiro seguro: Number.MAX_SAFE_INTEGER = 9007199254740991. Acima disso, operacoes aritmeticas perdem precisao silenciosamente. Para numeros maiores, use BigInt.
Como Rust previne integer overflow?
Em modo debug, Rust causa um panic (crash controlado) se detecta overflow em tempo de execucao. Em modo release, por performance, faz wrapping (o numero volta ao inicio do range). Voce pode ser explicito usando metodos como checked_add() que retorna Option<T>, saturating_add() que limita ao maximo, ou wrapping_add() que faz wrap consciente.