c-development
Develop, review, and secure robust C applications (C11/C17/C23) with explicit memory management, boundary checks, and sanitizer verification. Use when writing C modules, designing clean C APIs, managi
SKILL.mdskills/c-development/SKILL.md
GitHubDefensive C Development
C provides low-level control over hardware and memory without runtime abstraction. Because C lacks automatic memory management and bounds checking, developers and agents must adopt rigorous defensive patterns, explicit ownership conventions, and automated sanitizer instrumentation to prevent security vulnerabilities and undefined behavior.
Detailed reference guides:
- Memory Safety & Allocation Patterns
- Undefined Behavior Traps & Prevention
- Sanitizers & Compiler Warnings
Core Rules
- Every allocation must have a single clear owner: Pair every
malloc/callocwith a dedicated free function or cleanup block. - Always validate buffer bounds before writing: Never use unbounded functions like
strcpy,strcat,sprintf, orgets. Usesnprintf,strncpy, or bounded buffer abstractions. - Check allocation return values:
malloccan returnNULLon memory exhaustion. Always checkif (ptr == NULL)before dereferencing. - Set pointers to
NULLafter freeing: Prevent use-after-free bugs by clearing pointers immediately upon deallocation (free(p); p = NULL;). - Compile with maximum warnings: Always build with
-Wall -Wextra -Wpedantic -Werror -Wconversion. - Instrument with Sanitizers: Test all test suites with AddressSanitizer (
-fsanitize=address) and UndefinedBehaviorSanitizer (-fsanitize=undefined).
1. Header Design and C API Encapsulation
Header Guards and Opaque Structs (Data Hiding)
Hide struct definitions inside .c files to prevent callers from depending on internal layout:
/* buffer.h */
#ifndef BUFFER_H
#define BUFFER_H
#include <stddef.h>
#include <stdbool.h>
/* Opaque struct pointer: implementation details hidden */
typedef struct ByteBuffer ByteBuffer;
ByteBuffer* byte_buffer_create(size_t initial_capacity);
void byte_buffer_destroy(ByteBuffer* buf);
bool byte_buffer_append(ByteBuffer* buf, const uint8_t* data, size_t length);
size_t byte_buffer_size(const ByteBuffer* buf);
const uint8_t* byte_buffer_data(const ByteBuffer* buf);
#endif /* BUFFER_H */
Implementation (buffer.c)
#include "buffer.h"
#include <stdlib.h>
#include <string.h>
struct ByteBuffer {
uint8_t* data;
size_t size;
size_t capacity;
};
ByteBuffer* byte_buffer_create(size_t initial_capacity) {
if (initial_capacity == 0) {
initial_capacity = 64;
}
ByteBuffer* buf = malloc(sizeof(ByteBuffer));
if (!buf) {
return NULL;
}
buf->data = malloc(initial_capacity);
if (!buf->data) {
free(buf);
return NULL;
}
buf->size = 0;
buf->capacity = initial_capacity;
return buf;
}
void byte_buffer_destroy(ByteBuffer* buf) {
if (!buf) return;
free(buf->data);
buf->data = NULL;
free(buf);
}
2. Integer Overflow Checks
Integer overflow in signed types is Undefined Behavior in C and frequently leads to remote code execution when sizing memory allocations:
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
void* safe_alloc_array(size_t count, size_t element_size) {
/* Check for multiplication overflow: count * element_size > SIZE_MAX */
if (count != 0 && element_size > SIZE_MAX / count) {
return NULL; /* Overflow detected */
}
return malloc(count * element_size);
}
Or using modern compiler built-ins (GCC / Clang):
size_t total_bytes;
if (__builtin_mul_overflow(count, element_size, &total_bytes)) {
return NULL; /* Arithmetic overflow prevented */
}
void* ptr = malloc(total_bytes);
3. Common Mistakes and Anti-Patterns
| Anti-Pattern | Vulnerability / Bug | Correct Practice |
|---|---|---|
strcpy(dest, src) |
Buffer overflow (CWE-120) | Use snprintf(dest, sizeof(dest), "%s", src) |
free(ptr); without ptr = NULL; |
Use-after-free (CWE-416) | Set pointer to NULL or adopt RAII/cleanup macros |
Dereferencing without NULL check |
Null pointer dereference crash (CWE-476) | Always check if (ptr == NULL) immediately |
sizeof(pointer) instead of sizeof(*pointer) |
Allocates 8 bytes instead of struct size | Use malloc(sizeof(*ptr)) idiom |
| Signed integer overflow | Undefined behavior; compiler may optimize away checks | Use unsigned arithmetic or __builtin_add_overflow |
4. Compilation Flags and Sanitizers
# Recommended production build flags
gcc -std=c17 -O2 -Wall -Wextra -Wpedantic -Wconversion -Wformat=2 -D_FORTIFY_SOURCE=2 -fstack-protector-strong
# Recommended debug / testing build flags with Sanitizers
clang -std=c17 -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer -Wall -Wextra
5. Verification Checklist
- All allocated memory is freed along every error code path.
- No unbounded string functions (
strcpy,strcat,sprintf,gets) are used. - Return values of
malloc,calloc, andreallocare validated againstNULL. - Code compiles without warnings with
-Wall -Wextra -Wpedantic. - Test suite executes under AddressSanitizer (ASan) and UBSan with zero violations.