Buffer Overrun — Como Resolver em Zig

Buffer Overrun — Como Resolver em Zig

O Que Este Erro Significa

O buffer overrun (ou buffer overflow) ocorre quando o programa escreve ou lê dados além dos limites de um buffer alocado. Em Zig, builds Debug e ReleaseSafe detectam esses acessos e emitem um panic ao invés de permitir corrupção de memória silenciosa.

A mensagem de panic:

thread 1 panic: index out of bounds

Ou, em caso de estouro de escrita detectado pelo alocador:

error(gpa): buffer overrun detected

Buffer overruns são uma das classes mais perigosas de bugs de segurança — em C, são a causa de inúmeras vulnerabilidades. Zig os previne com verificações automáticas de limites.

Causas Comuns

1. Indexação Além do Tamanho do Array

pub fn main() void {
    var buffer: [10]u8 = undefined;
    buffer[10] = 42; // PANIC: índice 10 em array de tamanho 10 (0..9)
}

2. Loop com Condição de Parada Incorreta

pub fn main() void {
    var buffer: [5]u8 = undefined;
    var i: usize = 0;
    while (i <= 5) : (i += 1) { // <= ao invés de <
        buffer[i] = @intCast(i); // PANIC quando i == 5
    }
}

3. Cópia com Tamanho Incorreto

const std = @import("std");

pub fn main() void {
    var destino: [5]u8 = undefined;
    const origem = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    // PANIC: origem é maior que destino
    @memcpy(&destino, &origem);
}

4. Buffer Fixo Recebendo Dados de Tamanho Variável

const std = @import("std");

pub fn main() !void {
    var buffer: [64]u8 = undefined;
    const stdin = std.io.getStdIn().reader();

    // Se o usuário digitar mais de 64 caracteres...
    const line = try stdin.readUntilDelimiterOrEof(&buffer, '\n');
    // readUntilDelimiter retorna erro se o buffer for pequeno demais
    _ = line;
}

5. Escrever Além do Final de Slice Alocado

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const buffer = try allocator.alloc(u8, 10);
    defer allocator.free(buffer);

    // PANIC: escrevendo além do buffer de 10 bytes
    buffer[15] = 0xFF;
}

6. Formatação em Buffer Insuficiente

const std = @import("std");

pub fn main() void {
    var buf: [5]u8 = undefined;
    // "valor: 12345" precisa de mais de 5 bytes
    const resultado = std.fmt.bufPrint(&buf, "valor: {d}", .{@as(u32, 12345)});
    // Retorna erro NoSpaceLeft, mas se ignorado...
    _ = resultado;
}

Como Corrigir

Solucao 1: Verificar Limites Antes de Acessar

const std = @import("std");

pub fn main() void {
    var buffer: [10]u8 = undefined;
    const indice: usize = 15;

    if (indice < buffer.len) {
        buffer[indice] = 42;
    } else {
        std.debug.print("Índice {} fora dos limites (max: {})\n", .{ indice, buffer.len - 1 });
    }
}

Solucao 2: Usar Loops for Idiomáticos

pub fn main() void {
    var buffer: [5]u8 = undefined;

    // CORRETO: for itera exatamente sobre os índices válidos
    for (&buffer, 0..) |*byte, i| {
        byte.* = @intCast(i);
    }
}

Solucao 3: Usar @min para Limitar Tamanho

const std = @import("std");

fn copiarSeguro(destino: []u8, origem: []const u8) usize {
    const tamanho = @min(destino.len, origem.len);
    @memcpy(destino[0..tamanho], origem[0..tamanho]);
    return tamanho;
}

pub fn main() void {
    var destino: [5]u8 = undefined;
    const origem = "dados muito longos para o buffer";
    const copiados = copiarSeguro(&destino, origem);
    std.debug.print("Copiados {} bytes\n", .{copiados});
}

Solucao 4: Usar ArrayList para Tamanho Dinâmico

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var buffer = std.ArrayList(u8).init(allocator);
    defer buffer.deinit();

    // ArrayList cresce automaticamente — sem buffer overrun
    try buffer.appendSlice("dados que podem ser de qualquer tamanho");
    try buffer.appendSlice(" e mais dados aqui");
    std.debug.print("Total: {} bytes\n", .{buffer.items.len});
}

Solucao 5: Alocar Buffer de Tamanho Adequado

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const dados = "texto de entrada";

    // Alocar exatamente o tamanho necessário
    const buffer = try allocator.alloc(u8, dados.len);
    defer allocator.free(buffer);

    @memcpy(buffer, dados);
}

Solucao 6: Usar bufPrint com Verificação de Erro

const std = @import("std");

pub fn main() void {
    var buf: [100]u8 = undefined; // Buffer generoso

    const resultado = std.fmt.bufPrint(&buf, "valor: {d}, nome: {s}", .{
        @as(u32, 42),
        "Zig Brasil",
    }) catch |err| {
        std.debug.print("Buffer muito pequeno: {}\n", .{err});
        return;
    };

    std.debug.print("{s}\n", .{resultado});
}

Slicing Seguro

Zig oferece slicing com verificação automática de limites:

pub fn main() void {
    const dados = [_]u8{ 1, 2, 3, 4, 5 };

    // Slicing com verificação
    const parte = dados[0..3]; // OK: [0, 1, 2]
    _ = parte;

    // PANIC: 6 > dados.len (5)
    // const invalido = dados[0..6];
}

Diferenca entre Build Modes

ModoVerificação de LimitesComportamento
DebugSimPanic com stack trace
ReleaseSafeSimPanic com stack trace
ReleaseFastNaoComportamento indefinido
ReleaseSmallNaoComportamento indefinido

Sempre desenvolva em Debug e teste em ReleaseSafe antes de usar ReleaseFast.

Padrão Seguro para Buffers

const std = @import("std");

const BufferError = error{BufferCheio};

const SafeBuffer = struct {
    dados: []u8,
    posicao: usize = 0,

    fn escrever(self: *SafeBuffer, byte: u8) BufferError!void {
        if (self.posicao >= self.dados.len) return BufferError.BufferCheio;
        self.dados[self.posicao] = byte;
        self.posicao += 1;
    }

    fn espacoRestante(self: *const SafeBuffer) usize {
        return self.dados.len - self.posicao;
    }
};

Erros Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.