Secure Coding in C: A Comprehensive Guide to Writing Safe and Robust Programs

Introduction

C remains one of the most powerful programming languages, giving developers unparalleled control over system resources. However, this power comes with significant responsibility. C's manual memory management, lack of built-in bounds checking, and pointer arithmetic make it particularly susceptible to security vulnerabilities. This comprehensive guide explores secure coding practices in C, helping you write robust, safe, and secure applications.


1. Memory Management Security

1.1 Buffer Overflow Prevention

Buffer overflows remain one of the most common and dangerous vulnerabilities in C programs.

Unsafe Code:

void unsafe_copy(char *src) {
char buffer[64];
strcpy(buffer, src);  // No bounds checking!
}

Safe Alternatives:

#include <string.h>
#include <stdio.h>
// Use strncpy with careful null termination
void safe_copy_fixed(char *src, size_t src_size) {
char buffer[64];
strncpy(buffer, src, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';  // Ensure null termination
}
// Use snprintf for formatted strings
void safe_format(char *src) {
char buffer[64];
snprintf(buffer, sizeof(buffer), "%s", src);
}
// Use strlcpy (if available, BSD extension)
#ifdef __USE_BSD
void safe_copy_bsd(char *src) {
char buffer[64];
strlcpy(buffer, src, sizeof(buffer));
}
#endif
// Dynamic allocation based on input size
char* safe_dynamic_copy(const char *src) {
if (src == NULL) return NULL;
size_t len = strlen(src);
char *buffer = malloc(len + 1);
if (buffer == NULL) {
return NULL;
}
memcpy(buffer, src, len + 1);
return buffer;
}

1.2 String Handling Security

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// Safe string concatenation
int safe_strcat(char *dest, size_t dest_size, const char *src) {
size_t dest_len = strnlen(dest, dest_size);
size_t src_len = strnlen(src, dest_size - dest_len);
if (dest_len + src_len >= dest_size) {
return -1;  // Not enough space
}
memcpy(dest + dest_len, src, src_len);
dest[dest_len + src_len] = '\0';
return 0;
}
// Safe string tokenization
void safe_tokenize(const char *input) {
char *buffer = malloc(strlen(input) + 1);
if (buffer == NULL) return;
strcpy(buffer, input);
// Use strtok_r for thread safety
char *saveptr;
char *token = strtok_r(buffer, " ", &saveptr);
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok_r(NULL, " ", &saveptr);
}
free(buffer);
}
// Validate string length before copying
int safe_string_copy(char *dest, size_t dest_size, const char *src) {
if (dest == NULL || src == NULL) return -1;
size_t src_len = strlen(src);
if (src_len >= dest_size) {
return -1;  // Buffer too small
}
memcpy(dest, src, src_len + 1);
return 0;
}

1.3 Heap Memory Security

#include <stdlib.h>
#include <string.h>
// Always check allocation results
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
return ptr;
}
// Clear sensitive data before freeing
void secure_free(void **ptr, size_t size) {
if (ptr == NULL || *ptr == NULL) return;
// Overwrite with zeros
memset(*ptr, 0, size);
free(*ptr);
*ptr = NULL;  // Prevent dangling pointer
}
// Reallocation with bounds checking
void* safe_realloc(void *ptr, size_t new_size) {
void *new_ptr = realloc(ptr, new_size);
if (new_ptr == NULL && new_size > 0) {
fprintf(stderr, "Reallocation failed\n");
// Original pointer remains valid
return NULL;
}
return new_ptr;
}
// Use calloc for zero-initialized memory
void* safe_calloc(size_t nmemb, size_t size) {
// Check for multiplication overflow
if (nmemb > 0 && size > SIZE_MAX / nmemb) {
return NULL;  // Overflow would occur
}
return calloc(nmemb, size);
}

2. Integer Security

2.1 Integer Overflow Prevention

#include <limits.h>
#include <stdbool.h>
// Check addition overflow
bool addition_overflows(int a, int b) {
if (a > 0 && b > 0 && a > INT_MAX - b) return true;
if (a < 0 && b < 0 && a < INT_MIN - b) return true;
return false;
}
// Check multiplication overflow
bool multiplication_overflows(int a, int b) {
if (a == 0 || b == 0) return false;
if (a > 0 && b > 0 && a > INT_MAX / b) return true;
if (a < 0 && b < 0 && a < INT_MAX / b) return true;
if ((a > 0 && b < 0) || (a < 0 && b > 0)) {
if (a < 0 && b > 0 && a < INT_MIN / b) return true;
if (a > 0 && b < 0 && b < INT_MIN / a) return true;
}
return false;
}
// Safe addition with check
int safe_add(int a, int b, int *result) {
if (addition_overflows(a, b)) {
return -1;  // Overflow would occur
}
*result = a + b;
return 0;
}
// Safe multiplication for size_t (common in allocations)
bool size_mult_overflows(size_t a, size_t b) {
if (a == 0 || b == 0) return false;
return a > SIZE_MAX / b;
}
// Safe allocation with size check
void* safe_allocate_array(size_t count, size_t elem_size) {
if (size_mult_overflows(count, elem_size)) {
return NULL;  // Would overflow
}
return malloc(count * elem_size);
}

2.2 Type Conversion Security

#include <stdint.h>
// Safe casting between integer types
int32_t safe_int64_to_int32(int64_t value) {
if (value > INT32_MAX || value < INT32_MIN) {
// Handle overflow - could log, clamp, or error
return (value > 0) ? INT32_MAX : INT32_MIN;
}
return (int32_t)value;
}
// Check before casting from unsigned to signed
int safe_unsigned_to_signed(unsigned int uvalue) {
if (uvalue > INT_MAX) {
return -1;  // Would overflow
}
return (int)uvalue;
}
// Safe promotion and demotion
uint32_t safe_add_uint16(uint16_t a, uint16_t b) {
uint32_t result = (uint32_t)a + (uint32_t)b;
if (result > UINT16_MAX) {
return UINT16_MAX;  // Clamp to maximum
}
return result;
}

3. Input Validation

3.1 Function Argument Validation

#include <stdbool.h>
#include <assert.h>
// Always validate function arguments
int process_user_data(const char *username, const char *password, size_t len) {
// Validate pointers
if (username == NULL || password == NULL) {
errno = EINVAL;
return -1;
}
// Validate lengths
if (len == 0 || len > MAX_USERNAME_LEN) {
errno = EINVAL;
return -1;
}
// Validate content
for (size_t i = 0; i < len && username[i] != '\0'; i++) {
if (!isalnum(username[i]) && username[i] != '_') {
errno = EINVAL;
return -1;
}
}
return 0;
}
// Use assertions for debugging
#ifdef DEBUG
#define ARG_CHECK(expr) assert(expr)
#else
#define ARG_CHECK(expr) \
do { if (!(expr)) { errno = EINVAL; return -1; } } while(0)
#endif
int process_with_checks(const char *data, size_t size) {
ARG_CHECK(data != NULL);
ARG_CHECK(size > 0 && size <= MAX_SIZE);
ARG_CHECK(data[0] != '\0');
// Process data...
return 0;
}

3.2 User Input Sanitization

#include <ctype.h>
#include <stdbool.h>
// Validate email format (simplified)
bool is_valid_email(const char *email) {
if (email == NULL) return false;
const char *at = strchr(email, '@');
if (at == NULL || at == email || *(at + 1) == '\0') {
return false;
}
const char *dot = strrchr(email, '.');
if (dot == NULL || dot < at || *(dot + 1) == '\0') {
return false;
}
return true;
}
// Validate username (alphanumeric + underscore)
bool is_valid_username(const char *username) {
if (username == NULL || strlen(username) == 0) {
return false;
}
for (const char *p = username; *p; p++) {
if (!isalnum(*p) && *p != '_') {
return false;
}
}
return true;
}
// Validate numeric input
bool is_valid_positive_int(const char *str) {
if (str == NULL || *str == '\0') return false;
for (const char *p = str; *p; p++) {
if (!isdigit(*p)) return false;
}
// Check for overflow
unsigned long val = strtoul(str, NULL, 10);
if (val > INT_MAX) return false;
return true;
}
// Sanitize string by removing dangerous characters
void sanitize_string(char *str) {
if (str == NULL) return;
char *write = str;
for (const char *read = str; *read; read++) {
// Allow alphanumeric, space, underscore, hyphen
if (isalnum(*read) || *read == ' ' || 
*read == '_' || *read == '-') {
*write++ = *read;
}
}
*write = '\0';
}

4. Format String Security

4.1 Preventing Format String Vulnerabilities

#include <stdio.h>
// UNSAFE - Never do this!
void unsafe_log(const char *user_input) {
printf(user_input);  // Format string vulnerability!
}
// SAFE - Use format specifier
void safe_log(const char *user_input) {
printf("%s", user_input);  // Safe
}
// SAFE - Use fixed format
void safe_log_fixed(const char *user_input) {
puts(user_input);  // No formatting at all
}
// SAFE - Custom logging function
void secure_log(const char *format, ...) {
va_list args;
char buffer[1024];
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
// Ensure null termination
buffer[sizeof(buffer) - 1] = '\0';
// Log safely
fputs(buffer, stderr);
}
// Always use format specifiers in printf-like functions
void safe_printf_example(const char *user_input) {
printf("User said: %s\n", user_input);
fprintf(stderr, "Error: %s\n", user_input);
snprintf(NULL, 0, "%s", user_input);  // Safe even with NULL buffer
}

5. File Operations Security

5.1 Secure File Handling

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
// Check file permissions before opening
int safe_open_file(const char *filename, const char *mode) {
if (filename == NULL || mode == NULL) {
return -1;
}
// Check for directory traversal
if (strstr(filename, "..") != NULL) {
errno = EACCES;
return -1;
}
// Use open() with explicit permissions
int fd;
if (strcmp(mode, "r") == 0) {
fd = open(filename, O_RDONLY);
} else if (strcmp(mode, "w") == 0) {
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 
S_IRUSR | S_IWUSR);  // Only owner can read/write
} else if (strcmp(mode, "a") == 0) {
fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 
S_IRUSR | S_IWUSR);
} else {
errno = EINVAL;
return -1;
}
return fd;
}
// Secure temporary file creation
int create_secure_temp_file(char *path_template) {
// mkstemp creates and opens a file with 0600 permissions
int fd = mkstemp(path_template);
if (fd == -1) {
return -1;
}
// Unlink immediately so file is removed when closed
unlink(path_template);
return fd;
}
// Check file path for symbolic link attacks
bool is_safe_path(const char *path) {
struct stat st;
// Check if path is a symbolic link
if (lstat(path, &st) == 0) {
if (S_ISLNK(st.st_mode)) {
return false;  // Don't follow symlinks
}
}
// Check parent directory permissions
char *parent = strdup(path);
char *last_slash = strrchr(parent, '/');
if (last_slash != NULL) {
*last_slash = '\0';
if (stat(parent, &st) == 0) {
// Check if directory is writable by others
if (st.st_mode & S_IWOTH) {
free(parent);
return false;
}
}
}
free(parent);
return true;
}

6. Privilege Management

6.1 Principle of Least Privilege

#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
// Drop privileges after initialization
int drop_privileges(const char *username) {
struct passwd *pw = getpwnam(username);
if (pw == NULL) {
return -1;
}
// Drop group privileges
if (setgid(pw->pw_gid) != 0) {
return -1;
}
// Drop user privileges
if (setuid(pw->pw_uid) != 0) {
return -1;
}
// Verify we can't regain privileges
if (setuid(0) != -1) {
return -1;  // Still have root privileges
}
return 0;
}
// Run only necessary operations with elevated privileges
void perform_privileged_operation(void) {
uid_t original_uid = getuid();
// Temporarily drop privileges
if (setuid(geteuid()) != 0) {
return;
}
// Perform privileged operation
// ...
// Restore original privileges
if (setuid(original_uid) != 0) {
// Log error but continue
}
}

7. Race Condition Prevention

7.1 File Locking and Atomic Operations

#include <fcntl.h>
#include <sys/file.h>
// Use file locking to prevent race conditions
int atomic_file_write(const char *filename, const char *data) {
int fd = open(filename, O_WRONLY | O_CREAT, 0600);
if (fd == -1) {
return -1;
}
// Acquire exclusive lock
if (flock(fd, LOCK_EX) == -1) {
close(fd);
return -1;
}
// Write data
ssize_t written = write(fd, data, strlen(data));
// Release lock
flock(fd, LOCK_UN);
close(fd);
return written;
}
// Use atomic file operations
int atomic_file_rename(const char *oldpath, const char *newpath) {
// rename() is atomic on POSIX systems
if (rename(oldpath, newpath) == -1) {
return -1;
}
return 0;
}
// Use mkstemp for atomic temporary file creation
int safe_temporary_file(void) {
char template[] = "/tmp/myapp_XXXXXX";
int fd = mkstemp(template);
if (fd == -1) {
return -1;
}
// File is securely created with 0600 permissions
return fd;
}

8. Environment Security

8.1 Secure Environment Variable Handling

#include <stdlib.h>
#include <unistd.h>
// Clear environment for child processes
void secure_exec(const char *path, char *const argv[]) {
// Clear environment for security
if (clearenv() != 0) {
// Handle error
}
// Set only necessary environment variables
setenv("PATH", "/usr/local/bin:/usr/bin", 1);
setenv("LANG", "C", 1);
execv(path, argv);
}
// Safe environment variable retrieval
char* safe_getenv(const char *name, const char *default_value) {
char *value = getenv(name);
if (value == NULL) {
return (char*)default_value;
}
// Validate environment variable content
if (strchr(value, '/') != NULL || strchr(value, ';') != NULL) {
// Potentially dangerous characters
return (char*)default_value;
}
return value;
}

9. Signal Handler Safety

9.1 Async-Signal-Safe Handlers

#include <signal.h>
#include <unistd.h>
#include <errno.h>
volatile sig_atomic_t signal_received = 0;
// Safe signal handler
void safe_signal_handler(int signum) {
// Only async-signal-safe functions allowed
signal_received = signum;
// write() is async-signal-safe
write(STDERR_FILENO, "Signal received\n", 16);
}
// UNSAFE - Never do this in signal handlers
void unsafe_signal_handler(int signum) {
printf("Signal %d received\n", signum);  // NOT safe!
malloc(100);  // NOT safe!
free(NULL);   // NOT safe!
}
// Set up safe signal handling
void setup_signal_handlers(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = safe_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;  // Restart interrupted system calls
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
}
// Signal handling with self-pipe trick
int signal_pipe[2];
void pipe_signal_handler(int signum) {
int saved_errno = errno;
write(signal_pipe[1], &signum, sizeof(signum));
errno = saved_errno;
}
void setup_self_pipe(void) {
pipe(signal_pipe);
// Set non-blocking
int flags = fcntl(signal_pipe[0], F_GETFL);
fcntl(signal_pipe[0], F_SETFL, flags | O_NONBLOCK);
struct sigaction sa;
sa.sa_handler = pipe_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGUSR1, &sa, NULL);
}
void process_signals(void) {
int signum;
while (read(signal_pipe[0], &signum, sizeof(signum)) > 0) {
// Safe to call any function here
printf("Processing signal: %d\n", signum);
}
}

10. Cryptographic Security

10.1 Secure Random Number Generation

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
// Use /dev/urandom for cryptographic randomness
int secure_random_bytes(void *buf, size_t size) {
int fd = open("/dev/urandom", O_RDONLY);
if (fd == -1) {
return -1;
}
ssize_t result = read(fd, buf, size);
close(fd);
return result == (ssize_t)size ? 0 : -1;
}
// Secure random integer
int secure_random_int(void) {
int value;
if (secure_random_bytes(&value, sizeof(value)) == 0) {
return value;
}
return -1;
}
// UNSAFE - Never use rand() for security
int insecure_random_int(void) {
return rand();  // Predictable!
}
// Generate random password/token
int generate_secure_token(char *buffer, size_t size) {
const char *chars = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
size_t chars_len = strlen(chars);
unsigned char random_bytes[64];
if (secure_random_bytes(random_bytes, size) != 0) {
return -1;
}
for (size_t i = 0; i < size - 1; i++) {
buffer[i] = chars[random_bytes[i] % chars_len];
}
buffer[size - 1] = '\0';
return 0;
}

10.2 Constant-Time Comparison

#include <stdbool.h>
#include <string.h>
// Constant-time comparison to prevent timing attacks
bool constant_time_compare(const void *a, const void *b, size_t size) {
const unsigned char *ca = a;
const unsigned char *cb = b;
unsigned char result = 0;
for (size_t i = 0; i < size; i++) {
result |= ca[i] ^ cb[i];
}
return result == 0;
}
// Use for password/secret comparison
bool secure_password_check(const char *input, const char *stored) {
size_t input_len = strlen(input);
size_t stored_len = strlen(stored);
if (input_len != stored_len) {
return false;
}
return constant_time_compare(input, stored, input_len);
}

11. Compiler Security Features

11.1 Compiler Flags and Attributes

// GCC/Clang security attributes
__attribute__((nonnull))  // Function parameters should not be NULL
__attribute__((returns_nonnull))  // Function never returns NULL
__attribute__((warn_unused_result))  // Return value must be used
// Example
__attribute__((warn_unused_result))
int critical_operation(void) {
return 0;
}
// Stack protection
// Compile with: -fstack-protector-strong -D_FORTIFY_SOURCE=2
// -D_FORTIFY_SOURCE=2 adds runtime checks to string functions
// Address sanitizer (debug builds)
// Compile with: -fsanitize=address -g
// Undefined behavior sanitizer
// Compile with: -fsanitize=undefined -g

12. Error Handling and Logging

12.1 Secure Error Handling

#include <errno.h>
#include <stdio.h>
#include <string.h>
// Never expose internal details to users
void secure_error_handling(void) {
FILE *config = fopen("config.txt", "r");
if (config == NULL) {
// Log full error internally
syslog(LOG_ERR, "Failed to open config: %s", strerror(errno));
// Show user-friendly message
fprintf(stderr, "Configuration error. Please contact support.\n");
exit(EXIT_FAILURE);
}
}
// Sanitize error messages
void sanitize_error_message(char *msg) {
// Remove sensitive information
char *p;
while ((p = strstr(msg, "password=")) != NULL) {
char *end = strchr(p, ' ');
if (end) {
memset(p, '*', end - p);
}
}
}
// Logging with levels
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR,
LOG_CRITICAL
} LogLevel;
void secure_log(LogLevel level, const char *format, ...) {
// Don't log sensitive data
if (level == LOG_DEBUG && getenv("DEBUG") == NULL) {
return;
}
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}

13. Complete Secure Coding Example

Here's a complete example demonstrating many secure coding principles:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#define MAX_USERNAME_LEN 32
#define MAX_PASSWORD_LEN 128
#define MAX_BUFFER_SIZE 4096
// Secure user structure
typedef struct {
char username[MAX_USERNAME_LEN];
char password_hash[64];
int user_id;
} SecureUser;
// Validate username
bool validate_username(const char *username) {
if (username == NULL) return false;
size_t len = strlen(username);
if (len == 0 || len >= MAX_USERNAME_LEN) return false;
for (size_t i = 0; i < len; i++) {
if (!isalnum(username[i]) && username[i] != '_') {
return false;
}
}
return true;
}
// Constant-time password verification
bool verify_password(const char *input, const char *stored_hash) {
if (input == NULL || stored_hash == NULL) return false;
// Use constant-time comparison
return constant_time_compare(input, stored_hash, strlen(stored_hash));
}
// Secure file read with bounds checking
int secure_read_file(const char *filename, char *buffer, size_t buffer_size) {
if (filename == NULL || buffer == NULL || buffer_size == 0) {
errno = EINVAL;
return -1;
}
// Check for path traversal
if (strstr(filename, "..") != NULL) {
errno = EACCES;
return -1;
}
int fd = open(filename, O_RDONLY);
if (fd == -1) {
return -1;
}
// Check file size first
struct stat st;
if (fstat(fd, &st) == -1) {
close(fd);
return -1;
}
if (st.st_size >= (off_t)buffer_size) {
close(fd);
errno = ENOSPC;
return -1;
}
ssize_t bytes_read = read(fd, buffer, buffer_size - 1);
if (bytes_read >= 0) {
buffer[bytes_read] = '\0';
}
close(fd);
return bytes_read;
}
// Secure user authentication
int authenticate_user(const char *username, const char *password) {
// Validate inputs
if (!validate_username(username) || password == NULL) {
return -1;
}
// Use secure comparison
char stored_hash[64];
if (secure_read_file("user_db.txt", stored_hash, sizeof(stored_hash)) < 0) {
return -1;
}
if (!verify_password(password, stored_hash)) {
return -1;
}
return 0;
}
// Main function with secure argument handling
int main(int argc, char *argv[]) {
// Validate arguments
if (argc != 3) {
fprintf(stderr, "Usage: %s <username> <password>\n", argv[0]);
return EXIT_FAILURE;
}
// Sanitize input
char username[MAX_USERNAME_LEN];
char password[MAX_PASSWORD_LEN];
strncpy(username, argv[1], sizeof(username) - 1);
username[sizeof(username) - 1] = '\0';
strncpy(password, argv[2], sizeof(password) - 1);
password[sizeof(password) - 1] = '\0';
// Authenticate
if (authenticate_user(username, password) == 0) {
printf("Authentication successful\n");
// Drop privileges if running with elevated permissions
if (getuid() == 0) {
if (drop_privileges("nobody") != 0) {
fprintf(stderr, "Failed to drop privileges\n");
return EXIT_FAILURE;
}
}
// Perform secure operations...
} else {
// Use generic error message
fprintf(stderr, "Authentication failed\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

14. Security Checklist

Pre-Development

  • [ ] Define security requirements
  • [ ] Threat model the application
  • [ ] Establish secure coding standards

Development

  • [ ] Use compiler security flags (-fstack-protector-strong, -D_FORTIFY_SOURCE=2)
  • [ ] Enable warnings (-Wall -Wextra -Wpedantic -Werror)
  • [ ] Use static analysis tools
  • [ ] Validate all inputs
  • [ ] Check all return values
  • [ ] Use safe string functions (strncpy, snprintf)
  • [ ] Prevent buffer overflows
  • [ ] Guard against integer overflows
  • [ ] Initialize all variables
  • [ ] Free memory and set pointers to NULL
  • [ ] Use const where appropriate
  • [ ] Avoid format string vulnerabilities

Testing

  • [ ] Use address sanitizer (-fsanitize=address)
  • [ ] Use undefined behavior sanitizer (-fsanitize=undefined)
  • [ ] Perform fuzz testing
  • [ ] Run with Valgrind
  • [ ] Test edge cases

Deployment

  • [ ] Strip binaries (-s flag)
  • [ ] Run with least privilege
  • [ ] Use secure configuration defaults
  • [ ] Log securely (no sensitive data)
  • [ ] Implement proper error handling

15. Common Vulnerabilities and Prevention

VulnerabilityPrevention
Buffer OverflowUse bounds-checked functions, check sizes
Format StringAlways use format specifiers, never pass user input as format
Integer OverflowCheck before operations, use safe arithmetic functions
Use After FreeSet pointers to NULL after free, use static analysis
Double FreeSet pointers to NULL after free
Memory LeakAlways free allocated memory, use RAII patterns
Race ConditionUse file locking, atomic operations
InjectionValidate and sanitize all input
Information LeakDon't expose internal details in errors
TOCTOUUse atomic operations, check and use same file descriptor

Conclusion

Secure coding in C requires constant vigilance and adherence to best practices. The language's power and flexibility demand that developers take responsibility for memory safety, input validation, and proper error handling. By following the principles and examples in this guide, you can write C code that is not only functional but also resilient against attacks.

Remember: security is not a feature to add at the end—it must be integrated throughout the development lifecycle. Stay informed about new vulnerabilities, keep your tools updated, and never assume that "it works" means "it's secure."

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper