⚡ Cap 12: Cadeias de Caracteres (Strings), e Segurança OWASP/CERT C
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.
🗺️ 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
+ 1Byte: A funçãostrlen("RIO")retorna3(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+ 1provoca 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:
- Receba uma URL completa (ex:
"https://api.gateway.com:8080/v1/telemetria"). - 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").
- O Protocolo (ex:
- Utilize
snprintf,strstrestrchrpara manipular os ponteiros sem estourar limites de memória. - 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