Secure coding practices in C
9 mins read

Secure coding practices in C

Address security vulnerabilities in C programming by delving into secure coding practices, memory safety, input validation, and preventing common security pitfalls.

Secure coding practices in C are essential to address security vulnerabilities and prevent malicious attacks in software applications. This involves techniques to ensure memory safety, validate input properly, and avoid common security pitfalls. Let’s explore a simple example of a secure coding practice in C by focusing on input validation to prevent buffer overflows.

Example: Secure Input Validation

Insecure code that doesn’t perform proper input validation can lead to buffer overflows and other vulnerabilities. Let’s create a simple program that reads user input into a buffer without proper validation, and then modify it to use secure input validation.

1. Insecure Code (Without Input Validation):

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[8]; // Vulnerable buffer
    printf("Enter a string: ");
    gets(buffer); // Unsafe function, can lead to buffer overflow
    printf("You entered: %s\n", buffer);
    return 0;
}

Explanation of Insecure Code:

  1. #include <stdio.h>: Include the standard I/O library for input and output functions.
  2. char buffer[8];: Declare a character array buffer with a size of 8 bytes.
  3. printf("Enter a string: ");: Prompt the user to enter a string.
  4. gets(buffer);: Read user input into the buffer. This function doesn’t perform bounds checking, making it susceptible to buffer overflows.
  5. printf("You entered: %s\n", buffer);: Print the user-entered string.
  6. return 0;: End the program.

Secure Code (With Input Validation):

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[8];
    printf("Enter a string: ");
    if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
        buffer[strcspn(buffer, "\n")] = '\0'; // Remove newline if present
        printf("You entered: %s\n", buffer);
    } else {
        printf("Error reading input.\n");
    }
    return 0;
}

Explanation of Secure Code:

  1. #include <stdio.h>: Include the standard I/O library.
  2. char buffer[8];: Declare the buffer with a size of 8 bytes.
  3. printf("Enter a string: ");: Prompt the user to enter a string.
  4. if (fgets(buffer, sizeof(buffer), stdin) != NULL) {: Read user input into the buffer using fgets. It limits input to the size of the buffer.
  5. buffer[strcspn(buffer, "\n")] = '\0';: Remove the trailing newline character from the input if present (added by fgets).
  6. printf("You entered: %s\n", buffer);: Print the user-entered string.
  7. } else { printf("Error reading input.\n"); }: Handle the case where input reading fails.
  8. return 0;: End the program.

Output:

In both cases, the program prompts the user to enter a string. However, the secure code performs proper input validation and handles the newline character, ensuring safer input processing. If you provide input that fits within the buffer, both versions of the code will display the entered string. If you provide input that exceeds the buffer size, the insecure code can lead to a buffer overflow and unpredictable behavior, while the secure code will safely handle the input.

2. Memory Safety:

Memory safety is a crucial concept in secure coding that involves preventing vulnerabilities related to improper memory manipulation, such as buffer overflows, use-after-free, and null pointer dereferences. Ensuring memory safety helps prevent security breaches, crashes, and unpredictable behavior in software applications. Let’s delve into an example of memory safety in C, focusing on preventing buffer overflows.

Example: Using strncpy Safely to Prevent Buffer Overflow

Insecure code that uses unsafe functions like strcpy can lead to buffer overflows. We’ll modify this code to use the safer function strncpy to prevent this vulnerability.

Insecure Code (Using strcpy):

#include <stdio.h>
#include <string.h>

int main() {
    char source[] = "This is a long string that can cause buffer overflow!";
    char destination[20]; // Small buffer size

    strcpy(destination, source); // Unsafe function

    printf("Destination: %s\n", destination);

    return 0;
}

Explanation of Insecure Code:

  1. #include <stdio.h> and #include <string.h>: Include the standard I/O and string manipulation libraries.
  2. char source[] = "This is a long string...";: Declare a source string that is longer than the destination buffer.
  3. char destination[20];: Declare a small buffer for the destination.
  4. strcpy(destination, source);: Use the strcpy function to copy the source string into the destination buffer. This can lead to a buffer overflow if the source is larger than the destination.

Secure Code (Using strncpy):

#include <stdio.h>
#include <string.h>

int main() {
    char source[] = "This is a long string that can cause buffer overflow!";
    char destination[20];

    strncpy(destination, source, sizeof(destination) - 1); // Safe function
    destination[sizeof(destination) - 1] = '\0'; // Null-terminate the string

    printf("Destination: %s\n", destination);

    return 0;
}

Explanation of Secure Code:

  1. #include <stdio.h> and #include <string.h>: Include the necessary libraries.
  2. char source[] = "This is a long string...";: Declare the source string.
  3. char destination[20];: Declare the destination buffer.
  4. strncpy(destination, source, sizeof(destination) - 1);: Use strncpy to copy a portion of the source string into the destination buffer, ensuring that the destination buffer is not overrun.
  5. destination[sizeof(destination) - 1] = '\0';: Manually null-terminate the string to ensure proper string handling.

Output:

Both the insecure and secure versions of the code will produce the same output. However, the key difference lies in the behavior when the source string is larger than the destination buffer.

For the insecure code:

Destination: This is a long string that can cause buffer overflow!

For the secure code:

Destination: This is a long string

3. Handling Resources Securely:

Handling resources securely, such as files, memory, and network connections, is an important aspect of writing secure C programs. Properly managing resources helps prevent memory leaks, unauthorized access, and potential security vulnerabilities. Let’s explore an example of secure file handling, along with an explanation of each step and the expected output.

Example: File Handling with Error Checking

In this example, we’ll demonstrate how to securely handle files by opening a file for reading and performing some file operations.

#include <stdio.h>

int main() {
    FILE* file = fopen("data.txt", "r");
    if (file != NULL) {
        char buffer[100];
        while (fgets(buffer, sizeof(buffer), file) != NULL) {
            printf("%s", buffer);
        }
        fclose(file);
    } else {
        printf("File open failed.\n");
    }
    return 0;
}

Explanation:

  1. #include <stdio.h>: Include the standard I/O library.
  2. FILE* file = fopen("data.txt", "r");: Open the file named “data.txt” in read mode. The fopen function returns a file pointer (FILE*) that points to the opened file. If the file doesn’t exist or cannot be opened, file will be NULL.
  3. if (file != NULL) {: Check if the file was opened successfully.
  4. char buffer[100];: Declare a character array buffer to temporarily store data read from the file.
  5. while (fgets(buffer, sizeof(buffer), file) != NULL) {: Use a loop to read lines from the file using fgets. The loop continues until fgets returns NULL, indicating the end of the file.
  6. printf("%s", buffer);: Print the content of the buffer.
  7. fclose(file);: Close the file using the fclose function to release the associated resources.
  8. } else { printf("File open failed.\n"); }: Handle the case where the file could not be opened.
  9. return 0;: End the program.

Output:

Assuming the “data.txt” file contains the following lines:

Hello, Secure File Handling!
This is a sample file.
It contains some data.

The program will read the content of the “data.txt” file and print it to the console:

Hello, Secure File Handling!
This is a sample file.
It contains some data.

4. Avoiding Common Pitfalls:

Avoiding common pitfalls is a key aspect of secure coding. These pitfalls include practices that might inadvertently lead to vulnerabilities or compromise the security of an application. One such pitfall is the insecure storage of sensitive data, such as hardcoding passwords directly into the source code.

Example: Avoiding Hardcoded Passwords

#include <stdio.h>
#include <string.h>

int main() {
    char input[50];
    printf("Enter your password: ");
    scanf("%s", input);

    char password[] = "my_secure_password"; // Should be stored securely, not hardcoded

    if (strcmp(input, password) == 0) {
        printf("Access granted.\n");
    } else {
        printf("Access denied.\n");
    }

    return 0;
}

Explanation:

  1. #include <stdio.h>: Include the standard I/O library.
  2. char input[50];: Declare an array to store user input (password).
  3. printf("Enter your password: ");: Prompt the user to enter a password.
  4. scanf("%s", input);: Read the user’s input and store it in the input array.
  5. char password[] = "my_secure_password";: In this example, the password is hardcoded directly into the source code. However, this is an insecure practice.
  6. if (strcmp(input, password) == 0) {: Compare the user input with the hardcoded password.
  7. printf("Access granted.\n");: Display a message if the passwords match.
  8. } else { printf("Access denied.\n"); }: Display a message if the passwords do not match.
  9. return 0;: End the program.

Output:

Enter your password: my_secure_password
Access granted.

Explanation of Output:

In this example, the user is prompted to enter a password. If the entered password matches the hardcoded password “my_secure_password,” the program displays “Access granted.” Otherwise, it displays “Access denied.”

Why Avoiding Common Pitfalls is Important:

Hardcoding sensitive information, such as passwords, directly into the source code is a dangerous practice. If an attacker gains access to the source code, they can easily discover the password, compromising the security of the application. Instead of hardcoding sensitive data, developers should use secure methods for storing and retrieving such information, such as using encryption or secure configuration management tools.

Leave a Reply

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