sprintf and snprintf C Functions – Syntax, Examples, and Security Best Practices

Igal Zeifman
Igal Zeifman

5  min read | min read | 25/05/2023

What ןs the sprintf() Function

In C programming language the sprintf() function is used for formatting strings through the merger of text with variables, numbers, etc.

The sprintf() function accepts a format string as its initial argument, followed by a varying number of extra arguments, which specify what to insert into the formatted string. The final output is then saved in a C-character array.

For example, the following code formats a floating-point decimal with a specified number of decimal points:

#include <stdio.h>

int main(void) {
    double pi = 3.14159265358979323846;
    char buffer[10]; // allocate space for the formatted string
    
    // format the value of pi with two digits after the decimal point
    sprintf(buffer, "%.2f", pi);
    
    // print the formatted string to the console
    printf("pi = %s\n", buffer);
    
    return 0;
}

The concern with the sprintf function is that, when not utilized properly, it can lead to buffer overflows. The similar snprintf the function is more secure because it uses an extra parameter—the maximum number of characters for the output string that can prevent this issue.

Common Use Cases for the sprint() Function

To give a general sense of what the function does, here are examples of how it is commonly used:

  1. Formatting strings with variables: When you need to create a formatted string that includes variables or other data, you can use sprintf to insert the values into the desired positions in the string.
  2. Floating-point number formatting: sprintf can be used to format floating-point numbers with a specific number of decimal places or in scientific notation.
  3. Creating file paths: When constructing file paths dynamically based on user input or other variables, you can use sprintf to format the directory path, file name, and extension.
  4. Date and time formatting: You can use sprintf to format date and time values based on the current system time or other timestamp values.
  5. Generating unique IDs or filenames: If you need to generate unique IDs or filenames based on specific patterns, you can use sprintf to format the values according to the desired pattern.

How sprintf() Works: Syntax Code Example

Here is the general syntax of the sprintf function:

int sprintf(char *str, const char *format, [arg1, arg2, ... ]);

As you can see it had three types of parameters:

  • str—a character array where data will be written
  • format—a C string that defines the output format, including placeholders for the integer arguments to be inserted in the formatted string. It follows the same specifications as print().
  • [arg1, arg2, …]—the integers to be converted

Return value

The return type of the sprintf function is int. If the function executes successfully, it returns the number of characters written to the buffer, excluding the terminating null character. For example,  if the execution fails because the output was bigger than the available buffer, sprintf returns a negative value.

Code example

Below is an example demonstrating the usage of the sprintf() function in C:

#include <stdio.h>

int main()
{
    // Declare a character buffer of size 50
    char buffer[50];

    // Declare and initialize character variables for comparison
    char char1 = 's';
    char char2 = 'a';

    // Calculate the ASCII value difference between the two characters
    int difference = char1 - char2;

    // Assign a formatted string to the buffer using sprintf function
    // Store the return value (number of characters written to the buffer) in an integer variable
    int buffer_length = sprintf(buffer, "Ascii value difference of %c and %c is %d", char1, char2, difference);

    // Print the length of the buffer
    printf("The length of the buffer is: %d\n", buffer_length);

    // Print the value of the buffer string
    printf("The final string is: %s", buffer);

    // Return 0 to indicate the program executed successfully
    return 0;
}

The result should be the following output:

The length of the buffer is: 39
The final string is: Ascii value difference of s and a is 18

sprintf() vs snprintf()

As briefly mentioned above, the main distinction between these two functions is how they deal with buffer overflow situations. The traditional sprintf() function lacks built-in protection against buffer overflows. Writing too much data to the allocated space can lead to issues such as memory corruption or even security threats.

In contrast, the snprintf() function is designed with safety in mind. It incorporates an extra parameter—the maximum number of characters (including the null-terminator) to be written into the output string—which helps prevent buffer overflows by ensuring data does not exceed a specified limit.

This means that there are some benefits to using snprintf() over sprintf() , including:

  • Safety: buffer overflows… Enough said.
  • Improved error handling: With snprintf(), you can detect errors like truncation or encoding issues by checking its return value, whereas with sprintf(), there’s no direct way to determine if something went wrong during formatting.
  • Simplified debugging: When working with complex format strings or large buffers, using snprintf makes it easier to identify issues related to incorrect size calculations since it explicitly states a maximum output size.

To sprintf() or not to sprint()…

Generally, it’s recommended to use snprintf() over sprintf(), particularly in security-critical applications or when dealing with untrusted data. However, there are instances where using sprintf might be acceptable, or even downright necessary. For instance:

  • If you have full control over the input data and can ensure that buffer overflow will not happen.
  • If performance is a critical factor for your application and you are confident about the safety of your code.
  • If you are working on legacy codebases where altering existing function calls could introduce new bugs or compatibility issues.

Security Best Practices

The primary security risk associated with using sprintf() is that of a buffer overflow attack, While being a relatively common issue, these issues are not trivial and could be leveraged to cause some serious damage. For instance, in the context of IoT, This can potentially allow an attacker to take control of a device, compromising system stability and confidentiality.

This is why OWASP (Open Web Application Security Project) ranks buffer overflows among their top 10 most critical web application security risks due to their potential impact on system stability and confidentiality.

Here are some steps you can take to minimize the risk of a buffer overflow attack:

  • Determine buffer size: Always ensure the target buffer is large enough to hold the formatted string and a null terminator. This can be achieved by calculating the maximum possible length of your output string, including any formatting characters.
  • Use snprift: Whenever possible, use snprintf instead of sprintf, sine specifying a limit on the number of characters to be written can help protect against buffer overflow vulnerabilities.
  • Error handling: Check for errors when using these functions. For example, if snprintf return value is greater than or equal to its size parameter (the specified buffer size), it indicates that truncation occurred due to insufficient space in the target buffer. In such cases, consider increasing your buffer size or handling this error appropriately within your application logic.
  • Avoid user-controlled data as format strings: Never use user-controlled data as format strings in calls to these functions, as attackers could potentially exploit this vulnerability by injecting malicious code through crafted input values.

Deterministic Security for IoT

Sternum’s patented EIV™ (embedded integrity verification) technology protects from these with runtime (RASP-like) protection that deterministically prevents all memory and code manipulation attempts, offering blanket protection from a broad range of software weaknesses (CWEs), including the ones related to improper use of sprintf() and other exploitable functions.

Embedding itself directly in the firmware code, EIV™ is agentless and connection agnostic. Operating at the bytecode level, it is also universally compatible with any IoT device or operating system (RTOS, Linux, OpenWrt, Zephyr, Micirum, FreeRTOS, etc.) and has a low overhead of only 1-3%, even on legacy devices.

The runtime protection features are also augmented by (XDR-like) threat detection capabilities of Sternum’s Cloud platform, its AI-powered anomaly detection, and extended monitoring capabilities.

To learn more, check out these case studies of how this technology was used to:

Help a Fortune 500 company catch memory leaks in pre-production (Zephyr device)

Uncover buffer overflow vulnerabilities in 80,000 NAS devices

Uncover buffer overflow vulnerability in a very popular smart plug device

Also check out the video below to see Sternum EIV™ in action, as it provides out-of-the-box mitigation of Ripple20 malware, used for memory corruption attacks.

JUMP TO SECTION

Enter data to download case study

By submitting this form, you agree to our Privacy Policy.