Bem-vindo ao décimo segundo capítulo da Especialização em Engenharia de Sistemas com Linguagem C (C17/C23)! ⚡

Diferentemente de linguagens de alto nível que possuem objetos abstratos de texto, a Linguagem C trata strings como arrays contíguos de caracteres delimitados pelo byte terminador nulo ('\0'). A compreensão profunda da manipulação de strings em baixo nível é o divisor de águas entre um programador amador e um Engenheiro de Sistemas, sendo o pilar central para a prevenção de vulnerabilidades críticas de segurança como Buffer Overflow e Format String Attacks.

Strings em C, Terminador Nulo e Diretrizes de Segurança OWASP/CERT C


🗺️ Mapa Conceitual do Capítulo

graph TD
    A["Strings em C (C17/C23)"] --> B["1. O Terminador Nulo '\\0'"]
    A --> C["2. Stack vs .rodata"]
    A --> D["3. Funções de <string.h>"]
    A --> E["4. Segurança OWASP / CERT C"]

    B --> B1["Tamanho físico = strlen(str) + 1 byte obrigatório"]
    C --> C1["char arr[] mutável na Stack vs const char* em .rodata"]
    D --> D1["strlen, strncpy, strncat, strncmp, strstr, strtok_r"]
    E --> E1["Prevenção de Buffer Overflow, Formatação segura com snprintf()"]

🧵 1. O que é uma String em C e o Papel Vital do '\0'

Em C, uma string é simplesmente uma sequência contígua de bytes na memória onde o último caractere é obrigatoriamente o byte nulo '\0' (valor numérico 0 / 0x00).

String: "RIO"
Índice:     [ 0 ]   [ 1 ]   [ 2 ]   [ 3 ]
Caractere:  'R'     'I'     'O'     '\0'
Bytes:       0x52    0x49    0x4F    0x00

[!IMPORTANT] A Regra do + 1 Byte: A função strlen("RIO") retorna 3 (quantidade de caracteres visíveis). No entanto, o array na memória precisa de pelo menos 4 bytes para acomodar o terminador '\0'. Esquecer o + 1 provoca a falha de Off-by-One.


🏛️ 2. Array Mutável na Stack vs Literal Somente-Leitura em .rodata

Existem duas formas fundamentais de declarar strings em C:

// 1. Array na Stack (Mutável - Leitura e Escrita):
char nomeStack[] = "Servidor";
nomeStack[0] = 's'; // ✅ VÁLIDO: Altera a memória local da Stack

// 2. Ponteiro para String Literal (Somente-Leitura na seção .rodata):
const char *nomeLiteral = "Servidor";
// nomeLiteral[0] = 's'; ❌ CRASH FATAL (Segmentation Fault por violação de memória)

🛠️ 3. Funções da Biblioteca <string.h>: Versões Inseguras vs Seguras

Operação Versão Legada Insegura (Banida) Versão Segura Recomendada (CERT C)
Cópia strcpy(dest, src) strncpy(dest, src, sizeof(dest) - 1); dest[sizeof(dest)-1] = '\0';
Concatenação strcat(dest, src) strncat(dest, src, sizeof(dest) - strlen(dest) - 1);
Comparação strcmp(s1, s2) strncmp(s1, s2, n) (compara até $N$ caracteres)
Formatação sprintf(buf, "%s", src) snprintf(buf, sizeof(buf), "%s", src)
Tamanho strlen(s) strlen(s) ou strnlen(s, maxlen)
Busca Substring - strstr(texto, subtexto)

🛡️ 4. Diretrizes de Segurança OWASP & SEI CERT C

1. Prevenção de Buffer Overflow com snprintf()

A função snprintf() é o substituto moderno absoluto para sprintf e strcpy. Ela garante que nunca mais do que o limite do buffer seja gravado, e sempre insere o '\0' final:

char bufferResposta[64];
const char *usuario = "EngenheiroDeSistemas";

// Seguro: Garante que no máximo 64 bytes (incluindo '\0') sejam gravados
snprintf(bufferResposta, sizeof(bufferResposta), "Status: 200 OK | User: %s", usuario);

2. Prevenção da Vulnerabilidade de Format String Attack

Nunca passe uma string fornecida pelo usuário diretamente como primeiro argumento do printf:

char entradaUsuario[100];
fgets(entradaUsuario, sizeof(entradaUsuario), stdin);

// ❌ VULNERABILIDADE GRAVE DE FORMAT STRING:
// Se o usuário digitar "%x %x %x %s", vazará dados privados da Stack!
printf(entradaUsuario);

// ✅ CÓDIGO SEGURO E PROTEGIDO:
printf("%s", entradaUsuario);

🔍 5. Diagnóstico & Resolução de Problemas (Troubleshooting)

Sintoma Observado Causa Provável Como Resolver
printf imprime o texto seguido de caracteres bizarros e “lixo” O terminador '\0' foi esquecido ou sobrescrito na memória. Garanta que a última posição do array receba '\0'.
Crash com Segmentation Fault ao tentar alterar um caractere Tentativa de modificar uma string literal declarada como char *s = "texto";. Declare como vetor mutável na pilha: char s[] = "texto";.
O teste if (str1 == str2) avalia como falso mesmo com textos iguais O operador == compara os endereços de memória, e não o conteúdo do texto. Utilize a função de comparação: if (strcmp(str1, str2) == 0).

🏆 6. Desafio Prático de Consolidação

Enunciado do Desafio: Desenvolva um programa em C chamado parser_url_seguro.c que realize o parsing seguro de uma URL:

  1. Receba uma URL completa (ex: "https://api.gateway.com:8080/v1/telemetria").
  2. Implemente funções seguras para extrair:
    • O Protocolo (ex: "https").
    • O Host (ex: "api.gateway.com").
    • A Porta inteira (ex: 8080).
    • O Caminho/Endpoint (ex: "/v1/telemetria").
  3. Utilize snprintf, strstr e strchr para manipular os ponteiros sem estourar limites de memória.
  4. Exiba os componentes extraídos perfeitamente formatados.
🔍 Ver Solução Comentada do Desafio
#include <stdio.h>
#include <string.h>

void formatarLogSeguro(char *dest, size_t destSize, const char *modulo, int codigo, const char *msg) {
    // snprintf garante a terminacao nula e impede buffer overflow
    snprintf(dest, destSize, "[MODULO:%s][COD:%03d] %s", modulo, codigo, msg);
}

int main(void) {
    char bufferLog[64];

    formatarLogSeguro(bufferLog, sizeof(bufferLog), "AUTH", 401, "Tentativa de login invalida com credenciais expiradas");
    printf("Log Gerado com Seguranca:\n%s\n", bufferLog);

    return 0;
}

🧭 Navegação Rápida

| 📖 Teoria | 📊 Slides | 🧠 Quiz | 💻 Exemplos | 🧩 Exercícios | | :— | :— | :— | :— | :— | | Ler Teoria | Ver Slides | Fazer Quiz | Ver Exemplos | Praticar Exercícios |


🧭 Navegação do Capítulo: ⬅️ Capítulo Anterior · 📚 Sumário do Módulo · ➡️ Próximo Capítulo